如何通过点击任意点选择/拖动来旋转Fabrijs中的线?

2022-04-07 00:00:00 javascript fabricjs

我有一条线,其一端位于用户可以旋转或拉伸的固定点。

jsfiddlehere

似乎在Fabric JS中,选择要旋转的线/对象的唯一方法是中间的小选择框。另外,线很窄,所以很难选择。通常,用户必须在直线上拖动一个矩形选择框以将其选中,然后抓取未标记的旋转框。

我想将其简化为:单击线上的任意位置并拖动以旋转。

想法?

thx.

代码片段:

var canvas = new fabric.Canvas("c", {stateful: true});


var line1 = new fabric.Line([ 100, 200, 330, 200 ], {
      fill: 'red',
      stroke: 'red',
      strokeWidth: 3,
      selectable: true,
      evented: false,
      minScaleLimit: 0.25,
      lockRotation: false,
      centeredRotation: false,
      centeredScaling: false,

      originX: "left",    // origin of rotation/transformation.      
      originY: "bottom",    // origin of rotation/transformation.

      lockMovementX: true,
      lockMovementY: true,
      lockScalingFlip: true,
      lockScalingX: false,
      lockScalingY: false,
      lockSkewingX: false,
      lockSkewingY: false,
      lockUniScaling: true
    });

解决方案

以下是您所需的一种方式。

的想法是,在每个scale事件中,我们将使用Fabric的内部fabric.canvas._rotateObject()旋转线,为其提供当前指针的位置。然后,立即调整线条长度以匹配比例,并将线条的比例重置为1。

就是这样,但是尽管您的示例相对容易完成(这条线是水平的),但是如果您想要初始化一条对角线,就会变得复杂得多。设想一条以[0, 0, 100, 100]为坐标的线。这将呈现一个100x100的矩形边界框。您可以旋转线条,但巨大的边框显然不是您想要的。

正因为如此,我们需要初始化直线,就像它旋转回水平位置一样,然后设置它应该具有的角度。为此,我们扩展了内置的fabric.Line类,并修改了构造函数以进行计算。而且,由于我们已经有了新类,我们还将向其中添加scale处理程序和默认选项。构造函数签名保持不变-new fabric.RotatingLine([x1, y1, x2, y2], options),其中x1, y1-固定点,x2, y2-可拖动的尖端。

最后,我们正在更改一些属性。例如,evented: false是您无法在单击时选择该行的原因。

下面是带有更多注释的代码片段,以防万一。

数据-lang="js"数据-隐藏="假"数据-控制台="真"数据-巴贝尔="假">
const canvas = new fabric.Canvas("c", {stateful: true})

fabric.RotatingLine = fabric.util.createClass(fabric.Line, {
  minLength: 50, // we need to set this thing in px now
  
  initialize: function (points, options) {
    const a = new fabric.Point(points[0], points[1])
    const b = new fabric.Point(points[2], points[3])
    // find this line's vector
    const vectorB = b.subtract(a)
    // find angle between line's vector and x axis
    let angleRad = Math.atan2(vectorB.y, vectorB.x)
    if (angleRad < 0) {
      angleRad = 2 * Math.PI + angleRad
    }
    const angleDeg = fabric.util.radiansToDegrees(angleRad)
    // find initial horizontal position by rotating the tip back
    const c = fabric.util.rotatePoint(b.clone(), a, -angleRad)
    options = options || {}
    // finally, initialize using transform points to make a horizontal line
    this.callSuper('initialize', [a.x, a.y, c.x, c.y], {
      noScaleCache: false, // false to force cache update while scaling (doesn't redraw parts of line otherwise)
      selectable: true,
      evented: true, // true because you want to select line on click
      //minScaleLimit: 0.25, // has no effect now because we're resetting scale on each scale event
      lockRotation: false,
      hasRotatingPoint: false, // to disable rotation control
      centeredRotation: false,
      centeredScaling: false,
      
      originX: "left",    // origin of rotation/transformation.      
      originY: "bottom",    // origin of rotation/transformation.
      
      lockMovementX: true,
      lockMovementY: true,
      lockScalingFlip: true,
      lockScalingX: false,
      lockScalingY: false,
      lockSkewingX: false,
      lockSkewingY: false,
      lockUniScaling: true,
      ...options,
      angle: angleDeg // note that we use the calculated angle no matter what
    })
    
    this.setControlsVisibility({
        tr: false,
        tl: false,
        bl: false,
        mt: false, // middle top disable
        mb: false, // midle bottom
        ml: false, // middle left
        mr: false, // I think you get it
    })
    
    this.on('scaling', function (e) {
      // rotate to the pointer's x,y
      this.canvas._rotateObject(e.pointer.x, e.pointer.y)
      // while _rotateObject() tries to keep left/top at initial value,
      // it sometimes fails because of rounding errors (?)
      // so we need to do it manually again
      this.set({left: this.x1, top: this.y1})
      // calculate new length before resetting scale
      const xOffset = (this.x2 - this.x1) * this.scaleX
      const newLength = Math.max(this.minLength, xOffset)
      // reset scaleX/scaleY and set new x coord for the tip point
      this.set({
        scaleX: 1,
        scaleY: 1,
        x2: this.x1 + newLength
      })
    })
  }
})


const line1 = new fabric.RotatingLine([ 200, 200, 330, 200 ], {
  fill: 'red',
  stroke: 'red',
  strokeWidth: 3,
});

const line2 = new fabric.RotatingLine([ 200, 200, 100, 100 ], {
  fill: 'blue',
  stroke: 'blue',
  strokeWidth: 3,
});

canvas.add(line1, line2)

// Disables group selection.
canvas.on('selection:created', (e) => {
  if(e.target.type === 'activeSelection') {
    canvas.discardActiveObject();
  } else {
    //do nothing
  }
})

// Keeps objects inside canvas. undos move/rotate/scale out of canvas.
canvas.on('object:modified', function (options) {
    let obj = options.target;
    let boundingRect = obj.getBoundingRect(true);
    if (boundingRect.left < 0
        || boundingRect.top < 0
        || boundingRect.left + boundingRect.width > canvas.getWidth()
        || boundingRect.top + boundingRect.height > canvas.getHeight()) {
        obj.top = obj._stateProperties.top;
        obj.left = obj._stateProperties.left;
        obj.angle = obj._stateProperties.angle;
        obj.scaleX = obj._stateProperties.scaleX;
        obj.scaleY = obj._stateProperties.scaleY;
        obj.setCoords();
        obj.saveState();
    }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.min.js"></script>
<canvas id='c' width="500" height="400"></canvas>

相关文章