Matthew Jones

Our drawing canvas is now created, but it doesn't do anything yet. In this part of our series on creating a drawing canvas with FabricJS and TypeScript, we're going to refactor our drawing editor to draw what might be the simplest kind of shape: a straight line. Unfortunately, doing such a thing is not as easy as it sounds.

Fabric Objects, Drawers, and DrawingMode

FabricJS has a concept of "objects" or two-dimensional things that have been placed onto the drawing canvas. In FabricJS, ALL 2D objects inherit from a base "Object" class. Objects can then implement certain events.

In this tutorial, we have a concept of "drawers" or classes that create objects. For each kind of object that can be drawn onto the canvas, we will have a "drawer" class which is responsible for creating the object, which in turn inherits from Fabric.Object. Each of these drawers should therefore implement an interface, one which allows them to "draw" the object they represent.

Note that this interface expects the classes that implement its methods to return a Promise<fabric.Object> for both the make() and resize() methods.

Also note the drawingMode property. This is an enum which tells the drawing canvas which mode we are currently in (alternatively, which shape will be drawn on the next mouse event); this allows the user to draw a single shape or line multiple times without needing to set the drawer again. The values are as follows:

Note that we are using the Fabric.Line class, which is FabricJS's implementation of a straight line. Wherever possible, we will use the FabricJS definitions for our classes.

We now have a class that will draw straight lines for us! Now what we need to do is create events that our canvas can listen to, which will allow the user to actually draw the lines.

Canvas Properties

At this point in the tutorial, we need to make some changes to the root DrawingEditor object we created in Part 1.

The first thing we need to do is allow the drawing editor to keep track of which drawer class is currently being used, as well as all the possible drawer classes that can be selected. For this, we need to modify the DrawingEditor class like so:

These changes will allow us to modify which drawer is currently selected based on some kind of user input. You'll see specific demos of this in later parts of the series.

Let's now discuss exactly how we want the user to interface with the canvas in order to draw a line.

User Interactions and Events

For this tutorial series, we're going to specify that a user draws an object by clicking down the mouse button, dragging the cursor to a new location, and then releasing the button. In this way, our drawing canvas will behave like many other drawing applications, using a "click-drag-release" pattern.

That means we must define "events" for each part of this sequence: mouse down (click), mouse over while holding button (drag), mouse up (release). We also need to specify what happens if the user is clicking-and-dragging the mouse over an-already created object, where the already-created object should not be modified in any way.

The problem is the "dragging". We need a way to tell the canvas that the user is currently holding down the mouse button, and therefore certain interactions should be modified. We do this by adding a new property to the DrawingEditor class:

When the user clicks the mouse within the canvas, the drawer should begin to draw an object starting from the point where the mouse was clicked. Fabric provides us with the MouseEvent class to represent mouse events, as well as a canvas.getPointer() method we can use to get the current position of the mouse pointer at the time of the event. By getting the MouseEvent and the pointer location, we can initialize an event and call our mouseDown() method like so:

MouseUp Event (Release)

Example Lines

With all of the events in place, we can now draw straight lines! Here's a GIF of this code in action:

Bug: Drawing Lines when Moving Objects

We've done quite a lot so far! However, there's a problem with our current implementation: while trying to drag an already-created object (a function which Fabric supports natively) we will accidentally draw another line! It looks like this:

We need to do some additional work to ensure this situation doesn't happen.

First, we need to keep track of what "mode" the cursor is in at any given time. This means we need to know if it is currently drawing or selecting an object. Hence, we need a new enumeration:

const enum CursorMode {
Draw,
Select
}

We also need our DrawingEditor object to keep track of the current CursorMode:

Finally, we need two new events: one in which when an object is selected the cursor mode is changed to Select; and one in which when the selected object is "cleared" (i.e. no longer selected) the cursor mode is reset to the default Draw.

GIF Time!

With all of this in place, we no longer draw lines when moving already created objects, as shown by this GIF:

Summary

In the second part of our Drawing with FabricJS and TypeScript series, we used our previously-created DrawingEditor object repesenting a canvas and refactored it to code up the ability to draw straight lines. In the process, we learned about "drawer" classes and mouse events, and which ones we needed to implement. We also encountered and fixed a bug that happened when dragging already-created lines.

In the next part of the series, we'll start working on drawers for basic shapes, including ovals and rectangles, as well as for text. We'll also need to work out a way for the user to select which drawer they want to use. For all of that, check out Part 3 of Drawing with FabricJS and TypeScript!