Fabric.js 是一款基于 HTML5 Canvas 的强大绘图库,它简化了 Canvas 原生 API 的复杂操作,提供了面向对象的编程模型,让开发者能轻松实现图形绘制、编辑、交互等功能。无论是开发简单的涂鸦工具,还是复杂的设计协作平台,Fabric.js 都是高效且可靠的选择。
本文将从基础到进阶,结合大量实战示例,全面讲解 Fabric.js 的核心功能与使用技巧,帮助你快速掌握这一工具的精髓。
一、Fabric.js 简介:为什么选择它?
Canvas 原生 API 虽然强大,但存在诸多痛点:
- 操作繁琐(绘制图形需手动调用
beginPath()
、fill()
等一系列方法); - 缺乏对象概念(绘制的图形无法直接作为"对象"进行选中、移动等操作);
- 状态管理复杂(无法直接保存/还原画布状态)。
Fabric.js 正是为解决这些问题而生:
- 面向对象:将图形、文本、图片等抽象为"对象",支持直接操作(选中、移动、缩放等);
- 丰富的API:内置大量图形类型(矩形、圆形、多边形等)和交互功能(拖拽、旋转、组合等);
- 序列化支持:可将画布状态转为 JSON 数据,轻松实现保存、还原、跨端同步;
- 扩展性强:支持自定义对象、滤镜、动画等高级功能。
二、快速入门:环境搭建与基础操作
1. 环境搭建
Fabric.js 支持多种引入方式,根据项目需求选择:
方式1:CDN 引入(适合快速测试)
html
<!-- 引入最新版 -->
<script src="https://cdn.jsdelivr.net/npm/fabric@5.3.0/dist/fabric.min.js"></script>
<!-- 页面中添加Canvas元素 -->
<canvas id="canvas" width="800" height="600" style="border: 1px solid #ccc;"></canvas>
方式2:npm 安装(适合工程化项目)
bash
npm install fabric --save
引入使用:
javascript
import { fabric } from 'fabric';
2. 初始化画布
通过 fabric.Canvas
类创建画布实例,这是所有操作的基础:
javascript
// 获取Canvas DOM元素
const canvasElement = document.getElementById('canvas');
// 初始化画布
const canvas = new fabric.Canvas(canvasElement, {
// 可选配置
width: 800, // 画布宽度(默认使用Canvas元素宽高)
height: 600, // 画布高度
backgroundColor: '#f5f5f5', // 背景色
selectionColor: 'rgba(255, 255, 0, 0.3)', // 选中对象时的背景色
selectionBorderColor: 'yellow', // 选中对象的边框色
});
3. 绘制基础图形
Fabric.js 内置了多种基础图形,通过简单的 API 即可创建并添加到画布:
示例1:绘制矩形
javascript
// 创建矩形对象
const rect = new fabric.Rect({
left: 100, // 左上角x坐标
top: 100, // 左上角y坐标
width: 150, // 宽度
height: 100, // 高度
fill: 'red', // 填充色
stroke: 'blue', // 边框色
strokeWidth: 2, // 边框宽度
angle: 15, // 旋转角度(度)
opacity: 0.8, // 透明度(0-1)
});
// 添加到画布
canvas.add(rect);
示例2:绘制圆形
javascript
const circle = new fabric.Circle({
left: 300,
top: 200,
radius: 50, // 半径
fill: 'green',
stroke: 'black',
strokeWidth: 1,
});
canvas.add(circle);
示例3:绘制文本
javascript
const text = new fabric.Text('Hello Fabric.js', {
left: 200,
top: 300,
fontSize: 24, // 字体大小
fill: '#333', // 文字颜色
fontWeight: 'bold', // 字体粗细
fontFamily: 'Arial', // 字体
angle: -10, // 旋转角度
});
canvas.add(text);
添加图形后,默认支持鼠标交互:点击选中对象,拖拽移动,拖动边角缩放/旋转。
三、核心功能:对象操作与事件处理
1. 对象基本操作
Fabric.js 的对象(图形、文本、图片等)都继承自 fabric.Object
,拥有统一的操作方法:
选中/取消选中对象
javascript
// 选中指定对象
canvas.setActiveObject(rect);
// 取消所有选中
canvas.discardActiveObject();
// 获取当前选中的对象
const activeObj = canvas.getActiveObject();
if (activeObj) {
console.log('当前选中对象:', activeObj.type); // 输出对象类型(如'rect'、'circle')
}
修改对象属性
javascript
// 直接修改属性(修改后需刷新画布)
rect.set({
fill: 'purple', // 更改填充色
width: 200, // 更改宽度
angle: 0, // 重置旋转角度
});
// 刷新画布使修改生效
canvas.renderAll();
删除对象
javascript
// 删除选中的对象
if (activeObj) {
canvas.remove(activeObj);
}
// 删除所有对象
canvas.clear();
2. 事件系统
Fabric.js 提供了丰富的事件机制,可监听画布或对象的各种行为(点击、移动、选中等)。
画布事件
javascript
// 监听画布点击
canvas.on('mouse:down', (e) => {
console.log('画布点击位置:', e.pointer.x, e.pointer.y);
});
// 监听画布上对象被选中
canvas.on('object:selected', (e) => {
console.log('选中对象:', e.target.type);
});
// 监听对象被移动
canvas.on('object:moved', (e) => {
console.log('对象新位置:', e.target.left, e.target.top);
});
对象事件
javascript
// 给单个对象绑定点击事件
rect.on('mousedown', () => {
console.log('矩形被点击了');
});
// 监听对象缩放事件
circle.on('scaled', (e) => {
console.log('圆形缩放后尺寸:', e.target.width, e.target.height);
});
3. 序列化与反序列化(状态保存/还原)
Fabric.js 最实用的功能之一:将画布状态转为可存储的数据,需要时再完整还原。
保存画布状态
javascript
// 序列化画布(包含所有对象信息)
const canvasData = canvas.toJSON();
// 转为JSON字符串(方便存储到localStorage或服务器)
const dataStr = JSON.stringify(canvasData);
// 保存到本地存储
localStorage.setItem('myCanvasState', dataStr);
还原画布状态
javascript
// 从本地存储读取数据
const savedData = JSON.parse(localStorage.getItem('myCanvasState'));
// 加载数据并还原画布
canvas.loadFromJSON(savedData, () => {
// 回调函数:数据加载完成后执行
canvas.renderAll(); // 强制刷新画布
console.log('画布已还原');
});
自定义序列化字段
默认 toJSON()
会保存对象的所有属性,可通过参数指定需要保存的字段(减少数据体积):
javascript
// 只保存位置、大小、填充色
const simpleData = canvas.toJSON(['left', 'top', 'width', 'height', 'fill']);
4. 图片处理
Fabric.js 支持加载网络图片、本地图片,并对图片进行编辑(缩放、旋转、裁剪等)。
加载网络图片
javascript
fabric.Image.fromURL('https://picsum.photos/200/200', (img) => {
// 图片加载完成后的回调
img.set({
left: 400,
top: 100,
angle: 20,
scaleX: 0.8, // 水平缩放
scaleY: 0.8, // 垂直缩放
});
canvas.add(img);
});
加载本地图片(通过文件选择器)
html
<input type="file" id="imageUpload" accept="image/*">
javascript
document.getElementById('imageUpload').addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
// 读取本地图片为DataURL并加载
fabric.Image.fromURL(event.target.result, (img) => {
img.set({ left: 100, top: 300 });
canvas.add(img);
});
};
reader.readAsDataURL(file);
});
图片裁剪
javascript
fabric.Image.fromURL('https://picsum.photos/400/400', (img) => {
// 裁剪图片(x, y, 宽度, 高度)
img.set({
left: 200,
top: 400,
clipTo: (ctx) => {
// 使用Canvas上下文裁剪为圆形
ctx.arc(100, 100, 100, 0, Math.PI * 2, false);
}
});
canvas.add(img);
});
四、进阶技巧:自定义与性能优化
1. 自定义对象
当内置图形满足不了需求时,可通过继承 fabric.Object
创建自定义对象。
示例:创建一个带文字的矩形
javascript
// 自定义对象类
class LabeledRect extends fabric.Rect {
constructor(options, text) {
// 调用父类构造函数
super(options);
// 自定义属性:文字内容
this.labelText = text || 'Label';
// 确保自定义属性可被序列化
this.toObject = function() {
return {
...super.toObject(),
labelText: this.labelText
};
};
}
// 重写渲染方法,绘制文字
_render(ctx) {
// 先渲染矩形(父类逻辑)
super._render(ctx);
// 再绘制文字
ctx.font = '16px Arial';
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 在矩形中心绘制文字
ctx.fillText(this.labelText, this.width/2, this.height/2);
}
}
// 使用自定义对象
const labeledRect = new LabeledRect({
left: 500,
top: 200,
width: 120,
height: 80,
fill: 'blue'
}, '自定义矩形');
canvas.add(labeledRect);
2. 撤销/重做功能
基于序列化功能,可实现画布操作的撤销与重做:
javascript
// 维护历史记录数组
const history = [];
const historyLimit = 20; // 最大历史记录数
let historyIndex = -1;
// 保存当前状态到历史记录
function saveState() {
// 移除当前状态之后的记录(避免重做时混乱)
if (historyIndex < history.length - 1) {
history.splice(historyIndex + 1);
}
// 序列化当前状态
const state = canvas.toJSON();
history.push(state);
// 限制历史记录数量
if (history.length > historyLimit) {
history.shift();
}
historyIndex = history.length - 1;
}
// 初始化时保存一次状态
saveState();
// 监听对象变化,自动保存状态
canvas.on('object:added object:removed object:modified', saveState);
// 撤销
function undo() {
if (historyIndex > 0) {
historyIndex--;
canvas.loadFromJSON(history[historyIndex], canvas.renderAll.bind(canvas));
}
}
// 重做
function redo() {
if (historyIndex < history.length - 1) {
historyIndex++;
canvas.loadFromJSON(history[historyIndex], canvas.renderAll.bind(canvas));
}
}
// 绑定按钮事件
document.getElementById('undoBtn').addEventListener('click', undo);
document.getElementById('redoBtn').addEventListener('click', redo);
3. 性能优化
当画布上对象数量较多(如 hundreds)时,可能出现卡顿,可通过以下方式优化:
启用对象缓存
javascript
// 对静态对象启用缓存(减少重绘计算)
rect.set({
cache: true, // 启用缓存
objectCaching: true
});
批量操作
多次修改对象时,先暂停渲染,完成后再刷新:
javascript
canvas.pauseRendering(); // 暂停渲染
// 批量修改
for (let i = 0; i < 100; i++) {
const obj = new fabric.Circle({...});
canvas.add(obj);
}
canvas.resumeRendering(); // 恢复渲染
canvas.renderAll(); // 一次性刷新
层级管理
通过 sendToBack()
、bringToFront()
等方法管理对象层级,避免不必要的覆盖计算:
javascript
// 将对象移到最上层
rect.bringToFront();
// 将对象移到最下层
circle.sendToBack();
五、实战案例:简易绘图应用
整合上述知识点,实现一个包含基础绘图、保存/加载、撤销/重做功能的绘图工具:
html
<!-- 工具栏 -->
<div>
<button id="rectBtn">矩形</button>
<button id="circleBtn">圆形</button>
<button id="textBtn">文字</button>
<button id="undoBtn">撤销</button>
<button id="redoBtn">重做</button>
<button id="saveBtn">保存</button>
<button id="loadBtn">加载</button>
<button id="clearBtn">清空</button>
</div>
<canvas id="canvas" width="800" height="600" style="border: 1px solid #ccc;"></canvas>
<script>
const canvas = new fabric.Canvas('canvas', { backgroundColor: '#fff' });
// 初始化历史记录(参考上文撤销/重做代码)
const history = [canvas.toJSON()];
let historyIndex = 0;
function saveState() { /* 实现同上 */ }
// 工具栏事件
document.getElementById('rectBtn').addEventListener('click', () => {
const rect = new fabric.Rect({
left: 100, top: 100, width: 100, height: 70, fill: '#' + Math.random().toString(16).slice(-6)
});
canvas.add(rect);
});
document.getElementById('circleBtn').addEventListener('click', () => {
const circle = new fabric.Circle({
left: 250, top: 150, radius: 40, fill: '#' + Math.random().toString(16).slice(-6)
});
canvas.add(circle);
});
document.getElementById('textBtn').addEventListener('click', () => {
const text = new fabric.Text('双击编辑', {
left: 400, top: 200, fontSize: 20, fill: '#333'
});
canvas.add(text);
});
document.getElementById('undoBtn').addEventListener('click', () => { /* 实现同上 */ });
document.getElementById('redoBtn').addEventListener('click', () => { /* 实现同上 */ });
document.getElementById('saveBtn').addEventListener('click', () => {
localStorage.setItem('myDrawing', JSON.stringify(canvas.toJSON()));
alert('保存成功');
});
document.getElementById('loadBtn').addEventListener('click', () => {
const data = localStorage.getItem('myDrawing');
if (data) {
canvas.loadFromJSON(JSON.parse(data), canvas.renderAll.bind(canvas));
}
});
document.getElementById('clearBtn').addEventListener('click', () => {
canvas.clear();
saveState();
});
// 监听对象变化,自动保存历史
canvas.on('object:added object:removed object:modified', saveState);
</script>
六、总结与资源推荐
Fabric.js 凭借其简洁的 API 和强大的功能,成为 Canvas 绘图领域的佼佼者。本文涵盖了从基础绘图、对象操作、事件处理到自定义对象、性能优化的核心知识点,配合实战示例,足以支撑大部分绘图类应用的开发。
进一步学习资源:
- 官方文档:fabricjs.com/docs(最权威的API参考)
- GitHub 仓库:github.com/fabricjs/fabric.js(源码与示例)
- 官方示例:fabricjs.com/demos(直观展示各种功能)
掌握 Fabric.js 后,你可以尝试开发更复杂的应用,如图表工具、图片编辑器、在线白板等。动手实践是最好的学习方式,不妨从本文的示例开始,逐步探索更多可能性!