在本系列的第一篇文章中 ,我研究了使用Fabric.js的原因,对象模型和对象层次结构以及Fabric中可用的各种实体(简单形状,图像和复杂路径)。 我还描述了如何对画布上的Fabric对象执行简单的操作。 既然大部分基本知识都已不复存在,那么让我们开始讨论有趣的东西。
动画
没有受人尊敬的画布库没有动画工具,Fabric也不例外。 鉴于Fabric强大的对象模型和图形功能,如果没有内置动画助手,这将是一个耻辱。
还记得更改任何对象的属性有多么容易吗? 您只需调用set方法,并传递相应的值即可:
rect.set('angle', 45);
为对象设置动画同样容易。 每个Fabric对象都有一个动画方法,可以使该对象动画化。
rect.animate('angle', 45, { onChange: canvas.renderAll.bind(canvas) });
第一个参数是要设置动画的属性,第二个参数是动画的结束值。 如果矩形的角度为-15°,并且在第二个参数中传递了45,则该矩形的动画范围为-15°至45°。 第三个参数是一个可选对象,用于指定动画的详细信息,例如持续时间,回调,缓动等。 我将在短期内展示这些示例。
动画方法的一个便利功能是它支持相对值。 例如,如果要为对象的left属性设置100px的动画,则可以这样进行:
rect.animate('left', '+100', { onChange: canvas.renderAll.bind(canvas) });
同样,可以将对象逆时针旋转5度,如下所示:
rect.animate('angle', '-5', { onChange: canvas.renderAll.bind(canvas) });
您可能想知道为什么我总是在这里指定onChange
回调。 如前所述,第三个参数是可选的,但是在每个动画帧上调用canvas.renderAll
可以使您看到实际的动画。 当您调用animate方法时,它仅根据特定算法(例如easing
)随时间对属性值进行动画处理。 因此, rect.animate('angle', 45)
更改对象的角度,但是在每次更改角度后都不会重新渲染画布。 而且,显然,您需要重新渲染才能观看动画。
请记住,在画布表面下方有一个完整的对象模型。 对象具有自己的属性和关系,而画布仅负责将对象的存在投影到外部世界。
每次更改后,动画不会自动重新渲染画布的原因是性能。 毕竟,您可以在画布上有成百上千个动画对象,如果每个对象都试图重新渲染屏幕,那将是不明智的。 大多数时候,您可能需要显式指定canvas.renderAll
作为onChange
回调。
可以传递动画的其他选项如下:
-
from
允许您指定要设置动画的属性的起始值(如果您不想使用当前值)。 -
duration
默认为500毫秒。 此选项可用于更改动画的持续时间。 -
onComplete
在动画结束时调用的回调。 -
easing
缓动功能。
所有这些选择应该是不言自明的,除非是easing
。 让我们仔细看看。
默认情况下,动画使用线性函数进行动画处理。 如果这不是您所需要的,那么fabric.util.ease
提供了fabric.util.ease
缓动选项。 例如,如果要以弹性方式将对象向右移动,请执行以下操作:
rect.animate('left', 500, { onChange: canvas.renderAll.bind(canvas), duration: 1000, easing: fabric.util.ease.easeOutBounce });
请注意, fabric.util.ease.easeOutBounce
是一个宽松的选项。 其他值得注意的选项包括easeInCubic
, easeOutCubic
, easeInElastic
, easeOutElastic
, easeInBounce
和easeOutExpo
。
只是为了让您了解Fabric中的动画可能会发生什么,您可以为对象的角度设置动画以使其旋转。 为左或顶部属性设置动画以使其移动; 动画其宽度和高度,使其收缩和增长; 为不透明度设置动画以使其淡入和淡出; 等等。
图像滤镜
在本系列的第一篇文章中,您了解了如何在Fabric中使用图像。 有fabric.Image
构造函数,它接受一个图像元素。 还有fabric.Image.fromURL
方法,可以从URL字符串创建图像实例。 这些图像中的任何一个都可以像其他任何对象一样抛出并呈现在画布上。
但是,与处理图像一样有趣,将图像滤镜应用于图像甚至更酷。 Fabric默认提供一些过滤器(您可以在此处查看它们),并使定义自己的过滤器变得容易。 您可能已经熟悉的一些内置滤镜是去除白色背景的滤镜,灰度滤镜或反转或亮度滤镜。 其他人可能不太熟悉,例如渐变透明度,棕褐色或噪点。
fabric.Image
每个实例都有一个滤镜属性,它是一个简单的滤镜数组。 该数组中的每个过滤器都是Fabric过滤器之一的实例或自定义过滤器的实例。
这是用于创建灰度图像的代码。 图1显示了结果。
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); });
图1.应用灰度图像过滤器
这是创建图像的棕褐色版本的方法,这将产生图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); });
图2.应用棕褐色图像过滤器
因为filters属性是一个简单的数组,所以您可以用通常的方式执行它想要的任何操作-删除过滤器(通过pop,splice或shift),添加过滤器(通过push,splice,unshift),甚至结合多个过滤器。 当您调用applyFilters
时,filters数组中存在的所有过滤器都会被一一应用。
这是您创建既棕褐色又明亮的图像的方法。 图3显示了结果。
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); });
图3.结合棕褐色和明亮图像滤镜
注意,我还将{ brightness: 100 }
对象传递给了亮度过滤器。 这是因为某些过滤器无需任何其他配置即可应用(例如,灰度,反转,棕褐色),而其他过滤器则可以对其行为进行更好的控制。 对于亮度滤镜,它是实际的亮度级别(0–255)
。 对于噪声滤波器,它是噪声值(0–1000)
。 对于移除白色滤镜,它是阈值和距离值。 等等。
现在您已经熟悉了Fabric过滤器,是时候打破常规,创建自己的过滤器了。 创建过滤器的模板非常简单。 您需要创建一个类,然后定义一个applyTo
方法。 (可选)您可以为过滤器提供toJSON
方法(支持JSON序列化)或initialize
方法(支持可选参数)。 下面是代码示例,结果如图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); };
图4.应用自定义图像过滤器
无需过多研究此代码,主要动作是在循环中发生的,在该循环中,我将每个像素的绿色(data[i+1])
和蓝色(data[i+2])
分量替换为0,实际上将其删除了。 标准RGB值的红色部分保持不变,基本上将整个图像涂成红色。 如您所见, applyTo
方法被传递到表示整个图像的主canvas元素。 从那里,您可以遍历其像素(getImageData().data)
,以所需的任何方式对其进行修改。
色彩
无论您是使用十六进制,RGB还是RGBA颜色更舒适,Fabric都能提供坚实的色彩基础,帮助您最自然地表达自己。 以下是在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)');
转换也很简单。 所述toHex()
方法转换颜色实例十六进制表示, toRgb()
到RGB色,和toRgba()
到RGB的alpha通道。
new fabric.Color('#f55').toRgb(); // "rgb(255,85,85)" new fabric.Color('rgb(100,100,100)').toHex(); // "646464" new fabric.Color('fff').toHex(); // "FFFFFF"
转换并不是您可以采用颜色的唯一步骤。 您还可以将一种颜色与另一种颜色叠加或将其转换为灰度版本。
var redish = new fabric.Color('#f55'); var greenish = new fabric.Color('#5f5'); redish.overlayWith(greenish).toHex(); // "AAAA55" redish.toGrayscale().toHex(); // "A1A1A1"
渐变色
使用颜色的一种更具表现力的方法是通过渐变。 渐变允许您将一种颜色与另一种颜色混合,从而创建一些令人惊叹的图形效果。
Fabric通过setGradientFill
方法支持渐变,该方法在所有对象上定义。 调用setGradientFill
就像设置对象的填充值一样,只不过您用渐变而不是单色填充对象。 下面是一些示例代码,其视觉效果如图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' } });
图5.将渐变填充应用于对象
在此示例中,我在位置100,100,
处创建了一个半径为50px的圆。 然后,将其填充设置为从白色到黑色的渐变,该渐变跨越该圆圈的整个高度。
传递给方法的参数是一个options对象,它需要两个坐标对( x1, y1
和x2, y2
),以及一个colorStops
对象。 坐标指定渐变的开始位置和结束位置。 colorStops
对象指定构成渐变的颜色。 您可以定义任意多个色标,只要它们的范围是0到1(例如0、0.1、0.3、0.5、0.75、1等)即可。 零(0)表示渐变的开始,而1表示渐变的结束。
这是创建从左到右的红蓝色渐变的代码。 图6显示了结果。
circle.setGradientFill({ x1: 0, y1: circle.height / 2, x2: circle.width, y2: circle.height / 2, colorStops: { 0: "red", 1: "blue" } });
图6.使用色标创建的渐变
下面的代码显示了一个五级彩虹渐变,颜色间隔甚至达到20%。 图7显示了结果。
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" } });
图7.彩虹渐变
您可以提出哪些很酷的版本?
文本
如果您不仅要在画布上显示图像和矢量形状,还要显示文本怎么办? Fabric通过fabric.Text
对象覆盖fabric.Text
您。
在Fabric中提供文本抽象有两个原因。 首先,它允许您以面向对象的方式处理文本。 像往常一样,本机画布方法仅允许您在很低的水平上填充或描边文本。 通过实例化fabric.Text
实例,您可以像处理任何其他Fabric对象一样使用文本-对其进行移动,缩放,更改其属性,等等。
第二个原因是要提供比canvas元素提供的功能丰富得多的功能。 某些Fabric附加功能包括:
多行支持不幸的是,本机文本方法只是忽略换行。
文本对齐左,中和右。 在处理多行文本时很有用。
文本背景背景也尊重文本对齐方式。
文字修饰下划线,上划线和删除线。
行高在处理多行文本时很有用。
这是一个“ hello world”示例:
var text = new fabric.Text('hello world', { left: 100, top: 100 }); canvas.add(text); });
那就对了! 在画布上显示文本就像在指定位置添加fabric.Text
实例一样简单。 如您所见,唯一需要的参数是实际的文本字符串。 第二个参数是常规选项对象,该对象可以具有任何常规属性,例如left,top,fill,opacity等。
但是,当然,文本对象也具有自己的文本相关属性。 让我们看看其中的一些。
字体系列
默认设置为Times New Roman, fontFamily
属性允许您更改用于呈现文本对象的字体系列。 更改属性会立即以新字体呈现文本。 图8显示了使用以下代码创建的效果。
var comicSansText = new fabric.Text("I'm in Comic Sans", { fontFamily: 'Comic Sans' });
图8.对fontFamily属性的更改
字体大小
字体大小控制渲染文本的大小。 请注意,与Fabric中的其他对象不同,您不能直接更改文本对象的width和height属性。 相反,您需要更改fontSize
值以使文本对象更大, 如图9所示 。 (或者,或者您可以使用scaleX/scaleY
属性。)
var text40 = new fabric.Text("I'm at fontSize 40", { fontSize: 40 }); var text20 = new fabric.Text("I'm at fontSize 20", { fontSize: 20 });
图9.控制字体大小
fontWeight
字体粗细可以使文本看起来更粗或更细。 就像在CSS中一样,您可以使用关键字(例如,普通或粗体-示例,请参见图10 )或数字(100、200、400、600、800)。 是否可以使用某些粗细取决于所选字体的粗细可用性。 如果您使用的是远程字体,则需要确保同时提供普通和粗体(以及任何其他必需的粗体)字体定义。
var normalText = new fabric.Text("I'm a normal text", { fontWeight: 'normal' }); var boldText = new fabric.Text("I'm at bold text", { fontWeight: 'bold' });
图10.字体粗细可以由关键字或数值控制
textDecoration
您可以使用文本修饰在文本上添加下划线,上划线或删除线。 同样,这与CSS类似,但是Fabric略有扩展,可以让您将这些装饰的任何组合一起使用。 因此,您可以使用带下划线和上划线,带删除线下划线的文本,等等, 如图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' });
图11.文本修饰示例
textShadow
文本阴影由四个部分组成:颜色,水平偏移,垂直偏移和模糊大小。 如果您使用CSS中的阴影,这些效果可能会非常熟悉。 通过更改这些值,可以进行很多组合(参见图12 )。
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' });
图12.文本阴影示例
字体样式
字体样式可以是两个值之一:normal或italic。 这类似于同名的CSS属性。 以下代码显示了一些使用fontStyle
示例, 图13显示了结果。
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' });
图13.斜体字体样式的示例
strokeStyle和strokeWidth
通过组合strokeStyle
(笔触的颜色)和strokeWidth
(笔划的宽度),可以实现一些有趣的文本效果, 如图14所示。 这是几个代码示例:
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 });
图14.使用strokeStyle和strokeWidth的文本效果
textAlign
使用多行文本对象时,文本对齐很有用。 对于单行文本对象,边界框的宽度始终与该行的宽度匹配,因此没有要对齐的内容。
textAlign
允许值为left,center和right。 图15显示了右对齐的文本。
var text = 'this isna multilinentextnaligned right!'; var alignedRightText = new fabric.Text(text, { textAlign: 'right' });
图15.右对齐文本
lineHeight
CSS可能熟悉的另一个属性是lineHeight
。 它允许您更改多行文本中文本行之间的垂直间距。 在下面的示例中,第一行文本的lineHeight
设置为3,第二行的lineHeight
设置为1。您看到的结果如图16所示。
var lineHeight3 = new fabric.Text('Lorem ipsum ...', { lineHeight: 3 }); var lineHeight1 = new fabric.Text('Lorem ipsum ...', { lineHeight: 1 });
图16.行高示例
背景颜色
最后, backgroundColor
是允许您为文本提供背景的内容。 请注意,背景仅填充文本字符占用的空间,而不填充整个边界框, 如图17所示 。 这意味着文本对齐方式会更改文本背景的呈现方式,行高也会更改,因为背景会尊重lineHeight
创建的行之间的垂直间距。
var text = 'this isna multilinentextnwithncustom lineheightn&background'; var textWithBackground = new fabric.Text(text, { backgroundColor: 'rgb(0,200,0)' });
图17.文本背景效果
大事记
事件驱动的体系结构是框架内强大功能和灵活性的基础。 Fabric也不例外,它提供了一个广泛的事件系统,从低级鼠标事件到高级对象事件。
这些事件使您可以利用画布上发生的各种动作的不同时刻。 您想知道何时按下鼠标吗? 只需观察mouse:down
事件即可。 如何将对象添加到画布? 在这种情况下, object:added
就在那里。 整个画布重新渲染怎么办? 只需使用after:render
。
事件API非常简单,类似于jQuery,Underscore.js或其他流行的JS库。 有一个on
方法可以初始化事件监听器,而有off
方法可以将其删除。
这是一个例子:
var canvas = new fabric.Canvas('...'); canvas.on('mouse:down', function(options) { console.log(options.e.clientX, options.e.clientY); });
在这段代码中,我将mouse:down
事件侦听器添加到画布上,并为其提供了一个事件处理程序,该处理程序将记录事件起源的坐标。 换句话说,处理程序将记录鼠标在画布上的确切位置。 事件处理程序接收一个options对象,该对象具有两个属性: e
是原始事件,而target
是画布上单击的对象(如果有)。 该事件始终存在,但是仅当用户确实单击画布上的对象时,目标才存在。 另外,仅在有意义的情况下将目标传递给事件处理程序,例如,对于mouse:down
而不是after:render
(表示整个画布已重绘)。
canvas.on('mouse:down', function(options) { if (options.target) { console.log('an object was clicked! ', options.target.type); } });
此示例将记录“单击了一个对象!” 如果单击一个对象。 它还将添加单击的对象的类型。
Fabric中可用的其他一些鼠标级别的事件是mouse:move
和mouse:up
。 通用事件包括after:render
,还有与选择相关的事件: before:selection:created
, selection:created
, selection:cleared
。 最后,对象事件包括object:modified
, object:selected
, object:moving
, object:scaling
, object:rotating
和object:added
。
每当对象移动(或缩放)一个像素时,都会连续触发诸如object:moving
(或object:scaling
)之类的事件。 另一方面,诸如object:modified
或selection:created
类的事件仅在操作结束时触发(对象修改或选择创建)。
注意事件如何直接附加到画布上( canvas.on('mouse:down', ...)
)。 可以想象,这意味着事件都限于画布实例。 如果页面上有多个画布,则可以将不同的事件侦听器附加到每个画布上。 它们都是独立的,并且仅尊重分配给它们的事件。
为了方便起见,Fabric进一步扩展了事件系统,并允许您将侦听器直接附加到画布对象。 看一下这段代码:
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'); });
在这里,我将事件侦听器直接附加到矩形和圆形实例。 而不是object:selected
,我正在使用selected事件。 同样,我可以使用修改事件(附加到画布上时为object:modified
),旋转事件(object:rotating
附加到画布上时为(object:rotating
)等。
查看此事件演示 ,以对Fabric的事件系统进行更广泛的探索。
在下一篇文章中,我将继续介绍更高级的功能:组,序列化(和反序列化)和类。
本文最初发布在http://msdn.microsoft.com/zh-cn/magazine/jj856929.aspx,并经许可在此处复制。