Fabric.js 入门
官网文档 fabricjs.com/docs/
操作API demo fabricjs.com/touch-event...
一 、 基础介绍
Fabric.js 是一个流行的 JavaScript HTML5 canvas 库,可以方便我们操作 canvas。
Fabric.js 是基于 DOM 和 BOM 开发的,所以最近写小程序,都不能使用 Fabric.js;都需要使用 webview
为什么使用Fabric.js
因为它提供了许多强大的功能和优点,推荐使用它有以下几个原因:
- 全面的功能: Fabric.js 提供了丰富的图形元素和工具,如矩形、椭圆、多边形、线条、文本等,并允许你在画布上灵活地操作它们。此外,它还支持各种变换和组合模式,如旋转、缩放、倾斜、编组等。
- 性能优化: Fabric.js 在处理大量对象或复杂图形时,能够提供良好的性能。它使用了一个高效的渲染引擎,能够智能地管理渲染缓存,并在需要时进行优化。
- 灵活性和可扩展性: Fabric.js 的设计非常灵活,可以通过继承和扩展现有的对象类来轻松地添加新的功能。此外,它还提供了各种事件(例如点击、触摸、双指缩放等),以方便与用户交互。
- 社区支持: Fabric.js 有一个活跃的社区,提供了大量的教程、示例和插件。这意味着当你遇到问题时,你很容易找到帮助和解决方案。
- 兼容性 : Fabric.js 在各种浏览器上表现良好,包括现代移动设备。这意味着你可以在各种设备上使用 Fabric.js 来创建丰富的交互体验。
总结起来就一句话:Fabric.js 是目前社区内操作 canvas 的库里,使用人数和应用最多的库。而且比其他库体量都大很多
基本概念
- 画布(Canvas)
在 Fabric.js 中,画布是一个非常重要的元素。你可以将它看作是绘画的"画布",可以在上面绘制各种形状、图像和文本。Fabric.js 也提供了丰富的交互和动画功能。
- 对象(Object)
Fabric.js 提供了许多内置对象,如矩形、椭圆、线条、文本等,这些对象都有一些通用属性,如 left
、top
、width
、height
等。同时,Fabric.js 也允许你创建自定义对象。
- 属性(Properties)
对象的属性定义了该对象的特点,比如颜色、大小、位置等。在 Fabric.js 中,你可以直接设置或修改对象的属性。
- 事件(Events)
Fabric.js 提供了丰富的事件模型,包括 mouse events,touch events,object events 等。你可以很容易地添加事件监听器到 Fabric.js 对象上,当特定事件发生时,你的回调函数会被调用。
- 变换(Transformations)
Fabric.js 提供了各种变换方法,如旋转(rotate()
)、缩放(scale()
)、倾斜(skewX()
和 skewY()
)等。这些方法可以用来改变对象的位置、大小和形状。
- 组合(Composition)
在 Fabric.js 中,你可以创建复杂的图形组合。一个组合可以包含多个对象,这些对象会作为一个整体进行变换。
- 图层(Layers)
在 Fabric.js 中,你可以创建多个画布,并在这些画布上添加对象。这样,你可以轻松地管理复杂的图形层次结构,每个层次可以包含多个对象。
- SVG 支持
Fabric.js 还支持从 SVG 文件创建对象。这意味着你可以很容易地从 SVG 文件导入图形,然后在 Fabric.js 画布上进行操作。
Fabric.js对比原生canvas
Fabric.js 和原生canvas都是用于创建和操作图形的强大工具,但它们在某些方面有所不同。以下是使用这两种库创建矩形的基本示例。
使用 Fabric.js 创建矩形:
arduino
// 引入 Fabric 库
import fabric from 'fabric';
const canvas = new fabric.Canvas('c');
// 创建矩形对象
const rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 200,
height: 200
});
// 将矩形添加到画布上
canvas.add(rect);
使用原生 canvas 创建矩形:
ini
// 获取页面中的canvas元素
const cnv = document.getElementById('c')
const ctx = cnv.getContext('2d')
ctx.moveTo(50, 50)
ctx.lineTo(200, 50)
ctx.lineTo(200, 120)
ctx.lineTo(50, 120)
ctx.lineTo(50, 50)
ctx.fillStyle = 'red'
ctx.fill()
这两段代码都会在 HTML 页面中创建一个红色的矩形,可以看出来 Fabric.js 的使用上更符合语义化,而且不用去换算每一笔画的位置等等繁琐过程
二、创建画布
绘制的开始最重要的是创建画布,下面以 react 为例;
javascript
function Canvas() {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = new fabric.Canvas(canvasRef.current,
{
// ...【各种配置】
fireRightClick: true, // 启用右键,button的数字为3
stopContextMenu: true, // 禁止默认右键菜单
controlsAboveOverlay: true, // 超出clipPath后仍然展示控制条
preserveObjectStacking: true
});
}, []);
return (
<canvas ref={canvasRef} width={600} height={400}/>
);
}
- 使用 useRef 引入 canvas 标签,这样可以避免页面多个 canvas 时使用 id 造成冲突;
- 【各种配置】
Canvas 对象下的所有成员都可以加
- 设置 width 和 height(非常重要);如图是写在标签上固定的宽高。
项目中常用的是根据图片的宽高和页面视窗换算后再设置宽高;
宽高没设置,或者是设置错误会导致渲染问题。
scss
canvasRef.current.setWidth(600)
canvasRef.current.setHeight(800)
三、绘制内容
往画布上绘制内容简单的说就两步,1. 绘制对象;2. 添加到画布
绘制对象
fabric.js 内置的对象有很多,这里简单举个例子;
php
// 矩形
const rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 200,
height: 200
});
canvas.add(rect);
// 圆形
const circle = new fabric.Circle({
left: 100,
top: 100,
fill: 'blue',
radius: 100,
});
canvas.add(circle);
// 文本
const text = new fabric.Text('今晚吃鸡,大吉大利', {
left: 100,
top: 100,
fill: 'black',
fontSize: 30,
});
canvas.add(text);
文档左边目录这里都是可使用的对象,大家需要时自己查阅。
看上面的代码,绘制时比较重要的点位信息是 top, left, right, bottom, width, height
以图片画框为例:
基准点都是以图片的左上角为 0, 0
向右为 X 轴正方向
向下为 Y 轴正方向
一张图上的一个框,点位数据是四个点:左上角,右上角,右下角,左下角;
points:"x1,y1,x2,y2,x3,y3,x4,y4"
Top: y1; left: x1; Width: x2-x1; Height: y3-y2
这时直接画上去大概率是错误的!
原图的点位画在原图上才是正确的,在屏幕上显示时图片大概率不是原图尺寸;一定要进行缩放换算
比例就是显图和原图的比例。offsetWidth / imgOriginWidth
但是这里有两个坑
- 算出的 scale 不是画布对象上的 scaleX ,scaleY
scaleX 和 scaleY 表示的是当前对象在画布上的缩放,一开始画上去默认是 1;用户操作缩放后才出发改变,不要跟图片缩放搞混。
-
边框设置 strokeWidth 时, width 和 height 要减去一个 strokeWidth;
设置对象
scss
getObjects()
getActiveObjects()
target.set({
stroke: '#35A1F7',
fill: 'rgba(53, 161, 247, 0.2)',
})
// canvas 可设置刷新机制,比如删除对象时更新画布;设置对象时并不会自动更新画布,执行 renderAll 更新
canvas.renderAll()
设置背景图
ini
const imageElement = new Image();
imageElement.setAttribute('crossOrigin', 'anonymous');
imageElement.src = src;
imageElement.onload = () => {
const { width: imgOriginWidth, height: imgOriginHeight } = imageElement;
// 计算缩放比例
const { offsetWidth, offsetHeight } = boxDomRef.current;
const scale_img_width = offsetWidth / imgOriginWidth; // 获取容器和图片的宽度比
const scale_img_height = offsetHeight / imgOriginHeight; // 获取容器和图片的高度比
// 如果图片的比例 高度大于最大高度 就以宽度的比例。
const scale = imgOriginHeight * scale_img_width > offsetHeight ? scale_img_height : scale_img_width;
canvasRef.current.scale = scale;
// 图片实际在canvas上的宽高
const imageWidth = imgOriginWidth * scale;
const imageHeight = imgOriginHeight * scale;
canvasRef.current.setWidth(offsetWidth);
canvasRef.current.setHeight(offsetHeight);
canvasRef.current.origin = imgData; // 将数据绑定到 canvas
fabric.Image.fromURL(src, img => {
// 是否居中 canvas 位置
const config: any = { scaleX: scale, scaleY: scale, type: 'background' };
if (centerX) {
config.left = (offsetWidth - imageWidth) / 2;
} else {
config.left = 0;
}
if (centerY) {
config.top = (offsetHeight - imageHeight) / 2;
} else {
config.top = 0;
}
img.set(config);
// 2. 再画图
canvasRef.current.setBackgroundImage(img, canvasRef.current.renderAll.bind(canvasRef.current));
});
};
绑定事件
未完待续
好用的API
- _previousPointer
记录鼠标上一次点击的位置;
这样可以很方便的解决代码里,每次 mouseDown 时记录初始位置,太麻烦。
- vptCoords
获取画布偏移的位置
鼠标画框
首先 isDrawingMode 设置为 false,开启时是画笔,影响画框。
两种方式:
- 通过 selection 配合 mouse 事件
ini
canvas.selection = true;
canvas.selectionColor = "transparent"; // 选框填充色:填充色:透明
canvas.selectionBorderColor = "#e11919"; // 选框边框颜色:红色
// 以上代码会在鼠标拖动时形成一个矩形框
// 然后再执行 mouse:up 事件时将矩形框添加至 canvas 上
- 只通过 mouse 事件
arduino
通过 mouse:down;mouse:move;mouse:up 三个事件组合画框