3
头图

This article is from the Fabricjs Chinese version . This site is a domestic version that I deployed with the permission of the community. I hope to recruit people who are also interested in fabricjs to translate and build together. There are links to PR under each article. Just use github to write and edit


image.png
Today I want to introduce to you Fabric.js — a magical library that allows you to easily manipulate canvas. Fabric not only provides a virtual canvas object, but also an svg renderer, interaction layer, and a set of very useful tools. This is A completely open source project, MIT agreement, has been maintained by many contributors for many years.

Fabric started in 2010, after experiencing the cumbersome API operations of native canvas. The original author wrote an interactive editor printio.ru — allowing users to customize the appearance. At that time, only flash apps needed this kind of interaction. Time is like an arrow. A little bit of accumulation formed the current Fabric.

Let's take a closer look!

Why choose fabric?
The current Canvas supports us to create some creative and magical graphics, but the API it provides is really low to heinous. If we just want to draw some simple graphics. But it requires a series of operations, various modifications to the central point , If you want to draw a complex figure-the operation is "more interesting".

Fabric's goal is to solve these problems.

The native canvas method only allows us to use some simple graphics operations, and then blindly touch the image on the canvas. Want to draw a rectangle? Use fillRect(left, top, width, height). Want to draw a line? Use moveTo(left, top) Combine with lineTo(x, y). This feels like drawing on a canvas with a brush. As you paint more and more, the controllability of the canvas content becomes worse.

In order to avoid this low-level operation, Fabric provides a simple and powerful object model based on it. Pay more attention to the state and rendering of the canvas, now, let's start learning to use "objects".

Let's use a simple example to draw a red rectangle to see the difference between the two. This is the implementation of the native <canvas> API


// 获取画布的引用
var canvasEl = document.getElementById('c');

// 获取2d context 对象 用来操作 (之前提到的bitmap)
var ctx = canvasEl.getContext('2d');

// 给当前上下文设置颜色
ctx.fillStyle = 'red';

// 在100, 100的位置创建一个20*20的矩形
ctx.fillRect(100, 100, 20, 20);
现在,让我们看看fabricjs怎么实现同样的效果:

// 包裹一下canvas (with id="c")
var canvas = new fabric.Canvas('c');

// 新建一个矩形对象
var rect = new fabric.Rect({
  left: 100,
  top: 100,
  fill: 'red',
  width: 20,
  height: 20
});

// 将矩形添加到canvas里
canvas.add(rect);

image.png
So far, the most different place is the size setting — the two examples are very similar. But you should also be aware of the difference between the two operating ideas. With the native method, we manipulate the context — representing the entire canvas. With Fabric, We operate on specific objects — instantiate them, modify their properties, and then add them to the canvase. These objects are the first citizens of the fabricjs world.

But drawing a red rectangle is not difficult. Let's make it interesting! For example, rotate it a little bit?

Let's try rotating 45 degrees. First, use the native <canvas> method:

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);
接下来使用fabric:

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);

image.png

what happened?

We only need to modify the object's "angle" to 45. Using the native method, things become more and more "interesting". We can't manipulate the object. Instead, in order to achieve the requirement, we rotate the entire canvas bitmap (ctx.translate, ctx.rotate). Then draw the rectangle, don't forget to position the origin at (-10, -10), so that it looks at (100, 100).

Now I am sure that you have understood the meaning of fabricjs and how much low-level code it has helped us reduce.

Let's look at another example-tracking canvas state.

Suppose at a certain point, we want to move the rectangle just now to another point on the canvas? If we can't manipulate the object, what will we do? Can we only adjust the fillRect again?

Not only that. When calling another fillRect, we drew a new rectangle on the canvas, but now there is one. Remember the wipe out function I mentioned before? In order to "move", we must first erase the previous , And then draw a new rectangle at the new position.


var canvasEl = document.getElementById('c');

...
ctx.strokRect(100, 100, 20, 20);
...

// 清除整个画布
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
ctx.fillRect(20, 50, 20, 20);
用fabric怎么实现?

var canvas = new fabric.Canvas('c');
...
canvas.add(rect);
...

rect.set({ left: 20, top: 50 });
canvas.renderAll();

image.png
Pay attention to the most important difference. After using Fabricjs, we no longer need to clear the previous canvas in order to "move". You only need to manipulate objects, simply modify their properties, and then re-render canvas to get the "latest screen".

Object
Now we have understood how to operate the fabric.Rect constructor to generate an instance. Of course, Fabric includes many basic shapes by default — primitive, triangle, ellipse, etc. These are all mounted in the fabric "variables" like fabric.Circle, fabric. Triangle, fabric.Ellipse, etc.

7 basic graphics provided by Fabric:

fabric.Circle
fabric.Ellipse ellipse
fabric.Line segment
fabric.Polygon polygon
fabric.Polyline Polyline
fabric.Rect rectangle
fabric.Triangle
Want to draw a circle? Just create a circle object and add it to the canvas. Same as other basic graphics:


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);

image.png

.. Now we have a green prototype at 100, 100, and a blue triangle at 50, 50.

Manipulate objects
Creating geometric figures — rectangles, circles, or others — is just the beginning. Later, we may need to modify these objects. Maybe some actions trigger changes, or play some kind of animation. Or want to modify some objects on mouse events Properties (color, transparency, size, position).

Fabric cares about rendering and state maintenance for us. We only need to manipulate the objects.

The previous example showed the set method and called set({ left: 20, top: 50 }) to "move" away from the previous position. In a similar way, we can modify any properties. But what properties are there?

As you might expect, it is related to position — left, top; size-related — width, height; rendering-related — fill, opacity, stroke, strokeWidth; scaling and rotation — scaleX, scaleY, angle; even flip — flipX, flipY and tilt skewX, skewY

Yes, to create a flip object in the fabric, you only need to set the flip* property to true.

You can read all the attributes through the get method, and then use the set method. Let's try to modify the attributes of the red rectangle:

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);

image.png

First, we set "fill" to "red" to make the image red. The next line sets the values of "brush width" and "brush color", giving the rectangle a light green border of 5px width. Finally, we modify "Angle" and "flipY" attributes. Note that the three lines of different setting syntax are all supported.

This example shows the versatility of the set method. You will use it often in the future, so this method supports various usage methods as much as possible.

After talking about setting properties, what about getting it? You only need to use the general get method, of course, there are various special get* to get an attribute. To get the "width" of an object, you can use get('width' ) Or getWidth(). Want to get the "scaleX" property — get('scaleX') or getScaleX(), etc. The "public" properties of the object have getWidth or getScaleX methods ("stroke", "strokeWidth", "Angle", etc.)

You may have noticed that in the previous example, there is no difference between the objects created by the initial configuration and the objects created using the set method. This is because they are exactly the same. You can use "configuration" during initialization, or after creating the object Use the set method:

var rect = new fabric.Rect({ width: 10, height: 20, fill: '#f55', opacity: 0.7 });

// 一样的

var rect = new fabric.Rect();
rect.set({ width: 10, height: 20, fill: '#f55', opacity: 0.7 });

default allocation
At this point, you may want to ask—what happens when you create an object without passing "configuration". Are there any other attributes?

Of course there is. Objects in Fabricjs always have default properties. If they are omitted when they are created, the default values will be used. Try it:

var rect = new fabric.Rect(); // No configuration is passed in

rect.get('width'); // 0
rect.get('height'); // 0

rect.get('left'); // 0
rect.get('top'); // 0

rect.get('fill'); // rgb(0,0,0)
rect.get('stroke'); // null

rect.get('opacity'); // 1
This rectangle uses the default values. Positioned at 0, 0, black, completely opaque, without borders and no dimensions (both width and height are 0). Because they are all 0, we can’t see it. Just attach a positive integer to the width and height , We can look at a black rectangle in the upper left corner of the canvas.

image.png

Hierarchy and Inheritance
Fabric objects do not exist independently. They all inherit from a source object

Most objects inherit from the root object fabric.Object. fabric.Object represents a two-dimensional, with coordinates, width and height, and a series of other graphic features. These are the object properties seen before — fill, stroke, angle, opacity , flip*, etc.

Using inheritance, we can define methods on fabric.Object and provide them to all subclasses. For example, if you want to add a custom getAngleInRadians method to all objects, you can directly define it on fabric.Object.prototype


fabric.Object.prototype.getAngleInRadians = function() {
  return this.get('angle') / 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, this method immediately takes effect on all instances.

When creating a sub-class, some properties and methods of its own are often defined on the sub-class. For example, fabric.Circle needs a “radius” property. fabric.Image — I will talk about it below — needs the getElement/setElement method to use To access/set HTML elements.
It is very common to use prototypes in advanced projects to get custom renderings and behaviors.

Canvas
Now that we have finished talking about the Fabricjs object in detail, let me turn back and talk about canvas.

In all the Fabricjs examples, does the first line you see create a canvas object? — New fabric.Canvas('...'). fabric.Canvas wraps the <canvas> element, which is responsible for all Fabric.Object objects. Pass in an id and return the fabric.Canvas instance.

We can add objects in, by reference, or delete them:


var canvas = new fabric.Canvas('c');
var rect = new fabric.Rect();

canvas.add(rect); // 添加进去

canvas.item(0); //  获取刚才添加的fabric.Rect
canvas.getObjects(); // 获取画布中所有的对象

canvas.remove(rect); // 删除fabric.Rect

So the main function of fabric.Canvas is to manage objects, and it can also write some configuration. Want to set the background of the canvas? Cut all the content? Set different width/height? Is it interactive? Including but not limited to these properties can be passed For fabric.Canvas, the same as objects, at any time:

var canvas = new fabric.Canvas('c', {
  backgroundColor: 'rgb(100,100,200)',
  selectionColor: 'blue',
  selectionLineWidth: 2
  // ...
});

// or

var canvas = new fabric.Canvas('c');
canvas.setBackgroundImage('http://...');
canvas.onFpsUpdate = function(){ /* ... */ };
// ...

Interactive function
Let's now talk about the interactive function. A unique Fabricjs function-built-in-the interactive layer above the object layer.

The existence of the object model allows programmatic access and manipulation of objects on the canvas. But for users, you need to use the mouse or fingers to operate. After you initialize the canvas through new fabric.Canvas('...'), you can choose to drag Rotate and zoom automatically, and even operate together after group selection!

image.pngimage.png

If we want to drag something on the canvas—such as a picture—we only need to create the canvas and add a picture to it. No additional operations are required.

We can pass a boolean value to Fabric's "selection" or pass a boolean value to the object's "selectable" field to control whether it is interactive.


var canvas = new fabric.Canvas('c');
...
canvas.selection = false; // 关闭群选
rect.set('selectable', false); // 单个对象不可选

If you don't want this interactive feature? You can use fabric.StaticCanvas instead of fabric.Canvas. Everything else is 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 "lightweight" version of the canvas without any event processing logic. You can still manipulate the entire object model — add objects, delete or modify them, or modify the canvas configuration — these are still available as always. Just the event system Gone.

In short, if you need a canvas that does not require interaction, the lighter StaticCanvas is enough.

picture
Speaking of pictures...

It's not interesting to play with rectangles and circles on the canvas. Let's try to play with pictures? As you think, Fabric makes this easy. Let's instantiate a fabric.Image object and add it to the canvas:

(html)

<canvas id="c"></canvas>
<img src="my_image.png" id="my-image">

(js)

var canvas = new fabric.Canvas('c');
var imgElement = document.getElementById('my-image');
var imgInstance = new fabric.Image(imgElement, {
  left: 100,
  top: 100,
  angle: 30,
  opacity: 0.85
});
canvas.add(imgInstance);

Note that we give an image element pure to the fabric.Image constructor. This creates an instance of fabric.Image. And, we immediately set the coordinates, rotation, and transparency of the image. After adding it to the canvas, you will see an image At the position of 100, 100, it is rotated by 30 degrees, and there is a slight transparency. Not bad

image.png

So, what if there is no such image element in the document, we just have a url? Then it's time for fabric.Image.fromURL to come in handy

fabric.Image.fromURL('my_image.png', function(oImg) {
canvas.add(oImg);
});
Does it look very simple and intuitive? Just call fabric.Image.fromURL, pass the url, and call the callback function after the image is loaded. The first default parameter of the callback function is the fabric.Image object. At this time, you You can modify the properties as before, and then add it to the canvas:

fabric.Image.fromURL('my_image.png', function(oImg) {
  // scale image down, and flip it, before adding it onto canvas
  oImg.scale(0.5).set('flipX', true);
  canvas.add(oImg);
});

path
We first understand simple graphics, and then pictures. Let’s take a look at more complex graphics and content

First look at a pair of powerful combinations-path and grouping.

In Fabric, a path represents a shape that can be modified, filled, and stroked. A path is composed of a bunch of commands, essentially imitating a pen from one point to another. Through "move", " Line", "curve", or "arc" commands can form magical patterns. With the help of Paths (PathGroup's), the grouping function of paths, users can have more room for imagination.

Paths in Fabric are very similar to SVG <path> elements. They use the same syntax, so they can be converted to each other. Later we will study serialization and SVG parsing more carefully. Let me remind you that you will hardly create Paths manually. Example. And you will often use Fabric's built-in SVG renderer. But in order to understand Path, let's try to create a simple one manually:


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);

image.png

We instantiated a fabric.Path object and passed it a string of path instructions. Although it looks mysterious, it is actually very easy to understand. "M" stands for the "move" command, which commands the invisible pen finger At the position of 0, 0. "L" represents "line" with a stroke of 200, 100. Then, another "L" draws a line of 170, 200. Finally, "z" commands the pen to close this line Line segment, determine the final shape. This way we get a triangle.

Obviously fabric.Path is just another object in Fabric, and we can also modify its properties. But we can modify more:


...
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);

image.png
Out of curiosity, let's try a slightly more complicated graphic. You will find that what I said before is correct, there is no way to write the path by hand.


...
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 }));

"M" still stands for "move", so the pen starts from "121.32, 0". Then "L" means this draws a straight line to "44.58, 0". The current position is still acceptable. The "C" command represents "Cubic Bézier curve". Command the pen to draw a cubic Bézier curve from the current position to "36.67, 0". The start control point is "29.5, 3.22" and the end control point is "24.31, 8.41". Then follow The last pile of Bezier curves finally formed this nice arrow.

image.png

Generally speaking, you don't use it so "rudely" directly, you may use methods like fabric.loadSVGFromString or fabric.loadSVGFromURL to load SVG files, and leave all the work to Fabric.

Speaking of the entire SVG document, the path of Fabric represents the SVG <path> element. The collection of paths that often appear in SVG documents is the concept of a group (fabric.Group instance) in Fabric. As you think, a group is just a group A collection of paths and other objects. And fabric.Group inherits from fabric.Object, so its addition and modification behavior is the same as other objects.

Just like using paths, you may not use them directly. But once you need it, you should know how it works.

postscript
We just introduced some basic things in Fabric. Now you can easily manipulate simple and complex graphics and pictures on the canvas. — Position, size, rotation, color, border, transparency.

In the next chapter of the series, we will talk about groups; animation; literary classes; SVG parsing, rendering, serialization; events; image filters, etc.

At the same time, look at examples or basic data or elsewhere, or look directly at the documentation, wiki, and source code.

Hope Fabric can bring you fun.

Read Part 2.


阿古达木
574 声望17 粉丝

牛逼的工程师就是能用简单的代码和思路写出复杂的功能