Introduction to Fabric.js: the Fun Stuff

Tweet

In the first article in this series, I looked at the reasons to use Fabric.js, at its object model and object hierarchy, and at different kinds of entities available in Fabric—simple shapes, images, and complex paths. I also described how to perform simple operations with Fabric objects on a canvas. Now that most of the basics are out of the way, let’s get down to the fun stuff.

Animation

No respectable canvas library goes without an animation facility, and Fabric is no exception. Given Fabric’s powerful object model and graphical capabilities, it would be a shame not to have animation helpers built in.

Remember how easy it is to change the property of any object? You just call the set method, passing the corresponding values:

        rect.set('angle', 45);

Animating an object is just as easy. Every Fabric object has an animate method that, well… animates that object.

        rect.animate('angle', 45, {
	  onChange: canvas.renderAll.bind(canvas)
	});

The first argument is the property to animate, and the second argument is the ending value of the animation. If a rectangle has a -15° angle, and you pass 45 in the second argument, the rectangle is animated from -15° to 45°. The third argument is an optional object specifying finer details of animation, such as duration, callbacks, easing, and so on. I’ll show examples of these shortly.

One convenient feature of the animate method is that it supports relative values. For example, if you want to animate an object’s left property by 100px, you can do it like this:

        rect.animate('left', '+100', { onChange: canvas.renderAll.bind(canvas) });

Similarly, rotating an object 5 degrees counterclockwise can be accomplished like so:

        rect.animate('angle', '-5', { onChange: canvas.renderAll.bind(canvas) });

You might wonder why I always specify an onChange callback here. As I mentioned, the third argument is optional, but calling canvas.renderAll on each animation frame is what allows you to see actual animation. When you call the animate method, it only animates a property value over time, following a specific algorithm (for example, easing). So, rect.animate('angle', 45) changes an object’s angle but won’t rerender the canvas after each change of the angle. And, obviously, you need this rerendering to see the animation.

Remember that there’s an entire object model beneath that canvas surface. Objects have their own properties and relations, and a canvas is responsible only for projecting the objects’ existence to the outside world.

The reason animate doesn’t automatically rerender the canvas after each change is performance. After all, you can have hundreds or thousands of animating objects on a canvas, and it wouldn’t be wise if every one of them tried to rerender the screen. Most of the time, you probably need to explicitly specify canvas.renderAll as the onChange callback.

Other options you can pass to animate are the following:

  • from Allows you to specify a starting value of the property being animated (if you don’t want to use the current value).
  • duration Defaults to 500 ms. This option can be used to change the duration of an animation.
  • onComplete The callback that’s invoked at the end of the animation.
  • easing The easing function.

All of these options should be self-explanatory, except perhaps easing. Let’s take a closer look at it.

By default, animate uses a linear function for animation. If that’s not what you need, there’s a slew of easing options available in fabric.util.ease. For example, if you want to move an object to the right in a bouncy fashion, do this:

        rect.animate('left', 500, {
	  onChange: canvas.renderAll.bind(canvas),
	  duration: 1000,
	  easing: fabric.util.ease.easeOutBounce
	});

Notice that fabric.util.ease.easeOutBounce is an easing option. Other notable options include easeInCubic, easeOutCubic, easeInElastic, easeOutElastic, easeInBounce, and easeOutExpo.

Just to give you some idea of what becomes possible with animation in Fabric, you can animate an object’s angle to make it rotate; animate left or top properties to make it move; animate its width and height to make it shrink and grow; animate opacity to make it fade in and out; and so on.

Image filters

In the first article in this series, you saw how to work with images in Fabric. There’s the fabric.Image constructor that accepts an image element. There’s also the fabric.Image.fromURL method, which can create an image instance from a URL string. Any of these images can be thrown and rendered on a canvas just like any other object.

But as fun as it is to work with images, it’s even cooler to apply image filters to them. Fabric provides a few filters by default (you can view them here) and makes defining your own filters easy. Some of the built-in filters you might already be familiar with are a filter to remove a white background, the grayscale filter, or invert or brightness filters. Others might be a little less familiar, such as gradient transparency, sepia, or noise.

Every instance of fabric.Image has a filters property, which is a simple array of filters. Each of the filters in that array is an instance of one of the Fabric filters or an instance of a custom filter.

Here’s the code you use to create a grayscale image. Figure 1 shows the results.

        fabric.Image.fromURL('pug.jpg', function(img) {
	 // add filter
	  img.filters.push(new fabric.Image.filters.Grayscale());
	  // apply filters and re-render canvas when done
	  img.applyFilters(canvas.renderAll.bind(canvas));
	  // add image onto canvas
	  canvas.add(img);
	});

Applying a Grayscale Image Filter
Figure 1. Applying a Grayscale Image Filter

And here’s how to create a sepia version of an image, which results in the image effects shown in Figure 2.

        fabric.Image.fromURL('pug.jpg', function(img) {
	  img.filters.push(new fabric.Image.filters.Sepia());
	  img.applyFilters(canvas.renderAll.bind(canvas));
	  canvas.add(img);
	});

Applying a Sepia Image Filter
Figure 2. Applying a Sepia Image Filter

Because the filters property is a simple array, you can perform any operation you want with it in the usual way—remove a filter (via pop, splice, or shift), add a filter (via push, splice, unshift), or even combine multiple filters. Any filters present in the filters array will be applied one by one when you call applyFilters.

Here’s how you can create an image that’s both sepia and bright. Figure 3 shows the results.

        fabric.Image.fromURL('pug.jpg', function(img) {
	  img.filters.push(
	    new fabric.Image.filters.Sepia(),
	    new fabric.Image.filters.Brightness({ brightness: 100 }));
	  img.applyFilters(canvas.renderAll.bind(canvas));
	  canvas.add(img);
	});

Combining a Sepia and a Bright Image Filter
Figure 3. Combining a Sepia and a Bright Image Filter

Notice that I also passed the { brightness: 100 } object to the brightness filter. That’s because some filters can be applied without any additional configuration (for example, grayscale, invert, sepia), and others provide finer control for their behavior. For the brightness filter, it’s the actual brightness level (0–255). For the noise filter, it’s the noise value (0–1000). For the remove white filter, it’s the threshold and distance values. And so on.

Now that you’re familiar with Fabric filters, it’s time to break out of the box and create your own. The template for creating a filter is pretty straightforward. You need to create a class and then define an applyTo method. Optionally, you might give the filter the toJSON method (support for JSON serialization) or the initialize method (support for optional parameters). Below is an example of the code, with the results shown in Figure 4.

        fabric.Image.filters.Redify = fabric.util.createClass({
	  type: 'Redify',
	  applyTo: function(canvasEl) {
	    var context = canvasEl.getContext('2d'),
	      imageData = context.getImageData(0, 0,
	        canvasEl.width, canvasEl.height),
	      data = imageData.data;
	    for (var i = 0, len = data.length; i < len; i += 4) {
	      data[i + 1] = 0;
	      data[i + 2] = 0;
	    }
	    context.putImageData(imageData, 0, 0);
	  }
	});
	fabric.Image.filters.Redify.fromObject = function(object) {
	  return new fabric.Image.filters.Redify(object);
	};

Applying a Custom Image Filter
Figure 4. Applying a Custom Image Filter

Without delving too much into this code, the main action happens in a loop, where I replace the green (data[i+1]) and blue (data[i+2]) components of each pixel with 0, essentially removing them. The red component of standard RGB values stays untouched, essentially painting the entire image red. As you can see, the applyTo method is passed to the main canvas element representing the entire image. From there, you can iterate over its pixels (getImageData().data), modifying them in any way you want.

Colors

Whether you’re more comfortable working with hex, RGB, or RGBA colors, Fabric provides a solid color foundation to help you express yourself most naturally. Here are some of the ways in which you can define a color in Fabric:

        new fabric.Color('#f55');
	new fabric.Color('#123123');
	new fabric.Color('356735');
	new fabric.Color('rgb(100,0,100)');
	new fabric.Color('rgba(10, 20, 30, 0.5)');

Conversion is straightforward as well. The toHex() method converts color instances to hex representation, toRgb() to RGB colors, and toRgba() to RGB with alpha channel.

        new fabric.Color('#f55').toRgb(); // "rgb(255,85,85)"
	new fabric.Color('rgb(100,100,100)').toHex(); // "646464"
	new fabric.Color('fff').toHex(); // "FFFFFF"

Conversion is not the only step you can take with colors. You can also overlay one color with another or turn it to a grayscale version.

        var redish = new fabric.Color('#f55');
	var greenish = new fabric.Color('#5f5');
	redish.overlayWith(greenish).toHex(); // "AAAA55"
	redish.toGrayscale().toHex(); // "A1A1A1"

Gradients

An even more expressive way to work with colors is via gradients. Gradients allow you to blend one color with another, creating some stunning graphical effects.

Fabric supports gradients through the setGradientFill method, which is defined on all objects. Calling setGradientFill is just like setting the fill value of an object, except that you fill the object with a gradient rather than a single color. Below is some sample code, with the visual effect shown in Figure 5.

        var circle = new fabric.Circle({
	  left: 100,
	  top: 100,
	  radius: 50
	});
	circle.setGradientFill({
	  x1: 0,
	  y1: 0,
	  x2: 0,
	  y2: circle.height,
	  colorStops: {
	  0: '#000',
	  1: '#fff'
	}
	});

Applying a Gradient Fill to an Object
Figure 5. Applying a Gradient Fill to an Object

In this example, I create a circle at location 100,100, with a 50px radius. I then set its fill to a gradient from white to black that spans the entire height of that circle.

The argument passed to a method is an options object, which expects two coordinate pairs (x1, y1 and x2, y2), as well as a colorStops object. Coordinates specify where a gradient starts and where it ends. The colorStops object specifies which colors a gradient is made of. You can define as many color stops as you want, as long as they range from 0 to 1 (for example, 0, 0.1, 0.3, 0.5, 0.75, 1, and so on). Zero (0) represents the beginning of a gradient, and 1 represents its end.

Here’s code that creates a left-to-right, red-blue gradient. Figure 6 shows the results.

        circle.setGradientFill({
	  x1: 0,
	  y1: circle.height / 2,
	  x2: circle.width,
	  y2: circle.height / 2,
	  colorStops: {
	    0: "red",
	    1: "blue"
	  }
	});

A Gradient Created Using Color Stops
Figure 6. A Gradient Created Using Color Stops

The code below shows a five-stop rainbow gradient, with colors spanning even 20 percent intervals. Figure 7 shows the results.

        circle.setGradientFill({
	  x1: 0,
	  y1: circle.height / 2,
	  x2: circle.width,
	  y2: circle.height / 2,
	  colorStops: {
	  0: "red",
	    0.2: "orange",
	    0.4: "yellow",
	    0.6: "green",
	    0.8: "blue",
	    1: "purple"
	}
	});

A Rainbow Gradient
Figure 7. A Rainbow Gradient

Which cool versions can you come up with?

Text

What if you want to display not only images and vector shapes on a canvas but also text? Fabric has you covered through fabric.Text objects.

There are two reasons for providing text abstraction in Fabric. First, it allows you to work with text in an object-oriented fashion. Native canvas methods—as usual—only allow you to fill or stroke text on a very low level. By instantiating fabric.Text instances, you can work with text just like you work with any other Fabric object—move it, scale it, change its properties, and so on.

The second reason is to provide much richer functionality than what the canvas element gives us. Some of the Fabric additions include:

Multiline support Native text methods, unfortunately, simply ignore new lines.
Text alignment Left, center, and right. Useful when working with multiple lines of text.
Text background Background also respects text alignment.
Text decoration Underline, overline, and strikethrough.
Line height  Useful when working with multiple lines of text.

Here’s a “hello world” example:

        var text = new fabric.Text('hello world', { left: 100, top: 100 });
	  canvas.add(text);
	});

That’s right! Displaying text on a canvas is as simple as adding an instance of fabric.Text at a specified location. As you can see, the only required parameter is the actual text string. The second argument is the usual options object, which can have any of the usual properties, such as left, top, fill, opacity, and so on.

But, of course, text objects also have their own text-related properties. Let’s look at some of them.

fontFamily

Set as Times New Roman by default, the fontFamily property allows you to change the font family used to render a text object. Changing the property immediately renders text in the new font. Figure 8 shows the effects created by using the following code.

        var comicSansText = new fabric.Text("I'm in Comic Sans", {
	  fontFamily: 'Comic Sans'
	});

A Change to the fontFamily Property
Figure 8. A Change to the fontFamily Property

fontSize

Font size controls the size of rendered text. Note that unlike with other objects in Fabric, you can’t change a text object’s width and height properties directly. Instead, you need to change the fontSize value to make text objects larger, as you can see in Figure 9. (Either that, or you can use scaleX/scaleY properties.)

        var text40 = new fabric.Text("I'm at fontSize 40", {
	  fontSize: 40
	});
	var text20 = new fabric.Text("I'm at fontSize 20", {
	  fontSize: 20
	});

Controlling Font Size
Figure 9. Controlling Font Size

fontWeight

Font weight lets you make text look thicker or thinner. Just as in CSS, you can use keywords (such as normal or bold—see Figure 10 for an example) or numbers (100, 200, 400, 600, 800). Whether you can use certain weights depends on the availability of that weight for a chosen font. If you’re using a remote font, you need to be sure you provide both normal and bold (as well as any other required weight) font definitions.

        var normalText = new fabric.Text("I'm a normal text", {
	  fontWeight: 'normal'
	});
	var boldText = new fabric.Text("I'm at bold text", {
	  fontWeight: 'bold'
	});

Font Weight Can Be Controlled by Keywords or Numerical Values
Figure 10. Font Weight Can Be Controlled by Keywords or Numerical Values

textDecoration

You use text decoration to add underline, overline, or strikethrough to text. Again, this is similar to CSS, but Fabric goes a little further and allows you to use any combination of these decorations together. So, you can have text that’s both underlined and overlined, underlined with strikethrough, and so on, as you can see in Figure 11.

        var underlineText = new fabric.Text("I'm underlined text", {
	  textDecoration: 'underline'
	});
	var strokeThroughText = new fabric.Text("I'm stroke-through text", {
	  textDecoration: 'line-through'
	});
	var overlineText = new fabric.Text("I'm overlined text", {
	  textDecoration: 'overline'
	});

Examples of text decorations
Figure 11. Examples of text decorations

textShadow

Text shadows consist of four components: color, horizontal offset, vertical offset, and blur size. These effects might be very familiar if you’ve worked with shadows in CSS. Lots of combinations are possible (see Figure 12) by changing these values.

        var shadowText1 = new fabric.Text("I'm a text with shadow", {
	  textShadow: 'rgba(0,0,0,0.3) 5px 5px 5px'
	});
	var shadowText2 = new fabric.Text("And another shadow", {
	  textShadow: 'rgba(0,0,0,0.2) 0 0 5px'
	});
	var shadowText3 = new fabric.Text("Lorem ipsum dolor sit", {
	  textShadow: 'green -5px -5px 3px'
	});

Examples of Text Shadows fontStyle
Figure 12. Examples of Text Shadows

fontStyle

A font style can be one of two values: normal or italic. This is similar to the CSS property of the same name. The following code shows some examples of using fontStyle, and Figure 13 shows the results.

        var italicText = new fabric.Text("A very fancy italic text", {
	  fontStyle: 'italic',
	  fontFamily: 'Delicious'
	});
	var anotherItalicText = new fabric.Text("another italic text", {
	  fontStyle: 'italic',
	  fontFamily: 'Hoefler Text'
	});

Examples of Italic Font Styles
Figure 13. Examples of Italic Font Styles

strokeStyle and strokeWidth

By combining strokeStyle (color of the stroke) and strokeWidth (its width), you can achieve some interesting text effects, as shown in Figure 14. Here are a couple of code examples:

        var textWithStroke = new fabric.Text("Text with a stroke", {
	  strokeStyle: '#ff1318',
	  strokeWidth: 1
	});
	var loremIpsumDolor = new fabric.Text("Lorem ipsum dolor", {
	  fontFamily: 'Impact',
	  strokeStyle: '#c3bfbf',
	  strokeWidth: 3
	});

Text Effects Using strokeStyle and strokeWidth
Figure 14. Text Effects Using strokeStyle and strokeWidth

textAlign

Text alignment is useful when you’re working with a multiline text object. With a one-line text object, the width of the bounding box always matches that line’s width, so there’s nothing to align.

Allowed values for textAlign are left, center, and right. Figure 15 shows right-aligned text.

        var text = 'this isna multilinentextnaligned right!';
	var alignedRightText = new fabric.Text(text, {
	  textAlign: 'right'
	});

Right-Aligned Text
Figure 15. Right-Aligned Text

lineHeight

Another property that might be familiar from CSS is lineHeight. It allows you to change vertical spacing between text lines in multiline text. In the following example, the first chunk of text has lineHeight set to 3, and the second one to 1. The results you see are shown in Figure 16.

        var lineHeight3 = new fabric.Text('Lorem ipsum ...', {
	  lineHeight: 3
	});
	var lineHeight1 = new fabric.Text('Lorem ipsum ...', {
	  lineHeight: 1
	});

Examples of Line Height
Figure 16. Examples of Line Height

backgroundColor

Finally, backgroundColor is what allows you to give text a background. Note that a background fills only the space occupied by text characters, not the entire bounding box, as you can see in Figure 17. This means that text alignment changes the way the text background is rendered—and so does line height, because background respects vertical spacing between lines created by lineHeight.

        var text = 'this isna multilinentextnwithncustom lineheightn&background';
	var textWithBackground = new fabric.Text(text, {
	  backgroundColor: 'rgb(0,200,0)'
	});

Text Background Effects
Figure 17. Text Background Effects

Events

The event-driven architecture is the basis for some amazing power and flexibility within a framework. Fabric is no exception, and it provides an extensive event system, starting from low-level mouse events to high-level object ones.

These events allow you to tap into different moments of various actions happening on a canvas. Do you want to know when the mouse was pressed? Just observe the mouse:down event. How about when an object was added to a canvas? In this case, object:added is there for you. And what about when the entire canvas is rerendered? Just use after:render.

The event API is very simple and resembles that of jQuery, Underscore.js, or other popular JS libraries. There’s an on method to initialize the event listener, and an off method to remove it.

Here’s an example:

        var canvas = new fabric.Canvas('...');
	canvas.on('mouse:down', function(options) {
	  console.log(options.e.clientX, options.e.clientY);
	});

In this code, I’m adding the mouse:down event listener onto the canvas and giving it an event handler that will log coordinates of where the event originated. In other words, the handler will log where exactly on the canvas the mouse was pressed. The event handler receives an options object, which has two properties: e, which is the original event, and target, which is a clicked object on the canvas, if any. The event is present at all times, but the target exists only if a user actually does click an object on the canvas. Also, the target is passed to handlers of events only where it makes sense—for example, for mouse:down but not for after:render (which denotes that the entire canvas was redrawn).

        canvas.on('mouse:down', function(options) {
	  if (options.target) {
	    console.log('an object was clicked! ', options.target.type);
	  }
	});

This example will log “an object was clicked!” if you click an object. It will also add the type of object clicked.

Some of the other mouse-level events available in Fabric are mouse:move and mouse:up. Generic events include after:render, and there are also selection-related events: before:selection:created, selection:created, selection:cleared. And finally, object events include object:modified, object:selected, object:moving, object:scaling, object:rotating, and object:added.

Events like object:moving (or object:scaling) are fired continuously every time an object is moved (or scaled) even by a pixel. On the other hand, events like object:modified or selection:created are fired only at the end of the action (object modification or selection creation).

Note how events are attached right onto the canvas (canvas.on('mouse:down', ...)). As you can imagine, this means that events are all scoped to canvas instances. If you have multiple canvases on a page, you can attach different event listeners to each of them. They’re all independent and respect only events that are assigned to them.

For convenience, Fabric takes the event system even further and allows you to attach listeners directly to canvas objects. Take a look at this code:

        var rect = new fabric.Rect({ width: 100, height: 50, fill: 'green' });
	rect.on('selected', function() {
	  console.log('selected a rectangle');
	});
	var circle = new fabric.Circle({ radius: 75, fill: 'blue' });
	circle.on('selected', function() {
	  console.log('selected a circle');
	});

Here I’m attaching event listeners directly to rectangle and circle instances. Instead of object:selected, I’m using the selected event. Similarly, I could have used the modified event (object:modified when attaching to the canvas), the rotating event (object:rotating when attaching to the canvas), and so on.

Check this events demo for a more extensive exploration of Fabric’s event system.

In the next article, I’ll move on to more advanced features: groups, serialization (and deserialization) and classes.

This article was originally published at http://msdn.microsoft.com/en-us/magazine/jj856929.aspx and is reproduced here with permission.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • just me

    You have many changes from version to version.
    The latest version changed “strokestyle” to “stroke” and “setGradientFill” to “setGradient(“fill”,…)”