轻量fabric.js系列三:物体基类设计与实现🏖
引言:为什么需要物体基类?
在图形编辑系统中,物体基类(Object Base Class)是所有可视化元素的核心抽象。它定义了图形对象的通用属性(如位置、尺寸、颜色)和行为(如移动、缩放、序列化),为后续扩展矩形、圆形、路径等具体图形类型提供统一接口。
轻量fabric.js的目标是简化核心逻辑,去除冗余功能,保留最关键的物体管理能力。本文将详细拆解物体基类的设计思路,并提供可复用的代码实现。
一、物体基类的核心职责
1. 属性管理:统一数据结构
物体基类需管理两类属性:
- 基础属性:
x,y(位置)、width,height(尺寸)、visible(可见性) - 样式属性:
fill(填充色)、stroke(边框色)、opacity(透明度)
class LightFabricObject {constructor(options = {}) {// 基础属性this.x = options.x || 0;this.y = options.y || 0;this.width = options.width || 100;this.height = options.height || 100;this.visible = options.visible !== false;// 样式属性this.fill = options.fill || '#000000';this.stroke = options.stroke || null;this.opacity = options.opacity || 1;}}
2. 坐标变换:支持矩阵操作
图形编辑需支持旋转、缩放等变换,基类需封装变换矩阵的计算逻辑:
class LightFabricObject {// ...其他代码setAngle(angle) {this.angle = angle;this.calculateTransformMatrix();}calculateTransformMatrix() {const rad = (this.angle || 0) * Math.PI / 180;const cos = Math.cos(rad);const sin = Math.sin(rad);// 简化版变换矩阵(省略透视和错切)this.transformMatrix = [cos * this.scaleX, sin * this.scaleY, 0,-sin * this.scaleX, cos * this.scaleY, 0,this.x, this.y, 1];}}
3. 事件系统:基础交互支持
基类需提供事件分发机制,支持点击、拖拽等交互:
class LightFabricObject {constructor() {this._eventListeners = {};}on(eventName, callback) {if (!this._eventListeners[eventName]) {this._eventListeners[eventName] = [];}this._eventListeners[eventName].push(callback);}fire(eventName, ...args) {const listeners = this._eventListeners[eventName] || [];listeners.forEach(cb => cb(...args));}}
二、关键方法实现
1. 边界框计算(Bounding Box)
计算物体在画布中的实际占据区域,考虑旋转和缩放:
class LightFabricObject {getBoundingRect() {// 简化版:假设物体是矩形,旋转后边界框需重新计算const halfWidth = this.width / 2;const halfHeight = this.height / 2;const rad = (this.angle || 0) * Math.PI / 180;// 旋转后的四个角点const points = [{ x: -halfWidth, y: -halfHeight },{ x: halfWidth, y: -halfHeight },{ x: halfWidth, y: halfHeight },{ x: -halfWidth, y: halfHeight }].map(p => ({x: p.x * Math.cos(rad) - p.y * Math.sin(rad) + this.x,y: p.x * Math.sin(rad) + p.y * Math.cos(rad) + this.y}));// 计算最小包围矩形const minX = Math.min(...points.map(p => p.x));const maxX = Math.max(...points.map(p => p.x));const minY = Math.min(...points.map(p => p.y));const maxY = Math.max(...points.map(p => p.y));return {left: minX,top: minY,width: maxX - minX,height: maxY - minY};}}
2. 序列化与反序列化
支持将物体保存为JSON或从JSON恢复:
class LightFabricObject {toJSON() {return {type: this.type || 'object',x: this.x,y: this.y,width: this.width,height: this.height,fill: this.fill,stroke: this.stroke,opacity: this.opacity,angle: this.angle};}static fromJSON(json) {const obj = new this(); // 假设子类调用Object.assign(obj, json);return obj;}}
三、性能优化策略
1. 脏标记机制(Dirty Flag)
仅在属性变更时重新计算布局:
class LightFabricObject {constructor() {this._isDirty = false;}set x(value) {this._x = value;this._isDirty = true;}get x() {return this._x;}update() {if (this._isDirty) {this.calculateTransformMatrix();this._isDirty = false;}}}
2. 批量渲染
合并多个物体的渲染调用,减少DOM操作:
class LightFabricCanvas {renderAll() {this.objects.forEach(obj => obj.update());// 使用离屏Canvas或requestAnimationFrame优化}}
四、扩展性设计
1. 混合模式(Mixin)支持
通过组合模式扩展功能(如可拖拽、可选中):
const DraggableMixin = {initDrag() {this.on('mousedown', this.startDrag.bind(this));},startDrag(event) {// 实现拖拽逻辑}};class LightFabricRect extends LightFabricObject {constructor(options) {super(options);Object.assign(this, DraggableMixin);this.initDrag();}}
2. 插件化架构
允许通过插件添加新功能(如滤镜、动画):
class LightFabricPluginSystem {constructor(canvas) {this.plugins = [];this.canvas = canvas;}addPlugin(plugin) {this.plugins.push(plugin);plugin.install(this.canvas);}}
五、实际应用示例
1. 创建自定义矩形类
class LightFabricRect extends LightFabricObject {constructor(options) {super(options);this.type = 'rect';this.rx = options.rx || 0; // 圆角this.ry = options.ry || 0;}render(ctx) {if (!this.visible) return;ctx.save();ctx.globalAlpha = this.opacity;ctx.translate(this.x, this.y);ctx.rotate(this.angle * Math.PI / 180);ctx.fillStyle = this.fill;ctx.beginPath();ctx.roundRect(-this.width / 2, -this.height / 2,this.width, this.height, this.rx, this.ry);ctx.fill();if (this.stroke) {ctx.strokeStyle = this.stroke;ctx.stroke();}ctx.restore();}}
2. 与Canvas集成
class LightFabricCanvas {constructor(canvasElement) {this.canvas = canvasElement;this.ctx = canvasElement.getContext('2d');this.objects = [];}add(object) {this.objects.push(object);}render() {this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);this.objects.forEach(obj => {obj.render(this.ctx);});}}
结论:基类设计的价值
通过实现轻量化的物体基类,我们获得了以下优势:
- 代码复用:所有图形类型共享核心逻辑
- 性能优化:集中管理渲染和变换计算
- 扩展性:通过混合模式和插件支持新功能
- 可维护性:清晰的职责分离
实际项目中,建议根据需求进一步裁剪功能,例如移除未使用的变换矩阵计算或简化事件系统。下一篇将介绍具体图形类型的实现(如文本、图片),敬请期待!