原文链接:

在本系列的第一部分中,我们只是开始熟悉 Fabric.js。我们研究了使用 Fabric 的原因,对象模型和对象层次结构,Fabric 中可用的不同种类的实体 - 简单的形状、图像和复杂的路径。我们还学习了如何对 canvas 上的 Fabric 对象执行简单的操作。

既然大多数基本知识都结束了,那么让我们开始一些有趣的事情吧!

一、Animation

没有动画工具的 canvas 库是不会受人尊重的,而且 Fabric 也不例外。由于具有如此强大的对象模型和图形功能,因此如果没有内置动画助手将是很可惜的。

还记得更改任何对象的属性有多么容易吗?我们只是调用了set方法,并传递了相应的值:

  1. rect.set("angle", 45)

好吧,为对象设置动画同样容易。每个Fabric对象都有一个 animate 方法,该方法可以动画化该对象

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

第一个参数是要设置动画的属性。第二个参数是动画的结束值。如果矩形具有-15°的角度,并且我们传递了45,则动画将从-15°变为45°。第三个参数是一个可选对象,用于指定动画的详细信息 - 持续时间、回调、缓动等。

animate 的一个便利功能是它还支持相对值。例如,如果您想为对象的left属性设置100px的动画,则可以这样做:

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

同样,可以将对象逆时针旋转5度,如下所示:

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

您可能想知道为什么我们总是在此处指定 “onChange” 回调。第三个参数不是可选的吗?是的,但是在每个动画帧上调用 canvas.renderAll 才使我们能够看到实际的动画!您会看到,当我们调用 animate 方法时,它只会按照特定算法(即缓动)随时间对属性值进行动画处理。因此,rect.animate('angle', 45) 将更改对象的角度,但在每次更改角度后都不会重新渲染 canvas 屏幕。我们显然需要重新渲染(Rerendering)才能看到实际的动画。

请记住,在 canvas 表面下方是有整个对象模型。对象具有自己的属性和关系,而 canvas 仅负责将它们的存在投射到外部世界(注:对象模型在好多领域都有应用,如React的Fiber、浏览器的DOM树等)。

每次更改后动画没有自动重新渲染画布的原因是由于性能。毕竟,我们可以在画布上有成百上千个动画对象,如果每个对象都试图重新渲染屏幕并不是很好。对于许多对象,您可以使用诸如 **requestAnimationFrame**(或其他基于计时器的)循环之类的东西连续不断地渲染画布,而无需为每个对象调用 renderAll。但是大多数时候,您可能需要显式指定canvas.renderAll作为”onChange”回调。

那么我们可以传递哪些其他选项进行动画处理呢?

  • from:允许指定可设置动画的属性的起始值(如果我们不希望使用当前值)。
  • duration:默认为500(ms)。可用于更改动画的持续时间。
  • onComplete:在动画结束时调用的回调。
  • easing:缓动功能。

所有这些选择都应该是不言自明的,除非可能是一个简单的选择。让我们仔细看看。

默认情况下,animate使用 “easeInSine” 功能进行动画处理。如果这不是您所需要的,那么 **fabric.util.ease** 下提供了许多宽松选项。例如,如果我们想以弹性的方式将对象向右移动:

  1. rect.animate("left",500,{
  2. onChange:canvas.renderAll.bind(canvas),
  3. duration:3000,
  4. easing:fabric.util.ease.easeOutBounce
  5. });

因此,这涵盖了 Fabric 的动画部分。只是给您一些可能的想法-您可以为对象的角度设置动画以使其旋转。动画左/上属性以使其移动;动画宽度/高度,使其缩小/增长;为不透明度设置动画以使其淡入/淡出;等等。

二、Image filters(图像滤镜)

在本系列的第一部分中,我们学习了如何在 Fabric 中使用图像。有 fabric.Image 构造函数,它接受图像元素。还有 fabric.Image.fromURL 方法,它可以创建URL字符串的图像实例。这些图像中的任何一个都可以像其他任何对象一样抛出并呈现在画布上。

但是,尽管处理图像非常有趣,但对它们应用图像滤镜甚至更酷

默认情况下,Fabric 提供的过滤器很少,无论是否支持 WEBGL 浏览器。它还可以轻松定义您自己的。您可能非常熟悉的一些内置滤镜 - 滤镜可去除白色背景、灰度滤镜、反转或亮度滤镜。其他一些可能不那么受欢迎 - 彩色矩阵、棕褐色或噪点。

那么我们如何将滤镜应用于 Fabric 中的图像?好吧,fabric.Image 的每个实例都具有 “filters” 属性,该属性是一个简单的过滤器数组。该阵列中的每个过滤器都是 Fabric 过滤器之一的实例。或您自己的自定义过滤器的实例。

因此,让我们创建一个灰度图像。

  1. fabric.Image.fromURL('pug.jpg', function(img) {
  2. // add filter(添加过滤器)
  3. img.filters.push(new fabric.Image.filters.Grayscale());
  4. // apply filters and re-render canvas when done(应用过滤器)
  5. img.applyFilters();
  6. // add image onto canvas (it also re-render the canvas)
  7. canvas.add(img);
  8. });

image.png

图像的棕褐色版本如何?

  1. fabric.Image.fromURL('pug.jpg', function(img) {
  2. img.filters.push(new fabric.Image.filters.Sepia());
  3. img.applyFilters();
  4. // add image onto canvas (it also re-render the canvas)
  5. canvas.add(img);
  6. });

image.png
由于 “filters” 属性是一个简单的数组,因此我们可以用通常的方式对其执行任何所需的操作 - 删除过滤器(通过pop、splice或shift),添加过滤器(通过push、splice或unshift),甚至组合多个过滤器。当我们调用 applyFilters 时,“过滤器”数组中存在的所有过滤器都会被一一应用。因此,让我们尝试创建既棕褐色又明亮的图像。

  1. fabric.Image.fromURL('pug.jpg', function(img) {
  2. img.filters.push(
  3. new fabric.Image.filters.Sepia(),
  4. new fabric.Image.filters.Brightness({ brightness: 100 })
  5. );
  6. img.applyFilters();
  7. canvas.add(img);
  8. });

请注意,我们还将 {Brightness:100} 对象传递给了 Brightness 滤镜。这是因为可以应用某些过滤器而无需任何其他配置(例如grayscale、invert、sepia),而其他过滤器可以对其行为进行更好的控制。对于亮度滤镜,它是实际的亮度级别(-1 全黑到1 全白)。对于噪声滤波器,它是噪声值(0-1000)。对于“去除颜色”滤镜,它是阈值和距离值。等等。

因此,既然您熟悉了Fabric过滤器,就该打破常规,创建自己的过滤器了!

用于创建过滤器的模板非常简单。我们需要创建一个“类”,然后定义 applyTo 方法。 (可选)我们可以将filter赋予 toJSON 方法(支持JSON序列化)和/或 initialize 方法(支持可选参数)。

  1. fabric.Image.filters.Redify = fabric.util.createClass(fabric.Image.filters.BaseFilter, {
  2. type: 'Redify',
  3. /**
  4. * Fragment source for the redify program
  5. */
  6. fragmentSource: 'precision highp float;\n' +
  7. 'uniform sampler2D uTexture;\n' +
  8. 'varying vec2 vTexCoord;\n' +
  9. 'void main() {\n' +
  10. 'vec4 color = texture2D(uTexture, vTexCoord);\n' +
  11. 'color.g = 0.0;\n' +
  12. 'color.b = 0.0;\n' +
  13. 'gl_FragColor = color;\n' +
  14. '}',
  15. applyTo2d: function(options) {
  16. var imageData = options.imageData,
  17. data = imageData.data, i, len = data.length;
  18. for (i = 0; i < len; i += 4) {
  19. data[i + 1] = 0;
  20. data[i + 2] = 0;
  21. }
  22. }
  23. });
  24. fabric.Image.filters.Redify.fromObject = fabric.Image.filters.BaseFilter.fromObject;

image.png
在不花太多时间研究此代码的情况下,主要动作是循环循环。其中我们将每个像素的绿色(data [i + 1])和蓝色(data [i + 2])分量替换为0,从本质上将它们删除。标准rgb三元组的红色部分保持不变,基本上使整个图像都涂成红色。如您所见,正在向applyTo方法传递一个选项对象,该对象包含在筛选管道阶段的图像的imageData。从那里,我们可以遍历其像素(getImageData().data),以我们想要的任何方式对其进行修改。如果浏览器是启用WEBGL的过滤器,则可以在GPU上运行。为此,您必须提供一个片段着色器,以描述对像素执行的操作。构造中定义了许多过滤器,您可以在其中看到如何编写片段或顶点着色器的示例。

三、Colors

无论您是使用十六进制,RGB还是RGBA颜色更舒适,Fabric 均可提供坚实的色彩基础,以帮助您最自然地表达自己。以下是在Fabric中定义颜色的一些方法:

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

转换也很简单。 toHex()会将颜色实例转换为十六进制表示形式。 toRgb()—变为RGB one,toRgba()—变为具有alpha通道的RGB。

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

转换不是唯一可以使用颜色进行的操作。您还可以将一种颜色与另一种颜色叠加,或将其转换为灰度版本。

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

四、Gradients

使用颜色的一种更具表现力的方式是通过渐变。渐变使我们可以将一种颜色混合到另一种颜色,从而创建一些令人惊叹的图形效果。

Fabric 支持在所有对象上设置填充或描边属性的渐变。为了为对象设置渐变,请首先创建渐变,然后将其分配给填充或描边

  1. var circle = new fabric.Circle({
  2. left: 100,
  3. top: 100,
  4. radius: 50
  5. });
  6. var gradient = new fabric.Gradient({
  7. type: 'linear',
  8. gradientUnits: 'pixels', // or 'percentage'
  9. coords: { x1: 0, y1: 0, x2: 0, y2: circle.height },
  10. colorStops:[
  11. { offset: 0, color: '#000' },
  12. { offset: 1, color: '#fff'}
  13. ]
  14. })
  15. circle.set('fill', gradient);

image.png
在上面的示例中,我们在100,100位置创建了一个半径为50px的圆。然后,我们将其填充设置为一个渐变,该渐变跨越该圆圈的整个高度,从黑色到白色。

渐变选项对象具有2个主要属性,coords和colorStops。coords 期望至少2个坐标对(x1,y1和x2,y2)将定义渐变在对象上的扩展方式,而colorStops是定义渐变颜色的数组。数组中的每种颜色都由一个offset定义,该偏移量表示其在渐变上的位置,该color定义了颜色本身,并最终定义了opacity属性。您可以定义任意多个色标,只要它们的偏移范围为0到1。 “0” 代表渐变的开始,”1” 代表渐变的结束。坐标是相对于对象左上角的,因此圆的最高点是0,最低点是 circle.height

您可以指定类型为 linearradial 以获得两种不同类型的渐变,而且 gradientUnits 默认为像素,但可以指定为“百分比”。 “百分比”将允许以对象大小的百分比指定渐变大小,“ 1”是对象大小的100%。

此设置对fabric.Text对象有用,该对象根据文本内容更改宽度或高度。

这是一个从左到右的红蓝色渐变的示例:

  1. var gradient = new fabric.Gradient({
  2. type: 'linear',
  3. gradientUnits: 'pixels', // or 'percentage'
  4. coords: { x1: 0, y1: 0, x2: circle.width, y2: 0 },
  5. colorStops:[
  6. { offset: 0, color: 'red' },
  7. { offset: 1, color: 'blue'}
  8. ]
  9. })
  10. // or in percentage
  11. var gradient = new fabric.Gradient({
  12. type: 'linear',
  13. gradientUnits: 'percentage',
  14. coords: { x1: 0, y1: 0, x2: 1, y2: 0 },
  15. colorStops:[
  16. { offset: 0, color: 'red' },
  17. { offset: 1, color: 'blue'}
  18. ]
  19. })

image.png

这是一个5级彩虹渐变,颜色的间隔甚至为20%:

  1. var gradient = new fabric.Gradient({
  2. type: 'linear',
  3. gradientUnits: 'pixels', // or 'percentage'
  4. coords: { x1: 0, y1: 0, x2: circle.width, y2: 0 },
  5. colorStops:[
  6. { offset: 0, color: 'red' },
  7. { offset: 0.2, color: 'orange' },
  8. { offset: 0.4, color: 'yellow' },
  9. { offset: 0.6, color: 'green' },
  10. { offset: 0.8, color: 'blue' },
  11. { offset: 1, color: 'purple' }
  12. ]
  13. });

image.png

五、Text

如果您不仅想在画布上显示图像和矢量形状,还想显示文本怎么办?Fabric 都为你做好了!遇到 fabric.Text 对象。

我们在Fabric中提供文本抽象有两个原因。首先,允许以面向对象的方式处理文本。原生canvas方法 - 像往常一样-只允许进行低级别的填充或笔划文本。通过实例化fabric.Text,我们可以像处理任何其他 Fabric object 一样处理文本—移动它、缩放它、更改它的属性等等。

第二个原因是要提供比 canvas 所提供的功能更丰富的功能。某些Fabric附加功能包括:

  • Multiline support 不幸的是,原生文本方法只是忽略了新行。
  • Text alignment Left, center, right. 使用多行文字时非常有用。
  • Text background 背景也遵循文本对齐。
  • Text decoration Underline, overline, strike-through.
  • Line height 使用多行文字时出错。
  • Char spacing 使文本更紧凑或间距更大。
  • Subranges 将颜色和属性应用于文本对象的子范围。
  • Multibyte 支持表情!
  • On canvas editing 使用交互式类,您可以直接在canvas上键入文本。

hello word的例子怎么样?

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

这是正确的!在画布上显示文本就像在所需位置添加 fabric.Text 实例一样简单。如您所见,唯一需要的第一个参数是实际的文本字符串,第二个参数是通常的选项对象,可以具有任何通常的left,top,fill,opacity等属性。

但是,当然,文本对象也具有其自己的独特的与文本相关的属性。让我们看看其中的一些:

5.1 fontFamily

默认情况下设置为”Times New Roman”,此属性允许我们更改用于渲染文本对象的字体系列。更改它会立即使文本以新字体呈现。

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

image.png

5.2 fontWeight

字体粗细允许使文本看起来更粗或更细。就像在CSS中一样,您可以使用关键字(“ normal”,“ bold”)或数字(100、200、400、600、800)。请注意,是否可以使用特定的粗细取决于所选字体的粗细可用性。如果您使用的是远程字体,则需要确保同时提供普通和粗体(以及任何其他必需的粗细)字体定义。

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

image.png

5.3 textDecoration

文本修饰允许在文本上添加非轮廓,上划线或删除线。这与CSS相似,但是 Fabric 更进一步,并允许将以上内容的任何组合一起使用。因此,您可以同时使用带下划线和上划线,或带下划线和删除线的文本。

  1. var underlineText = new fabric.Text("I'm an underlined text", {
  2. underline; true
  3. });
  4. var strokeThroughText = new fabric.Text("I'm a stroke-through text", {
  5. linethrough: true
  6. });
  7. var overlineText = new fabric.Text("I'm an overline text", {
  8. overline: true
  9. });

image.png

5.4 shadow

在1.3.0版之前,此属性称为”textShadow”。

文字阴影由4个成分组成:颜色,水平偏移,垂直偏移和模糊大小。如果您使用过CSS中的阴影,这可能看起来非常熟悉。通过更改这些值,可以进行很多组合。

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

image.png

5.5 fontStyle

字体样式可以是2个值之一:normal 或 italic。这类似于同名的CSS属性。

  1. var italicText = new fabric.Text("A very fancy italic text", {
  2. fontStyle: 'italic',
  3. fontFamily: 'Delicious'
  4. });
  5. var anotherItalicText = new fabric.Text("another italic text", {
  6. fontStyle: 'italic',
  7. fontFamily: 'Hoefler Text'
  8. });

image.png

5.6 stroke and strokeWidth

通过组合笔触(笔触的颜色)和strokeWidth(其宽度),可以对文本实现一些有趣的效果。

  1. var textWithStroke = new fabric.Text("Text with a stroke", {
  2. stroke: '#ff1318',
  3. strokeWidth: 1
  4. });
  5. var loremIpsumDolor = new fabric.Text("Lorem ipsum dolor", {
  6. fontFamily: 'Impact',
  7. stroke: '#c3bfbf',
  8. strokeWidth: 3
  9. });

image.png

5.7 textAlign

使用多行文本时,文本对齐很有用。对于单行文本,边框的宽度始终与该行的宽度完全匹配,因此没有任何对齐的地方。

允许的值为“ left”,“ center”和“ right”。

  1. var text = 'this is\na multiline\ntext\naligned right!';
  2. var alignedRightText = new fabric.Text(text, {
  3. textAlign: 'right'
  4. });

image.png

5.8 lineHeight

CSS 领域可能熟悉的另一个属性是 lineHeight。它允许我们更改多行文本中文本行之间的垂直间距。在下面的示例中,第一个文本块的lineHeight为3,第二个文本为— 1。

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

image.png

5.9 textBackgroundColor

最后,textBackgroundColor 可以为文本提供背景。请注意,背景仅填充文本字符占用的空间,而不填充整个边界框。这意味着文本对齐方式会更改文本背景的呈现方式。线高也是如此,因为背景尊重线之间的垂直空间,由lineHeight 创建。

  1. var text = 'this is\na multiline\ntext\nwith\ncustom lineheight\n&background';
  2. var textWithBackground = new fabric.Text(text, {
  3. textBackgroundColor: 'rgb(0,200,0)'
  4. });

image.png

六、Events

事件驱动的体系结构是框架内强大功能和灵活性的基础。 Fabric 也不例外,它提供了一个广泛的事件系统,从低级的”鼠标”事件到高级的对象事件。

这些事件使我们能够利用 cavas 上发生的各种动作的不同时刻。是否想知道何时按下鼠标?只需观察”mouse:down” 事件即可。将对象添加到画布的时怎么样? 可使用 “object:added”。那么当整个 canvas 重新渲染时又如何呢?只需使用”after:render”。

事件 API 非常简单,类似于jQuery,Underscore.js或其他流行的JS库。有on方法可以初始化事件监听器,而 off 方法可以将其删除。

让我们看一个实际的例子:

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

我们将事件”mouse:down”事件侦听器添加到 canvas 上,并为其提供事件处理程序,该事件处理程序将记录事件起源的坐标。换句话说,它将记录鼠标在画布上的确切位置。事件处理程序接收一个options对象,该对象具有2个属性:e-原始事件,target-画布上单击的对象(如果有)。该事件始终存在,但仅当您确实单击画布上的某个对象时,目标才存在。目标也仅传递给有意义的事件处理程序。例如,对于”mouse:down” 而不是”after:render”(表示重新绘制了整个画布)。

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

上面的示例将记录“单击了一个对象!”如果单击一个对象。它还将显示单击的对象的类型。

那么Fabric中还有哪些其他事件可用?好吧,在鼠标级别的菜单中有“ mouse:down”,“ mouse:move”和“ mouse:up”。从通用的开始,有“ after:render”。然后是与选择相关的事件:“before:selection:cleared”,“selection:created”,“selection:cleared”。最后,对象是:“object:modified”,“object:selected”,“object:moving”,“object:scaling”,“object:rotating”,“object:added”和“object:removed”

请注意,每次移动(或缩放)一个对象(甚至移动一个像素)时,都会连续触发”object:moving”(或“ object:scaling”)之类的事件。另一方面,仅在操作(对象修改或选择创建)结束时才触发“ object:modified”或“ selection:created”之类的事件。

注意我们是如何将事件直接附加到canvas上(canvas.on('mouse:down',...))。可以想象,这意味着事件都限于canvas实例。如果页面上有多个canvas,则可以将不同的事件侦听器附加到每个canvas。它们都是独立的,并且仅尊重分配给他们的事件。

为了方便起见,Fabric 进一步扩展了事件系统,并允许您将侦听器直接附加到canvas对象。让我们来看看:

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

我们将事件侦听器直接附加到矩形和圆形实例。代替“ object:selected”,我们使用“ selected”事件。同样,我们可以使用“ modified”事件(附加到canvas上时为“ object:modified”),“ rotating”事件(附加到画布上时为“ object:rotating”),依此类推。

查看此 events demo ,以对Fabric的事件系统进行更广泛的探索。