Given how often I've mentioned it recently, my dear readers, you are probably aware that my team and I have been working on a new project. Said project's primary goal was to replace an application that has been in use for many years at our company, in which users could create "drawings" of basic shapes, lines, text, and colors to map out where certain things should be placed at their facilities (think along the lines of an emergency-exit map).

I WISH our drawings were this cool. Photo by Waldemar Brandt / Unsplash

In the process of developing this application, my team and I settled on using a JavaScript library called FabricJS to implement the drawing portion. FabricJS is a library which enables, according to their own site, an "interactive object model on top of canvas element" and, more importantly for us, the ability to save drawings in the JSON format. It was a no-brainer.

It also occurred to us that there weren't any good, long-form tutorials about how to use FabricJS, so that's what I'm writing here. For this post and the next eight, we're going to see how to develop a drawing application using FabricJS and TypeScript.

Credit and Caveat

Before we get started, I need to give credit where credit is due: the code used in these samples was originally developed by my teammate (and fellow long-haired dude) Christopher Jestice. He gave permission for me to use this code and credit him. Refinements to his original code base (as well as certain changes made just for this series) are by me, so we consider these examples a truly joint effort.

Further, Chris and I want you dear readers to know the following: neither of us are intimately familiar with the workings of TypeScript. We chose it because of its similarity to our most-familiar language C#. If anyone out there has suggestions on how to improve the code we use in these posts, please feel free to let us know in the comments.

Goals

The main goal of this blog series is to create a drawing canvas, using FabricJS and TypeScript, which can do the following things:

  • Draw straight lines
  • Draw basic shapes (ovals, rectangles, triangles) and text
  • Draw freeform lines
  • Use colors for lines and fill
  • Use a few line styles (e.g. dotted, solid, dashed)
  • Display status messages
  • Implement cut/copy/paste with hotkeys
  • Have an undo/redo feature with hotkeys
  • Have a "save to server" feature with hotkey

More specifically, we want our final canvas to look like this:

By the end of this nine-part series, our canvas will fully implement all of the above features.

The Sample Project

As with all my code-based posts, this series has a sample project hosted on GitHub. Check it out!

exceptionnotfound/FabricJSDrawingWalkthrough
Contribute to exceptionnotfound/FabricJSDrawingWalkthrough development by creating an account on GitHub.

Without any further ado, let's get started building our drawing canvas with FabricJS and TypeScript!

Setting Up TypeScript

Before we can create the TypeScript classes to represent our drawing, we need a tsconfig.json file to give the TypeScript compiler a group of settings. Here's a shortened version of the one this sample project uses:

{
  "compilerOptions": {
    ...
    "sourceMap": false,
    "outFile": "../wwwroot/js/drawingEditor.js",
    "target": "es2015",
    "allowJs": true,
    "lib": ["es2015", "dom", "dom.Iterable"]
  },
  "compileOnSave": true,
  "include": [
    "*",
    ...
  ],
  "exclude": [
    "node_modules"
  ],
  "paths": {
    "*": [ "*" ]
  }
}
Check the sample project for the complete JSON file.

The two important properties to notice are the "lib" property and the "outFile" property. The "lib" property sets a list of TypeScript library files that need to be included in the compilation; because this project will use some pretty recent features, our list includes "es2015" for ECMAScript 2015 and "dom.iterable" for iteration properties.

Setting Up Libraries

There is some additional setup we need to do: we need to include the libraries for FabricJS, jQuery, Bootstrap, and a few typings for TypeScript. How you choose to include these libraries is up to you; Chris and I both use Visual Studio, and as such we chose to use the unimaginatively-named Library Manager (LibMan) extension. Here's the libman.json file we used:

{
  "version": "1.0",
  "defaultProvider": "cdnjs",
  "libraries": [
    {
      "provider": "unpkg",
      "library": "[email protected]",
      "destination": "wwwroot/lib/bootstrap/"
    },
    {
      "library": "[email protected]",
      "destination": "wwwroot/lib/jquery"
    },
    {
      "library": "[email protected]",
      "destination": "wwwroot/lib/fabric.js"
    },
    {
      "provider": "unpkg",
      "library": "[email protected]",
      "destination": "DrawingEditor/typings/jquery/",
      "files": [
        "jquery.d.ts"
      ]
    },
    {
      "provider": "unpkg",
      "library": "@types/[email protected]",
      "destination": "DrawingEditor/typings/fabric"
    }
  ]
}

Note the "destination" settings for the jQuery and FabricJS typings: they are included in the DrawingEditor directory, which is where we will place our TypeScript classes.

The Starting Markup

We can now start building the markup and script for our drawing canvas.

(NOTE: For this series, we are using ASP.NET Core Razor Pages. If you are unfamiliar with this tech stack, check some of my other posts about it. The markup won't be terribly different in other stacks, I believe.)

FabricJS uses a Canvas element to represent the drawing area. In our case, we want our canvas to use up as much of the visible area of the page as possible, so we are going to create the canvas in a sort-of roundabout way.

Here's our starting markup:

<div class="row editorContainer">
    <div class="CanvasContainer">
        <div id="canvas"></div>
    </div>
</div>

Notice: there's no Canvas element! That's because we're going to replace that innermost div with the Canvas when we create our drawing class in TypeScript. In other words, the innermost div is just a placeholder.

We now need a TypeScript class to represent the drawing area itself. This class needs to be able to replace the inner div with the markup for a FabricJS canvas, as well as create that canvas to a specified size.  Here's the typescript for that class:

class DrawingEditor {
    canvas: fabric.Canvas; //The FabricJS Canvas object 
                           //which will hold all the drawn elements

    constructor(private readonly selector: string, 
                canvasHeight: number, 
                canvasWidth: number) {
        //Replace the given element with a canvas
        $(`#${selector}`)
            .replaceWith(`<canvas id="${selector}" height=${canvasHeight} width=${canvasWidth}> </canvas>`);
        
        //Instantiate a new FabricJS Canvas on the created Canvas element.
        this.canvas = new fabric.Canvas(`${selector}`, { selection: false });
    }
}

The final piece of this initial puzzle is the script on the Razor Page to initialize this class and thereby create the canvas. Said script looks like this:

@section Scripts{
    <script src="~/lib/fabric.js/fabric.min.js"></script>
    <script src="~/js/drawingEditor.js" asp-append-version="true"></script>
    <script>
        $(function () {
            //Calculate canvas height as function of window, 
            //to accommodate different screen sizes
            var canvasHeight = (window.innerHeight - $('#canvas').offset().top) * .9;
            //Calculate canvas width
            var canvasWidth = $('#canvas').width();

            //The canvas is created inside this constructor, using the selector passed.
            var editor = new DrawingEditor('canvas', canvasHeight, canvasWidth);
        });
    </script>
}

The Example Page

With all our pieces in place, we can now run the project. The drawing page gives us the following screenshot:

It's not quite what we saw in the goal photo earlier in the post, but it's a start. The real test is to check the markup.

See the markup in the red box? It shows that a new div, one with class "canvas-container" was inserted where our innermost div was previously, and within the new div are two canvas objects. This is a function of how FabricJS behaves.

We did it! We created a brand new FabricJS canvas onto our Razor Pages app! But it doesn't actually do anything yet, and in the next part of this series, we'll add some very basic line-drawing functionality to our new canvas.

Don't forget to check out the sample project over on GitHub!

In the next part of the series, we'll begin implementing features, starting with a basic one: drawing straight lines. Check out Part 2 of this series!

Drawing with FabricJS and TypeScript Part 2: Straight Lines
Let’s add the ability to draw straight lines, and set up code we need for more drawers in the future!

Happy Coding!