Fabric.js 实现一条自定义的线条

654 阅读2分钟

最近开发遇到一个问题,app 端的线条和 web 端的线条不一致,产品的需求是要实现一致。

差异

具体的差异如下:

app 端

app 端的线条上下有 5px 的内边距,原点在左上角控制框左上角)。

期望.png

web 端

Fabric.js 自带的 Line 类实现的线条,设置 padding 属性为 5px 后,它的上下、左右内边距都是 5px。原点在线条的左上角。

fabricjs 自带的.png

期望

产品的期望是要和 app 端实现的一样:原点在控制框的左上角,上下有 5px 的内边距。左右内边距为 0

实现

如果你熟悉 Fabric.js,你会如何实现,先不看我的实现,你可以自己想想实现方式。

第一版方案

第一版的方案证明是失败的,实现复杂,而且遇到旋转后,就变得混乱了。

它的思路是这样的:

1、自定义 CustomLine 类型,继承自 fabric.Group

2、组内由三部分组成:红色的 fabric.Rectfabric.Line 和绿色的 fabric.Rect

第一版方案.png

坑点

1、改变线条宽高时,需要同时改变两个矩形的位置和宽高。

2、addWithUpdate 很神奇,会改变组的 lefttop

3、旋转后,在放大缩小会很复杂,因为要考虑矩形的旋转、宽高、位置的计算(我放弃了)。

第二版方案

这个方案,目前看起来是符合要求的,可能有些场景我还没考虑到。

思路是这样:

1、自定义 CustomLine 类型,继承自 fabric.Line

2、覆盖掉原 Line 类的 _setWidthHeight, _render, _getNonTransformedDimensions

具体代码如下:

fabric.CustomLine = fabric.util.createClass(fabric.Line, {
  type: "CustomLine",
  _fixedPadding: 5,
  initialize: function (points, options) {
    options || (options = {});
    this.callSuper("initialize", points, options);
  },
  _setWidthHeight: function (options) {
    options || (options = {});
    this.width = Math.abs(this.x2 - this.x1);
    this.height = Math.abs(this.y2 - this.y1) + this._fixedPadding * 2; // 增加高度,为内边距留空间
    this.left = "left" in options ? options.left : this._getLeftToOriginX();
    this.top = "top" in options ? options.top : this._getTopToOriginY();
  },
  _render: function (ctx) {
    ctx.beginPath();
    let p = this.calcLinePoints();
    ctx.moveTo(p.x1, p.y1 + this._fixedPadding); // 开始点,y 移动到控制框的中间
    ctx.lineTo(p.x2, p.y2 - this._fixedPadding); // 结束点,y 移动到控制框的中间
    ctx.lineWidth = this.strokeWidth;
    // TODO: test this
    // make sure setting "fill" changes color of a line
    // (by copying fillStyle to strokeStyle, since line is stroked, not filled)
    var origStrokeStyle = ctx.strokeStyle;
    ctx.strokeStyle = this.stroke || ctx.fillStyle;
    this.stroke && this._renderStroke(ctx);
    ctx.strokeStyle = origStrokeStyle;
  },
  _getNonTransformedDimensions: function () {
    let dim = this.callSuper("_getNonTransformedDimensions");
    if (this.strokeLineCap === "butt") {
      if (this.width === 0) {
        dim.y -= this.strokeWidth;
      }

      dim.x -= this.strokeWidth; // 原本的 Line 是判断 height 为 0 再重新为 x 减去 strokeWidth 的值,这是关键点。
    }
    return dim;
  },
});

总结

别问我是怎样总结出的。说多了都是泪。

如果这篇文章对你有用,请不吝点赞、收藏。