vue2 + fabric 实现 辅助线 功能(指令版)

1. 先新建一个指令文件,比如 directives/guideline.js:
import { fabric } from 'fabric';

export default {
  inserted(el) {
    // 创建 fabric.Canvas
    const canvas = new fabric.Canvas(el);

    let ctx = canvas.getSelectionContext(),
        aligningLineOffset = 5,
        aligningLineMargin = 4,
        aligningLineWidth = 1,
        aligningLineColor = '#3370ff',
        viewportTransform,
        zoom = 1;

    const verticalLines = [], horizontalLines = [];

    function drawVerticalLine(coords) {
      drawLine(coords.x, coords.y1, coords.x, coords.y2);
    }

    function drawHorizontalLine(coords) {
      drawLine(coords.x1, coords.y, coords.x2, coords.y);
    }

    function drawLine(x1, y1, x2, y2) {
      ctx.save();
      ctx.lineWidth = aligningLineWidth;
      ctx.strokeStyle = aligningLineColor;
      ctx.beginPath();
      ctx.moveTo(x1 * zoom + viewportTransform[4], y1 * zoom + viewportTransform[5]);
      ctx.lineTo(x2 * zoom + viewportTransform[4], y2 * zoom + viewportTransform[5]);
      ctx.stroke();
      ctx.restore();
    }

    function isInRange(value1, value2) {
      return Math.abs(Math.round(value1) - Math.round(value2)) <= aligningLineMargin;
    }

    canvas.on('mouse:down', () => {
      viewportTransform = canvas.viewportTransform;
      zoom = canvas.getZoom();
    });

    canvas.on('object:moving', (e) => {
      const activeObject = e.target;
      if (!activeObject) return;

      const center = activeObject.getCenterPoint();
      const canvasCenter = {
        x: canvas.getWidth() / 2,
        y: canvas.getHeight() / 2
      };

      const objBounds = activeObject.getBoundingRect(true);
      const objWidth = objBounds.width / zoom;
      const objHeight = objBounds.height / zoom;

      let hasCenterSnap = false;
      let hasEdgeSnap = false;

      verticalLines.length = 0;
      horizontalLines.length = 0;

      // 中心线吸附
      if (isInRange(center.x, canvasCenter.x)) {
        verticalLines.push({
          x: canvasCenter.x,
          y1: 0,
          y2: canvas.getHeight()
        });
        activeObject.left = canvasCenter.x - objWidth / 2;
        hasCenterSnap = true;
      }

      if (isInRange(center.y, canvasCenter.y)) {
        horizontalLines.push({
          y: canvasCenter.y,
          x1: 0,
          x2: canvas.getWidth()
        });
        activeObject.top = canvasCenter.y - objHeight / 2;
        hasCenterSnap = true;
      }

      // 元素之间吸附
      const objects = canvas.getObjects().filter(o => o !== activeObject);
      for (let obj of objects) {
        const oCenter = obj.getCenterPoint();
        const oBounds = obj.getBoundingRect(true);
        const oWidth = oBounds.width / zoom;
        const oHeight = oBounds.height / zoom;

        if (isInRange(obj.left, activeObject.left)) {
          verticalLines.push({
            x: obj.left,
            y1: 0,
            y2: canvas.getHeight()
          });
          activeObject.left = obj.left;
          hasEdgeSnap = true;
        }

        const objRight = obj.left + oWidth;
        const activeRight = activeObject.left + objWidth;
        if (isInRange(objRight, activeRight)) {
          verticalLines.push({
            x: objRight,
            y1: 0,
            y2: canvas.getHeight()
          });
          activeObject.left = objRight - objWidth;
          hasEdgeSnap = true;
        }

        if (isInRange(obj.top, activeObject.top)) {
          horizontalLines.push({
            y: obj.top,
            x1: 0,
            x2: canvas.getWidth()
          });
          activeObject.top = obj.top;
          hasEdgeSnap = true;
        }

        const objBottom = obj.top + oHeight;
        const activeBottom = activeObject.top + objHeight;
        if (isInRange(objBottom, activeBottom)) {
          horizontalLines.push({
            y: objBottom,
            x1: 0,
            x2: canvas.getWidth()
          });
          activeObject.top = objBottom - objHeight;
          hasEdgeSnap = true;
        }

        if (isInRange(oCenter.x, center.x)) {
          verticalLines.push({
            x: oCenter.x,
            y1: 0,
            y2: canvas.getHeight()
          });
          activeObject.left = oCenter.x - objWidth / 2;
          hasEdgeSnap = true;
        }

        if (isInRange(oCenter.y, center.y)) {
          horizontalLines.push({
            y: oCenter.y,
            x1: 0,
            x2: canvas.getWidth()
          });
          activeObject.top = oCenter.y - objHeight / 2;
          hasEdgeSnap = true;
        }
      }

      if (hasCenterSnap || hasEdgeSnap) {
        activeObject.setCoords();
      }
    });

    canvas.on('before:render', () => {
      if (canvas.contextTop) {
        canvas.clearContext(canvas.contextTop);
      }
    });

    canvas.on('after:render', () => {
      verticalLines.forEach(drawVerticalLine);
      horizontalLines.forEach(drawHorizontalLine);
    });

    canvas.on('mouse:up', () => {
      verticalLines.length = 0;
      horizontalLines.length = 0;
      canvas.renderAll();
    });

    // 把 canvas 挂到元素上,外面可以通过 el.canvas 拿到
    el.canvas = canvas;
  },

  unbind(el) {
    // 销毁 canvas
    if (el.canvas) {
      el.canvas.dispose();
    }
  }
}
2. 然后在 main.js 里全局注册
import Vue from 'vue';
import guideline from './directives/guideline';

Vue.directive('guideline', guideline);
用法:

在任意组件里,直接用:

<canvas id="myCanvas" v-guideline width="500" height="500"></canvas>
效果:
  1. 自动初始化 Fabric Canvas
  2. 自动开启辅助线
  3. 支持旋转、居中、边缘吸附
  4. 松开自动清除
  5. 不用你管任何复杂逻辑
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容