轻量fabric.js实现指南:物体基类设计与应用🏖

轻量fabric.js系列三:物体基类设计与实现🏖

引言:为什么需要物体基类?

在图形编辑系统中,物体基类(Object Base Class)是所有可视化元素的核心抽象。它定义了图形对象的通用属性(如位置、尺寸、颜色)和行为(如移动、缩放、序列化),为后续扩展矩形、圆形、路径等具体图形类型提供统一接口。

轻量fabric.js的目标是简化核心逻辑,去除冗余功能,保留最关键的物体管理能力。本文将详细拆解物体基类的设计思路,并提供可复用的代码实现。

一、物体基类的核心职责

1. 属性管理:统一数据结构

物体基类需管理两类属性:

  • 基础属性x, y(位置)、width, height(尺寸)、visible(可见性)
  • 样式属性fill(填充色)、stroke(边框色)、opacity(透明度)
  1. class LightFabricObject {
  2. constructor(options = {}) {
  3. // 基础属性
  4. this.x = options.x || 0;
  5. this.y = options.y || 0;
  6. this.width = options.width || 100;
  7. this.height = options.height || 100;
  8. this.visible = options.visible !== false;
  9. // 样式属性
  10. this.fill = options.fill || '#000000';
  11. this.stroke = options.stroke || null;
  12. this.opacity = options.opacity || 1;
  13. }
  14. }

2. 坐标变换:支持矩阵操作

图形编辑需支持旋转、缩放等变换,基类需封装变换矩阵的计算逻辑:

  1. class LightFabricObject {
  2. // ...其他代码
  3. setAngle(angle) {
  4. this.angle = angle;
  5. this.calculateTransformMatrix();
  6. }
  7. calculateTransformMatrix() {
  8. const rad = (this.angle || 0) * Math.PI / 180;
  9. const cos = Math.cos(rad);
  10. const sin = Math.sin(rad);
  11. // 简化版变换矩阵(省略透视和错切)
  12. this.transformMatrix = [
  13. cos * this.scaleX, sin * this.scaleY, 0,
  14. -sin * this.scaleX, cos * this.scaleY, 0,
  15. this.x, this.y, 1
  16. ];
  17. }
  18. }

3. 事件系统:基础交互支持

基类需提供事件分发机制,支持点击、拖拽等交互:

  1. class LightFabricObject {
  2. constructor() {
  3. this._eventListeners = {};
  4. }
  5. on(eventName, callback) {
  6. if (!this._eventListeners[eventName]) {
  7. this._eventListeners[eventName] = [];
  8. }
  9. this._eventListeners[eventName].push(callback);
  10. }
  11. fire(eventName, ...args) {
  12. const listeners = this._eventListeners[eventName] || [];
  13. listeners.forEach(cb => cb(...args));
  14. }
  15. }

二、关键方法实现

1. 边界框计算(Bounding Box)

计算物体在画布中的实际占据区域,考虑旋转和缩放:

  1. class LightFabricObject {
  2. getBoundingRect() {
  3. // 简化版:假设物体是矩形,旋转后边界框需重新计算
  4. const halfWidth = this.width / 2;
  5. const halfHeight = this.height / 2;
  6. const rad = (this.angle || 0) * Math.PI / 180;
  7. // 旋转后的四个角点
  8. const points = [
  9. { x: -halfWidth, y: -halfHeight },
  10. { x: halfWidth, y: -halfHeight },
  11. { x: halfWidth, y: halfHeight },
  12. { x: -halfWidth, y: halfHeight }
  13. ].map(p => ({
  14. x: p.x * Math.cos(rad) - p.y * Math.sin(rad) + this.x,
  15. y: p.x * Math.sin(rad) + p.y * Math.cos(rad) + this.y
  16. }));
  17. // 计算最小包围矩形
  18. const minX = Math.min(...points.map(p => p.x));
  19. const maxX = Math.max(...points.map(p => p.x));
  20. const minY = Math.min(...points.map(p => p.y));
  21. const maxY = Math.max(...points.map(p => p.y));
  22. return {
  23. left: minX,
  24. top: minY,
  25. width: maxX - minX,
  26. height: maxY - minY
  27. };
  28. }
  29. }

2. 序列化与反序列化

支持将物体保存为JSON或从JSON恢复:

  1. class LightFabricObject {
  2. toJSON() {
  3. return {
  4. type: this.type || 'object',
  5. x: this.x,
  6. y: this.y,
  7. width: this.width,
  8. height: this.height,
  9. fill: this.fill,
  10. stroke: this.stroke,
  11. opacity: this.opacity,
  12. angle: this.angle
  13. };
  14. }
  15. static fromJSON(json) {
  16. const obj = new this(); // 假设子类调用
  17. Object.assign(obj, json);
  18. return obj;
  19. }
  20. }

三、性能优化策略

1. 脏标记机制(Dirty Flag)

仅在属性变更时重新计算布局:

  1. class LightFabricObject {
  2. constructor() {
  3. this._isDirty = false;
  4. }
  5. set x(value) {
  6. this._x = value;
  7. this._isDirty = true;
  8. }
  9. get x() {
  10. return this._x;
  11. }
  12. update() {
  13. if (this._isDirty) {
  14. this.calculateTransformMatrix();
  15. this._isDirty = false;
  16. }
  17. }
  18. }

2. 批量渲染

合并多个物体的渲染调用,减少DOM操作:

  1. class LightFabricCanvas {
  2. renderAll() {
  3. this.objects.forEach(obj => obj.update());
  4. // 使用离屏Canvas或requestAnimationFrame优化
  5. }
  6. }

四、扩展性设计

1. 混合模式(Mixin)支持

通过组合模式扩展功能(如可拖拽、可选中):

  1. const DraggableMixin = {
  2. initDrag() {
  3. this.on('mousedown', this.startDrag.bind(this));
  4. },
  5. startDrag(event) {
  6. // 实现拖拽逻辑
  7. }
  8. };
  9. class LightFabricRect extends LightFabricObject {
  10. constructor(options) {
  11. super(options);
  12. Object.assign(this, DraggableMixin);
  13. this.initDrag();
  14. }
  15. }

2. 插件化架构

允许通过插件添加新功能(如滤镜、动画):

  1. class LightFabricPluginSystem {
  2. constructor(canvas) {
  3. this.plugins = [];
  4. this.canvas = canvas;
  5. }
  6. addPlugin(plugin) {
  7. this.plugins.push(plugin);
  8. plugin.install(this.canvas);
  9. }
  10. }

五、实际应用示例

1. 创建自定义矩形类

  1. class LightFabricRect extends LightFabricObject {
  2. constructor(options) {
  3. super(options);
  4. this.type = 'rect';
  5. this.rx = options.rx || 0; // 圆角
  6. this.ry = options.ry || 0;
  7. }
  8. render(ctx) {
  9. if (!this.visible) return;
  10. ctx.save();
  11. ctx.globalAlpha = this.opacity;
  12. ctx.translate(this.x, this.y);
  13. ctx.rotate(this.angle * Math.PI / 180);
  14. ctx.fillStyle = this.fill;
  15. ctx.beginPath();
  16. ctx.roundRect(-this.width / 2, -this.height / 2,
  17. this.width, this.height, this.rx, this.ry);
  18. ctx.fill();
  19. if (this.stroke) {
  20. ctx.strokeStyle = this.stroke;
  21. ctx.stroke();
  22. }
  23. ctx.restore();
  24. }
  25. }

2. 与Canvas集成

  1. class LightFabricCanvas {
  2. constructor(canvasElement) {
  3. this.canvas = canvasElement;
  4. this.ctx = canvasElement.getContext('2d');
  5. this.objects = [];
  6. }
  7. add(object) {
  8. this.objects.push(object);
  9. }
  10. render() {
  11. this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  12. this.objects.forEach(obj => {
  13. obj.render(this.ctx);
  14. });
  15. }
  16. }

结论:基类设计的价值

通过实现轻量化的物体基类,我们获得了以下优势:

  1. 代码复用:所有图形类型共享核心逻辑
  2. 性能优化:集中管理渲染和变换计算
  3. 扩展性:通过混合模式和插件支持新功能
  4. 可维护性:清晰的职责分离

实际项目中,建议根据需求进一步裁剪功能,例如移除未使用的变换矩阵计算或简化事件系统。下一篇将介绍具体图形类型的实现(如文本、图片),敬请期待!