fabric.js 中文教程第二部分

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

既然基础知识都差不多了,让我们开始学习一些有趣的东西吧!

动画(Animation)

一个强大的Canvas库当然离不开动画功能,Fabric也不例外。既然有这样一个强大的对象模型和图形功能,如果没有内置动画功能,那就太遗憾了。

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

rect.set('angle', 45);

嗯,为对象设置动画同样简单。每个Fabric对象都有一个animate方法,该方法可以为对象设置动画。

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

第一个参数是动画的属性,第二个参数是动画的最终位置,如果矩形为-15°角,我们传递45,这个矩形将从-15°到45°进行动画。第三个参数是一个可选的对象,指定动画的细节:持续时间,回调,动效等。

animate的一个方便之处在于它还支持相对值。例如,如果要将对象向左移动100px,则可以这样做:

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

同样的,逆时针旋转5度,可以像这样完成:

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

你可能会想知道为什么我们总是在那里指定“onChange”回调。不是说第三个参数是可选的吗?是为了每个动画帧的渲染都能让我们看到实际的动画效果!您可以看到,当我们调用animate方法时,它只会随着时间的推移赋予属性值,遵循特定的算法(例如动效)。所以rect.animate('angle',45)会改变对象的角度,但不会在每次角度变化后重新渲染画布屏幕。我们显然需要这种重新渲染才能看到实际的动画。

记住,在画布的下面有整个对象模型。对象有自己的属性和关系,而canvas只负责将它们的存在投射到外部世界。

每次更改之后,animate不会自动重新绘制画布的原因是由于性能。毕竟,我们可以在画布上有数以千计的动画对象,如果每个对象都尝试重新渲染屏幕,这是非常不妥的。在许多对象的情况下,您可以使用像requestAnimationFrame(或其他基于定时器的)循环来独立地渲染画布,而不需要为每个对象调用renderAll。但大多数情况下,您可能需要将canvas.renderAll显式指定为“onChange”的回调。

那么我们可以通过哪些其他选项来动画?

  • from 允许指定动画属性的起始值(如果我们不希望使用当前值)。
  • duration 默认为500ms。可以用来改变动画的持续时间。
  • onComplete 动画结束之后的回调。
  • easing 动效函数。

除了easing,所有这些选择都应该是不言而喻的。我们来看看吧。

默认情况下,animate使用“easeInSine”动效执行。如果这不是你需要的,那么在fabric.util.ease下有一大堆动效的选项。例如,如果我们想以一种有弹性的方式向右移动对象:

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

注意使用了fabric.util.ease.easeOutBounce作为easing的值,其他值得注意的还包括easeInCubiceaseOutCubiceaseInElasticeaseOutElasticeaseInBounceeaseOutExpo

所以这几乎涵盖了Fabric的动画部分。只是给你一些可能的想法——你可以设置对象角度的动画,使其旋转;设置left/top属性的动画以使其移动;设置width/height动画以使其收缩/增长;设置opacity 动画以使其淡入/淡出;等等

fabric.runningAnimations

如果需要访问当前由fabric运行的动画,请使用fabric.runningAnimations。它是一个对象数组,每个对象都是动画上下文对象。
便捷的方法:

  • fabric.runningAnimations.findAnimation(signature) 返回匹配signature的动画,该signaturefabric.util.animate返回的中止函数。
  • fabric.runningAnimations.findAnimationIndex(signature)findAnimation相同,返回索引。
  • fabric.runningAnimations.findAnimationsByTarget(target) 返回指定target属性的所有动画。
  • fabric.runningAnimations.cancelAll() 取消所有正在运行的动画。
  • fabric.runningAnimations.cancelByTarget(target) 取消所有指定target的动画。
  • object.dispose() 取消由对象(object.animate(…))创建的所有动画。如果您想使用fabric.util.animate添加动画,而不是object.animate(…),可以通过传递target属性将它们附加到对象上。这样,一旦对象被释放,动画将取消。
let cancel = fabric.util.animate({...});
let i = fabric.runningAnimations.findAnimationIndex(cancel);
 let context = fabric.runningAnimations.findAnimation(cancel);
 let cancelled = fabric.runningAnimations.cancelAll();
 
 //  the following statements are true
 cancelled[i] === context;
 cancelled[i].cancel === cancel;
 fabric.runningAnimations.length === 0;

图像滤镜(Image filters)

在本系列的第一部分中,我们学习了如何在Fabric中处理图像。这是fabric.Image构造函数,它接受图像元素。还有fabric.Image.fromURL方法,它可以通过URL字符串来创建图像实例。这些图像可以像任何其他对象一样在画布上渲染。

但是,像图像这种有趣的东西,对他们应用图像滤镜更加酷!

Fabric在默认情况下提供了很少的滤镜,对于支持或不支持WEBGL的浏览器都可以使用,也很容易去定义。一些你可能非常熟悉的内置的滤镜:去除白色背景、灰度滤镜、反色或亮度调色。其他的则可能不太受欢迎: colormatrix,sepia,noise。

那么我们怎么在Fabric中使用滤镜呢?fabric.Image的每个实例都有“filters”属性,它是一个简单的滤镜数组。该数组中的每个滤镜都是一个Fabric滤镜的一个实例,或您自己的自定义滤镜的一个实例。

让我们创建一个灰度(Grayscale)图像:

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();
  // add image onto canvas (it also re-render the canvas)
  canvas.add(img);
});

灰度
图像的色偏(Sepia)版本怎么样?

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

sepia
由于“filters”属性是一个数组,我们可以用数组方法执行任何所需的操作:移除滤镜(pop,splice,shift),添加滤镜(push,unshift,splice),甚至可以组合多个滤镜。当我们调用applyFilters时,“filters”数组中存在的任何滤镜将逐个应用,所以让我们尝试创建一个既色偏又明亮(Brightness)的图像。

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

Brightness
请注意,我们还将{brightness:100}对象传递给Brightness滤镜。这是因为有些滤镜不可配置,例如,灰度,反转,棕褐色(e.g. grayscale, invert, sepia),而其他过滤器可以配置。对于Brightness滤镜,能够配置亮度级别(-1 - 1 即: 全黑色 - 全白色)。对于噪声(noise)滤镜,能配置的是噪声值(0-1000)。对于“删除颜色”滤镜,能配置的是阈值和距离值。等等。

那么现在,您已经熟悉了Fabric滤镜,现在是时候开箱并创建自己的滤镜了!

创建过滤器的模板非常简单。我们需要创建一个“class”,然后定义applyTo方法。可选地,我们可以给这个过滤器一个toJSON方法(支持JSON序列化),还可以给个initialize方法(支持可选参数)。

fabric.Image.filters.Redify = fabric.util.createClass(fabric.Image.filters.BaseFilter, {

  type: 'Redify',

  /**
   * Fragment source for the redify program
   */
  fragmentSource: 'precision highp float;\n' +
    'uniform sampler2D uTexture;\n' +
    'varying vec2 vTexCoord;\n' +
    'void main() {\n' +
      'vec4 color = texture2D(uTexture, vTexCoord);\n' +
      'color.g = 0.0;\n' +
      'color.b = 0.0;\n' +
      'gl_FragColor = color;\n' +
    '}',

  applyTo2d: function(options) {
    var imageData = options.imageData,
        data = imageData.data, i, len = data.length;

    for (i = 0; i < len; i += 4) {
      data[i + 1] = 0;
      data[i + 2] = 0;
    }

  }
});

fabric.Image.filters.Redify.fromObject = fabric.Image.filters.BaseFilter.fromObject;

自定义
深入研究这段代码,主要的操作是在循环中进行。我们把绿色(data[i+1])和蓝色(data[i+2])替换为0,本质上是移除它们。标准rgb三元色的红色组件保持不变,基本上使整个图像变成红色。如您所见,applyTo方法传递一个选项对象,该对象包含滤镜管道的该阶段的图像的数据imageData。从这里,我们可以遍历其像素(getImageData().data),以任何我们想要的方式修改它们。如果要浏览器WEBGL启用的滤镜能够在GPU上运行,要做到这一点,你必须提供一个片段着色器来描述在像素上执行的操作。在Fabric中定义了许多滤镜,您可以在其中看到如何编写片段或着色器的示例。

颜色(Colors)

无论您是否使用十六进制,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()转换为带Alpha通道的RGB。

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"

渐变(Gradients)

更加表现出色的方式是通过渐变。渐变让我们将一种颜色混合到另一种颜色中,创造出一些令人惊叹的图形效果。

Fabric支持在所有对象上设置填充或笔划属性的渐变。要为对象设置渐变,首先创建渐变,然后将其指定为填充或笔划。

var circle = new fabric.Circle({
  left: 100,
  top: 100,
  radius: 50
});

var gradient = new fabric.Gradient({
  type: 'linear',
  gradientUnits: 'pixels', // or 'percentage'
  coords: { x1: 0, y1: 0, x2: 0, y2: circle.height },
  colorStops:[
    { offset: 0, color: '#000' },
    { offset: 1, color: '#fff'}
  ]
})

circle.set('fill', gradient);

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

渐变选项对象有两个主要属性,坐标和colorStops。坐标需要至少2个坐标对(x1,y1和x2,y2),它们将定义渐变在对象上的开始和结束的位置,而colorStops是定义渐变颜色的数组。数组中的每种颜色都由表示其在渐变上位置的偏移量、定义颜色本身的颜色以及最终的不透明度属性定义。您可以根据需要定义任意多个颜色停止符,只要它们的偏移范围从“0”到“1”,0表示渐变的开始,1表示渐变的结束。
坐标相对于对象的左上角,因此圆的最高点为0,最低点为circle.height
您可以指定类型linearradial以获得2种不同类型的渐变,也可以指定gradientUnits默认为像素,但可以指定为percentagepercentage将允许以对象大小的百分比指定渐变大小,其中“1”是对象大小的100%。
此设置对于根据文本内容更改宽度或高度的织物文本对象很有用。

下面是从左到右的红蓝渐变示例:

  var gradient = new fabric.Gradient({
    type: 'linear',
    gradientUnits: 'pixels', // or 'percentage'
    coords: { x1: 0, y1: 0, x2: circle.width, y2: 0 },
    colorStops:[
      { offset: 0, color: 'red' },
      { offset: 1, color: 'blue'}
    ]
  })
 // or in percentage
  var gradient = new fabric.Gradient({
    type: 'linear',
    gradientUnits: 'percentage',
    coords: { x1: 0, y1: 0, x2: 1, y2: 0 },
    colorStops:[
      { offset: 0, color: 'red' },
      { offset: 1, color: 'blue'}
    ]
  })

渐变02
这里有一个5站彩虹渐变,颜色间隔甚至为20%:

var gradient = new fabric.Gradient({
  type: 'linear',
  gradientUnits: 'pixels', // or 'percentage'
  coords: { x1: 0, y1: 0, x2: circle.width, y2: 0 },
  colorStops:[
    { offset: 0, color: 'red' },
    { offset: 0.2, color: 'orange' },
    { offset: 0.4, color: 'yellow' },
    { offset: 0.6, color: 'green' },
    { offset: 0.8, color: 'blue' },
    { offset: 1, color: 'purple' }
  ]
});

渐变03

文本(Text)

如果你想不仅在画布上显示图像和矢量形状,还要显示文本,该怎么办?Fabric也为您准备好了!认识fabric.Text对象。

我们提供文本的原因有两个,首先是允许以面向对象的方式处理文本。原生canvas方法,只允许在非常低的级别上填充或描边文本。通过实例化fabric.Text实例,我们可以使用文本,就像我们将使用任何其他Fabric对象:移动它,缩放它,更改其属性等。

第二个原因是提供比canvas给我们更丰富的功能,包括:

  • Multiline support支持多行 不幸的是,原生文本方法忽略了新建一行。
  • Text alignment文本对齐 左,中,右。使用多行文本时很有用。
  • Text background文本背景 背景也支持文本对齐。
  • Text decoration文字装饰 下划线,上划线,贯穿线。
  • Line height行高 在使用多行文本时有用。
  • Char spacing字符间距 使文本更紧凑或更间隔。
  • Subranges子范围 将颜色和属性应用到文本对象的子对象中。
  • Multibyte多字节 支持表情符号。
  • On canvas editing交互式画布编辑 可以直接在画布上键入文本。
    做一个hello world的例子:
var text = new fabric.Text('hello world', { left: 100, top: 100 });
canvas.add(text);

那就对了!在画布上显示文本就像在所需位置添加fabric.Text实例一样简单。您可以看到,唯一必需的第一个参数是实际的文本字符串。第二个参数是通常的选项对象,它可以具有通常的left,top,填fill,opacity等属性。

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

字体(fontFamily)

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

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

字体

字体大小(fontSize)

字体大小控制渲染文本的大小。请注意,与Fabric中的其他对象不同,您不能直接更改文本的width / height属性。相反,您需要更改“fontSize”值才能使文本对象更大或更小。或者,您可以随时使用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
});

字体大小

字体粗细(fontWeight)

fontWeight允许使文本更粗或更细。就像CSS一样,您可以使用关键字(“normal”,“bold”)或数字(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'
});

fontWeight

文本修饰(textDecoration)

文本修饰允许添加下划线,上划线或贯穿线到文本。这与CSS类似,但是Fabric进一步,并允许使用上述的任何组合。所以你可以有一个文本,既有下划线又有上划线,还有贯穿线。等等。

var underlineText = new fabric.Text("I'm an underlined text", {
  underline; true
});
var strokeThroughText = new fabric.Text("I'm a stroke-through text", {
  linethrough: true
});
var overlineText = new fabric.Text("I'm an overline text", {
  overline: true
});

textDecoration

阴影(shadow)

此属性在版本1.3.0之前被称为“textShadow”

文本阴影由4个组件组成:颜色,水平偏移,垂直偏移和模糊大小。如果您在CSS中使用阴影,这可能看起来很熟悉。通过更改这些值可以实现许多组合。

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

shadow

字体样式(fontStyle)

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

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

fontStyle

描边的颜色和宽度(stroke and strokeWidth)

通过组合stroke和strokeWidth,你可以在你的文字上获得一些有趣的效果。这里有几个例子:

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

stroke

文本对齐(textAlign)

使用多行文本时,文本对齐很有用。使用单行文本,边界框的宽度总是与该行的宽度完全匹配,因此没有任何对齐方式。

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

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

textAlign

行高(lineHeight)

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

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

lineHeight

文本背景颜色(textBackgroundColor)

最后,textBackgroundColor允许给文本一个背景。请注意,背景仅填充文本字符占用的空间,而不是整个边框。这意味着文本对齐改变了文本背景渲染的方式。一行的高度也是如此,因为背景是由lineHeight创建的线之间的垂直空间。

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

背景色

事件(Events)

事件驱动架构是框架内一些惊人的功能和灵活性的基础。Fabric也不例外,并提供广泛的事件系统,从低级“鼠标”事件开始到高级对象。

这些事件使我们能够深入了解画布上发生的各种动作的不同时刻。想知道鼠标是什么时候按下的吗?只需观察“鼠标:向下”事件。什么时候将对象添加到画布中?“object:added”(对象:添加)为您准备。当整个画布被重新渲染时呢?只需使用“after:render”即可。

事件API非常简单,类似于jQuery,Undercore.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”事件监听器添加到画布上,并为其提供一个事件处理程序,它将记录事件发生位置的坐标。换言之,它将记录在画布上按下鼠标的确切位置。事件处理程序接收一个选项对象,该对象有两个属性:e-原始事件,target-画布上单击的对象(如果有)。事件始终存在,但只有当您确实单击了画布上的某个对象时,target才存在。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: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.on('mouse:down',…))。正如您可以想象的那样,这意味着事件的范围都是画布实例。如果一个页面上有多个画布,可以将不同的事件监听器附加到每个画布上。他们都是独立的,只处理分配给他们自己的事件。

为方便起见,Fabric将进一步提升事件系统,并允许您将侦听器直接附加到canvas画布中的对象上。让我们来看看:

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" 事件。类似地,我们可以使用 “modified” 事件(事件附加到画布时为"object:modified")、 “rotating” 事件(事件在画布上时为"object:rotating")等等。

查看此事件演示,以更深入地了解Fabric的事件系统。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值