I started developing with Fabric three years ago after discovering the pains of working with the native canvas API. I was creating an interactive design editor for printio.ru—my startup that allows users to design their own apparel. The kind of interactivity I wanted existed only in Flash apps in those days. Now, very few libraries come close to what is possible with Fabric, so let’s take a closer look.
Why Fabric?
Canvas allows you to create some absolutely amazing graphics on the Web these days, but the API it provides is disappointingly low level. It’s one thing if you simply want to draw a few basic shapes on a canvas and forget about them. If you need any kind of interaction, to change a picture at any point, or to draw more complex shapes, the situation changes dramatically. Fabric aims to solve this problem.
Native canvas methods allow you only to fire off simple graphic commands, blindly modifying the entire canvas bitmap. Do you want to draw a rectangle? Use fillRect(left, top, width, height). Want to draw a line? Use a combination of moveTo(left, top) and lineTo(x, y). It’s as if you’re painting a canvas with a brush, layering more and more oil or acrylic on top, with very little control.
Instead of operating at such a low level, Fabric provides a simple but powerful object model on top of native methods. It takes care of canvas state and rendering and lets you work with objects directly.
Here’s a simple example that demonstrates this difference. Let’s say you want to draw a red rectangle somewhere on the canvas. Here’s how you would do it with the native canvas API:
// reference canvas element (with id="c") var canvasEl = document.getElementById('c'); // get 2d context to draw on (the "bitmap" mentioned earlier) var ctx = canvasEl.getContext('2d'); // set fill color of context ctx.fillStyle = 'red'; // create rectangle at a 100,100 point, with 20x20 dimensions ctx.fillRect(100, 100, 20, 20);
The code below shows how you do the same thing with Fabric. The result of both approaches is shown in Figure 1.
// create a wrapper around native canvas element (with id="c") var canvas = new fabric.Canvas('c'); // create a rectangle object var rect = new fabric.Rect({ left: 100, top: 100, fill: 'red', width: 20, height: 20 }); // "add" rectangle onto canvas canvas.add(rect);
Figure 1 Red Rectangle Drawn with Fabric or Native Canvas Methods
At this point, there’s almost no difference in the size of the rectangle—the two examples are pretty similar. However, you can already see how different the approach to working with canvas is. With native methods, you operate on context—an object representing the entire canvas bitmap. In Fabric, you operate on objects—you instantiate them, change their properties, and add them to the canvas. You can see that these objects are first-class citizens in Fabric land.
Rendering a plain red rectangle is too simple. You can at least have some fun with it and perhaps the shape slightly. Let’s try 45 degrees, first using native canvas methods:
var canvasEl = document.getElementById('c'); var ctx = canvasEl.getContext('2d'); ctx.fillStyle = 'red'; ctx.translate(100, 100); ctx.rotate(Math.PI / 180 * 45); ctx.fillRect(-10, -10, 20, 20);
And here’s how you do it in Fabric. (See Figure 2 for the results).
var canvas = new fabric.Canvas('c'); // create a rectangle with angle=45 var rect = new fabric.Rect({ left: 100, top: 100, fill: 'red', width: 20, height: 20, angle: 45 }); canvas.add(rect);
Figure 2 Red, Rotated Rectangle Drawn with Fabric or Native Canvas Methods
What’s happening here? All you have to do in Fabric is change the object’s angle value to 45. With native methods, however, more work is required. Remember that you can’t operate on objects. Instead, you have to tweak the
positioning and angle of the entire canvas bitmap (ctx.translate, ctx.rotate) to suit your needs. You then draw the rectangle again, remembering to offset the bitmap properly (-10, -10) so that it’s still rendered at the point of 100,100. As a bonus, you have to translate degrees to radians when rotating the canvas bitmap.
I’m sure you’re starting to see why Fabric exists and how much low-level boilerplate it hides.
Let’s take a look at another example: keeping track of canvas state.
What if at some point, you want to move the rectangle to a slightly different location on the canvas? How can you do this without being able to operate on objects? Would you just call another fillRect on a canvas bitmap? Not quite. Calling another fillRect command actually draws a rectangle on top of whatever is already drawn on the canvas. To move the rectangle, you need to first erase any previously drawn content and then draw the rectangle at a new location (see Figure 3).
var canvasEl = document.getElementById('c'); ... ctx.strokRect(100, 100, 20, 20); ... // erase entire canvas area ctx.clearRect(0, 0, canvasEl.width, canvasEl.height); ctx.fillRect(20, 50, 20, 20);
Here’s how you would accomplish this with Fabric:
var canvas = new fabric.Canvas('c'); ... canvas.add(rect); ... rect.set({ left: 20, top: 50 }); canvas.renderAll();
Figure 3 Red Rectangle Drawn at a New Location
Notice a very important difference: with Fabric, you don’t need to erase the content before attempting to modify any content. You still work with objects simply by changing their properties and then render the canvas again to get a fresh picture.
Objects
You saw in the last section how to work with rectangles by instantiating the fabric.Rect constructor. Fabric, of course, covers the other basic shapes as well—circles, triangles, ellipses, and so on. The shapes are exposed under the fabric “namespace” as fabric.Circle, fabric.Triangle, fabric.Ellipse, and so on. Fabric provides seven basic shapes:
To draw a circle, just create a circle object and add it to canvas.
var circle = new fabric.Circle({ radius: 20, fill: 'green', left: 100, top: 100 }); var triangle = new fabric.Triangle({ width: 20, height: 30, fill: 'blue', left: 50, top: 50 }); canvas.add(circle, triangle);
You do the same thing with any other basic shape. Figure 4 shows an example of a green circle drawn at location 100,100 and a blue triangle at 50,50.
Figure 4 A Blue Triangle and a Green Circle Drawn with Fabric
Manipulating objects
Creating graphical objects—rectangles, circles, or something else—is only the beginning. At some point, you will probably need to modify your objects. Perhaps a certain action will trigger a change of state or play an animation of some sort. Or you might want to change object properties (such as color, opacity, size, position) on certain mouse interactions.
Fabric takes care of canvas rendering and state management for you. We need only to modify the objects themselves. The example earlier demonstrated the set method and how calling set({ left: 20, top: 50 }) moved the object from its previous location. In a similar fashion, you can change any other property of an object.
As you would expect, Fabric objects have properties related to positioning (left, top), dimensions (width, height), rendering (fill, opacity, stroke, strokeWidth), scaling and rotation (scaleX, scaleY, angle), and flipping (flipX, flipY).Yes, creating flipped object in Fabric is as easy as setting the flip* property to true.
You can read any of these properties via a get method and set them via set. Here’s an example of how to change some of the red rectangle’s properties. Figure 5 shows the results.
var canvas = new fabric.Canvas('c'); ... canvas.add(rect); rect.set('fill', 'red'); rect.set({ strokeWidth: 5, stroke: 'rgba(100,200,200,0.5)' }); rect.set('angle', 15).set('flipY', true);
Figure 5 Red, Rotated, Stroked Rectangle Drawn with Fabric
First, the fill value is set to “red”. The next statement sets the strokeWidth and stroke values, giving the rectangle a 5 px stroke of a pale green color. Finally, the code changes the angle and flipY properties. Notice how each of the three statements uses slightly different syntax.
This demonstrates that set is a universal method. You will probably use it quite often, and it’s meant to be as convenient as possible. What about getters? There’s a generic get method and also a number of specific ones. To read the width property of an object, you use get(‘width’) or getWidth(). To get the scaleX value, you would use get(‘scaleX’), getScaleX() and so on. There’s a method like getWidth or getScaleX for each of the “public” object properties (stroke, strokeWidth, angle, and so on).
You might have noticed that in the earlier examples, objects were created with the same configuration hash as the one we just used in the set method. You can “configure” an object at the time of creation or use the set method later:
var rect = new fabric.Rect({ width: 10, height: 20, fill: '#f55', opacity: 0.7 }); // or functionally identical var rect = new fabric.Rect(); rect.set({ width: 10, height: 20, fill: '#f55', opacity: 0.7 });
Default Options
At this point, you might wonder what happens when you create an object without passing any “configuration” object. Does it still have those properties?
Yes. When specific settings are omitted during creation, objects in Fabric always have a default set of properties. You can use the following code to see this for yourself:
var rect = new fabric.Rect(); // notice no options passed in rect.getWidth(); // 0 rect.getHeight(); // 0 rect.getLeft(); // 0 rect.getTop(); // 0 rect.getFill(); // rgb(0,0,0) rect.getStroke(); // null rect.getOpacity(); // 1
This rectangle has a default set of properties. It’s positioned at 0,0, is black and fully opaque, and has no stroke and no dimensions (width and height are 0). Because no dimensions are given, you can’t see it on the canvas. Giving it any positive values for width and height would reveal a black rectangle at the top-left corner of the canvas, as shown in Figure 6.
Figure 6 How Default Rectangle Looks When Given Dimensions
Hierarchy and Inheritance
Fabric objects do not exist independently of each other. They form a very precise hierarchy. Most objects inherit from the root fabric.Object. The fabric.Object root object represents (more or less) a two-dimensional shape, positioned in a two-dimensional canvas plane. It’s an entity that has left/top and width/height properties, as well as a slew of other graphical characteristics. The properties listed for objects—fill, stroke, angle, opacity, flip*, and so on—are common to all Fabric objects that inherit from fabric.Object.
This inheritance allows you to define methods on fabric.Object and share them among all child “classes”. For example, if you want to have a getAngleInRadians method on all objects, you would simply create it on fabric.Object.prototype, as follows:
fabric.Object.prototype.getAngleInRadians = function() { return this.getAngle() / 180 * Math.PI; }; var rect = new fabric.Rect({ angle: 45 }); rect.getAngleInRadians(); // 0.785... var circle = new fabric.Circle({ angle: 30, radius: 10 }); circle.getAngleInRadians(); // 0.523... circle instanceof fabric.Circle; // true circle instanceof fabric.Object; // true
As you can see, the method immediately becomes available on all instances.
Even though child “classes” inherit from fabric.Object, they often also define their own methods and properties. For example, fabric.Circle needs a radius property, and fabric.Image—which we’ll look at in a moment—needs getElement and setElement methods for accessing and setting the HTML <img> element from which an image instance originates.
Canvas
Now that you’ve learned about objects in some detail, let’s get back to canvas.
The first thing you see in all of the Fabric examples is the creation of a canvas object— new fabric.Canvas(‘…’). The fabric.Canvas object serves as a wrapper around the <canvas> element and is responsible for managing all the Fabric objects on that particular canvas. It takes an ID of an element and returns an instance of fabric.Canvas.
You can add objects to it, reference them from it, or remove them, as shown here:
var canvas = new fabric.Canvas('c'); var rect = new fabric.Rect(); canvas.add(rect); // add object canvas.item(0); // reference fabric.Rect added earlier (first object) canvas.getObjects(); // get all objects on canvas (rect will be first and only) canvas.remove(rect); // remove previously-added fabric.Rect
Managing objects is the main purpose of fabric.Canvas, but it also serves as a configuration host. Do you need to set the background color or image for an entire canvas, clip all contents to a certain area, set a different width and height, or specify whether a canvas is interactive or not? All these options (and others) can be set on fabric.Canvas, either at the time of creation or later.
var canvas = new fabric.Canvas('c', { backgroundColor: 'rgb(100,100,200)', selectionColor: 'blue', selectionLineWidth: 2 // ... }); // or var canvas = new fabric.Canvas('c'); canvas.backgroundImage = 'http://...'; canvas.onFpsUpdate = function(){ /* ... */ }; // ...
Interactivity
One of the unique built-in features of Fabric is a layer of interactivity on top of the object model. The object model exists to allow programmatic access and manipulation of objects on the canvas, but on the outside—on a user level—there’s a way to manipulate those objects via the mouse (or via touch on touch devices). As soon as you initialize a canvas via the new fabric.Canvas(‘…’) call, it’s possible to select objects (see Figure 7), drag them around, scale or rotate them, and even group them (see Figure 8) to manipulate them in one chunk!
Figure 7 Red, Rotated Rectangle in Selected State (Controls Visible)
Figure 8 Rectangle and Circle Grouped (Controls Visible)
If you want to allow users to drag something on the canvas—let’s say an image—all you need to do is initialize the canvas and add an object to it. No additional configuration or setup is required.
To control this interactivity, you can use Fabric’s selection Boolean property on the canvas object in combination with the selectable Boolean property of individual objects:
var canvas = new fabric.Canvas('c'); ... canvas.selection = false; // disable group selection rect.set('selectable', false); // make object unselectable
But what if you don’t want an interactivity layer at all? If that’s the case, you can always replace fabric.Canvas with fabric.StaticCanvas. The syntax for initialization is absolutely the same:
var staticCanvas = new fabric.StaticCanvas('c'); staticCanvas.add( new fabric.Rect({ width: 10, height: 20, left: 100, top: 100, fill: 'yellow', angle: 30 }));
This creates a “lighter” version of canvas, without any event-handling logic. You still have the entire object model to work with—adding, removing or modifying objects, as well as changing any canvas configuration. All of this still works, it’s only event handling that’s gone.
Later in this article, when I go over the custom build option, you’ll see that if StaticCanvas is all you need, you can even create a lighter version of Fabric. This could be a nice option if you need something like non-interactive charts or non-interactive images with filters in your application.
Images
Adding rectangles and circles to a canvas is fun, but as you can imagine by now, Fabric also makes working with images very easy. Here’s how you instantiate the fabric.Image object and add it to a canvas, first in HTML and then in JavaScript:
HTML
<canvas id="c"></canvas> <img src="my_image.png" id="my-image">
JavaScript
var canvas = new fabric.Canvas('c'); var imgElement = document.getElementById('my-img'); var imgInstance = new fabric.Image(imgElement, { left: 100, top: 100, angle: 30, opacity: 0.85 }); canvas.add(imgInstance);
Notice that you pass an image element to the fabric.Image constructor. This creates an instance of fabric.Image that looks just like the image from the document. Moreover, you immediately set left/top values to 100/100, angle to 30, and opacity to 0.85. Once an image is added to a canvas, it is rendered at location 100,100 at a 30-degree angle and is slightly transparent (see Figure 9). Not bad!
Figure 9 Slightly Transparent and Rotated Image, Rendered with Fabric
If you don’t really have an image in a document but only a URL for an image, you can use fabric.Image.fromURL:
fabric.Image.fromURL('my_image.png', function(oImg) { canvas.add(oImg); });
Looks pretty straightforward, doesn’t it? Just call fabric.Image.fromURL, with a URL of an image, and give it a callback to invoke once the image is loaded and created. The callback function receives the already created fabric.Image object as its first argument. At that point, you can add it to your canvas or perhaps change it first and then add it, as shown here:
fabric.Image.fromURL('my_image.png', function(oImg) { // scale image down, and flip it, before adding it onto canvas oImg.scale(0.5).setFlipX(true); canvas.add(oImg); });
Path and PathGroup
We’ve looked at simple shapes and images. What about more complex, richer shapes and content? Meet Path and PathGroup, the power couple.
Paths in Fabric represent an outline of a shape, which can be filled, stroked and modified in other ways. Paths consist of a series of commands that essentially mimic a pen going from one point to another. With the help of such commands as move, line, curve, and arc, Paths can form incredibly complex shapes. And with the help of groups of Paths (PathGroup), the possibilities open up even more. Paths in Fabric closely resemble SVG <path> elements. They use the same set of commands, can be created from <path> elements, and can be serialized into them. I’ll describe more about serialization and SVG parsing later, but for now it’s worth mentioning that you will probably only rarely create Path instances by hand. Instead, you’ll use Fabric’s built-in SVG parser. But to understand what Path objects are, let’s create a simple one by hand (see Figure 10 for the results):
var canvas = new fabric.Canvas('c'); var path = new fabric.Path('M 0 0 L 200 100 L 170 200 z'); path.set({ left: 120, top: 120 }); canvas.add(path);
Figure 10 Simple Path Rendered with Fabric
Here you instantiate the fabric.Path object and pass it a string of path instructions. It might look cryptic, but it’s actually easy to understand. M represents the move command and tells the invisible pen to move to point 0, 0. L stands for line and makes the pen draw a line to point 200, 100. Then, another L creates a line to 170, 200. Lastly, z forces the drawing pen to close the current path and finalize the shape.
Since fabric.Path is just like any other object in Fabric, you can also change some of its properties, or modify it even more, as shown here and in Figure 11:
... var path = new fabric.Path('M 0 0 L 300 100 L 200 300 z'); ... path.set({ fill: 'red', stroke: 'green', opacity: 0.5 }); canvas.add(path);
Figure 11 A Simple, Modified Path
Out of curiosity, let’s take a look at a slightly more complex path syntax. You’ll see why creating paths by hand might not be the best idea:
... var path = new fabric.Path('M121.32,0L44.58,0C36.67,0,29.5,3.22,24.31,8.41 c-5.19,5.19-8.41,12.37-8.41,20.28c0,15.82,12.87,28.69,28.69,28.69c0,0,4.4, 0,7.48,0C36.66,72.78,8.4,101.04,8.4,101.04C2.98,106.45,0,113.66,0,121.32 c0,7.66,2.98,14.87,8.4,20.29l0,0c5.42,5.42,12.62,8.4,20.28,8.4c7.66,0,14.87 -2.98,20.29-8.4c0,0,28.26-28.25,43.66-43.66c0,3.08,0,7.48,0,7.48c0,15.82, 12.87,28.69,28.69,28.69c7.66,0,14.87-2.99,20.29-8.4c5.42-5.42,8.4-12.62,8.4 -20.28l0-76.74c0-7.66-2.98-14.87-8.4-20.29C136.19,2.98,128.98,0,121.32,0z'); canvas.add(path.set({ left: 100, top: 200 }));
Here, M still stands for the move command, so the pen starts its drawing journey at point 121.32, 0. Then there’s an L (line) command that brings the pen to 44.58, 0. So far so good. Now comes the C command, which stands for “cubic bezier.” This command makes the pen draw a bezier curve from the current point to 36.67, 0. It uses 29.5, 3.22 as a control point at the beginning of a line, and 24.31, 8.41 as the control point at the end of the line. This
whole operation is then followed by a dozen other cubic bezier commands, which finally create a nice-looking shape of an arrow, as shown in Figure 12.
Figure 12 Complex Path Rendered with Fabric
Chances are, you won’t work with such beasts directly. Instead, you can use something like the fabric.loadSVGFromString or fabric.loadSVGFromURL method to load an entire SVG file and let Fabric’s SVG parser do its job of walking over all SVG elements and creating corresponding Path objects.
In this context, while Fabric’s Path object usually represents a SVG <path> element, a collection of paths, often present in SVG documents, is represented as a PathGroup instance (fabric.PathGroup). PathGroup is nothing but a group of Path objects, and because fabric.PathGroup inherits from fabric.Object, it can be added to a canvas just like any other object and manipulated the same way.
Just like with Paths, you probably won’t be working with a PathGroup directly. But if you stumble on one after parsing a SVG document, you’ll know exactly what it is and what purpose it serves.
Wrapping Up For Now
I’ve only scratched the surface of what’s possible with Fabric. You can now easily create any of the simple shapes, complex shapes, or images; add them to a canvas and modify them any way you want—their positions, dimensions, angles, colors, strokes, opacity—you name it.
In the next article in this series, I’ll look at working with groups; animation; text; SVG parsing, rendering and serialization; events; image filters and more. Meanwhile, feel free to take a look at the annotated demos or benchmarks, join the discussion at Stack Overflow or go straight for the docs, wiki, and source. You can also learn more about HTML5 Canvas at the MSDN IE Developer Center, or check out Rey Bango’s An Introduction to the HTML 5 Canvas Element on Script Junkie.
Have fun experimenting with Fabric! I hope you enjoy the ride.
This article was originally published at http://msdn.microsoft.com/en-us/magazine/jj856929.aspx and is reproduced here with permission.