Graphics Editor
In this chapter, we will integrate FabricJS with our app and create an HTML canvas-based editor.
Editing graphics on the web requires working with the canvas
element. Fabric is a popular canvas library that we are going to use. We'll create an editor component to show at the /graphics/:graphicId
route.
By the end of this chapter, we'll have an editor with a canvas and options to add elements like shapes and text:

The big idea is to develop the editor
component like a controlled component, for example, an input. This way, we can pass the initial value of the canvas to the editor and add an on-change handler, and keep the editor mostly pure:

Install dependencies#
We'll need the fabric
and file-saver
package to build our editor:
yarn add fabric file-saver
fabric
provides a pragmatic wrapper to HTML5 Canvas and file-saver
helps in downloading files, which will be helpful for exporting our graphics.
Editor component#
Fabric works by hijacking an existing canvas node and elevating it to a fabric.Canvas
object. This enables Fabric's rich API. It lets us add and remove objects like text, SVG, and images. It also lets us serialize and de-serialize the canvas.
We will create an editor component in app.components.editor
.
Set up editor namespace#
Here's the ns
setup. We have imported all the required dependencies for the sake of convenience, but feel free to add more as and when you need them:
(ns app.components.editor
(:require [reagent.core :as r]
["fabric" :refer (fabric)]
["file-saver" :refer (saveAs)]
["@blueprintjs/core" :refer (Colors Button Intent InputGroup)]))
Accessing fabric classes#
fabric
exposes a variety of classes that we'll need to initialize objects. The ES6 syntax for importing with fabric
is:
import {fabric} from "fabric";
const c = new fabric.Canvas(args)
The equivalent CLJS would be:
(ns
[:require "fabric" :refer (fabric)])
(def c (new (aget fabric "Canvas") args))
We also need to ensure that all args
passed to fabric
are native JS objects and not CLJS data structures - ie be prepared to call clj->js
excessively. Data shape is a common source of bugs while dealing with native JS libraries.
Canvas component#
To be able to work with fabric
, we need a canvas
element in the DOM. Once the canvas element exists, we need to initialize the fabric.Canvas
class. We also need to clean up when the canvas is unmounted.
Let's create a canvas component using r/create-class
:
(defn canvas [f-canvas set-f-canvas set-selected-object]
(r/create-class
{:display-name ::canvas
:component-did-mount
(fn []
(let [initialized-canvas (new (aget fabric "Canvas") "c"
(clj->js {:height 600 :width 800}))]
(set-f-canvas initialized-canvas)
;; object selection event hook
(.on initialized-canvas "mouse:down"
#(set-selected-object (.-target %)))
))
:component-will-unmount
(fn []
(when f-canvas
(.dispose f-canvas))
(set-f-canvas nil))
:reagent-render
(fn []
[:canvas#c.border-2.border-gray-700])}))
The
canvas
component is a class-based component that receives thef-canvas
(fabric.Canvas
) object, aset-f-canvas
andset-selected-object
function. We'll talk aboutset-selected-object
later in this chapter.f-canvas
andset-f-canvas
are equivalent tovalue
andon-change
props on an input component. This makes the canvas controlled, as whenf-canvas
changes, the parent will be notified. This will all make more sense soon!The
:display-name
property helps with debugging.The
:reagent-render
method renders a:canvas
element with idc
.In
:component-did-mount
method, afabric.Canvas
object is initialized on the canvas with idc
. This method is called after the first render, ie thecanvas#c
will be available in DOM. The initialized canvas is given some default properties like:height
and:width
. Then theset-f-canvas
method is called, indicating to the parent that the controlled component changed state.The call to
.on
method is a hook into Fabric's event system. We'll cover this shortly.The
:component-will-unmount
method cleans up the initialized canvas by calling the.dispose
method onf-canvas
Background color component#
The canvas
will enhance the native HTML5 canvas, but we need more tools to manipulate it. The bg-color
tool will present the user with a list of colors to set as the canvas background:
(defn tool-title [t]
[:div.text-gray-800.text-lg.py-2 t])
This page is a preview of Tinycanva: Clojure for React Developers