Fabric.js 完全指南:从入门到实战的Canvas绘图引擎详解

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 绘图领域的佼佼者。本文涵盖了从基础绘图、对象操作、事件处理到自定义对象、性能优化的核心知识点,配合实战示例,足以支撑大部分绘图类应用的开发。

进一步学习资源:

掌握 Fabric.js 后,你可以尝试开发更复杂的应用,如图表工具、图片编辑器、在线白板等。动手实践是最好的学习方式,不妨从本文的示例开始,逐步探索更多可能性!

相关推荐
zhz52144 小时前
ArcGIS Pro 进程管理:自动化解决方案与最佳实践
运维·python·arcgis·自动化
颜酱4 小时前
理解 Webpack 的构建过程(实现原理),并实现一个 mini 版
前端·javascript·webpack
aesthetician4 小时前
Node.js 24.10.0: 拥抱现代 JavaScript 与增强性能
开发语言·javascript·node.js
前端老鹰4 小时前
解锁 JavaScript 字符串补全魔法:padStart()与 padEnd()
前端·javascript
狂野小青年4 小时前
Docker部署的gitlab升级的详细步骤(升级到17.6.1版本)
运维·docker·容器·gitlab升级
摸着石头过河的石头5 小时前
函数的超能力:JavaScript高阶函数完全指南
前端·javascript
渣哥5 小时前
面试必问:Spring 框架的核心优势,你能说全吗?
javascript·后端·面试
盛满暮色 风止何安6 小时前
防火墙的类别和登录Web的方法
linux·运维·服务器·网络·网络协议·tcp/ip·网络安全
千码君20166 小时前
React Native::关于react的匿名函数
javascript·react native·react.js·匿名函数·usecallback·命名函数·记忆化函数