KonvaJS - 在不使用偏移的情况下围绕光标旋转矩形

KonvaJS - Rotate rectangle around cursor without using offset

我正在使用 KonvaJS 将矩形拖放到预定义的槽中。有些插槽需要旋转 90 度。我在垂直旋转的插槽周围有一个点击框,因此当用户将矩形拖到该区域时,它会自动旋转 90 度(以匹配方向)。当它旋转时,它会从鼠标下方移出。这可以通过偏移来解决,但是矩形在捕捉后不会在视觉上与框对齐。这可以(可能)通过附加代码解决。

我试过旋转矩形,然后在鼠标下移动。由于用户仍在拖动它,这似乎没有按我的计划工作。

是否可以在不使用偏移的情况下强制矩形在鼠标下旋转?

这里 fiddle 显示了问题 - 可以通过将第一个变量设置为 true 来演示偏移问题。 https://jsfiddle.net/ChaseRains/1k0aqs2j/78/

var width = window.innerWidth;
var height = window.innerHeight;

var rectangleLayer = new Konva.Layer();
var holdingSlotsLayer = new Konva.Layer();
var controlLayer = new Konva.Layer();
var stage = new Konva.Stage({
  container: 'container',
  width: width,
  height: height,
  draggable: true
});
//vertical holding spot
holdingSlotsLayer.add(new Konva.Rect({
  x: 300,
  y: 25,
  width: 130,
  height: 25,
  fill: '#fff',
  draggable: false,
  rotation: 90,
  stroke: '#000'
}));

//horizontal holding spot
holdingSlotsLayer.add(new Konva.Rect({
  x: 25,
  y: 75,
  width: 130,
  height: 25,
  fill: '#fff',
  draggable: false,
  rotation: 0,
  stroke: '#000'
}));

//mask to set boundaries around where we wannt to flip the rectangle
controlLayer.add(new Konva.Rect({
  x: 215,
  y: 15,
  width: 150,
  height: 150,
  fill: '#fff',
  draggable: false,
  name: 'A',
  opacity: 0.5
}));
stage.add(holdingSlotsLayer, controlLayer);

//function for finding intersections
function haveIntersection(placeHolder, rectangle, zone) {
  if (rectangle.rotation == 0 || zone == true) {
    return !(
      rectangle.x > placeHolder.x + placeHolder.width ||
      rectangle.x + rectangle.width < placeHolder.x ||
      rectangle.y > placeHolder.y + placeHolder.height ||
      rectangle.y + rectangle.height < placeHolder.y
    );
  } else {
    return !(
      rectangle.x > placeHolder.x + 25 ||
      rectangle.x + rectangle.width < placeHolder.x ||
      rectangle.y > placeHolder.y + placeHolder.height + 90 ||
      rectangle.y + rectangle.height < placeHolder.y
    );
  }
}

//function to create rectangle group (so we can place text on the rectangle)
function spawnRectangle(angle) {
  var rectangleGroup = new Konva.Group({
    x: 95,
    y: 95,
    width: 130,
    height: 25,
    rotation: angle,
    draggable: true,
  });

  rectangleGroup.add(new Konva.Rect({
    width: 130,
    height: 25,
    fill: 'lightblue'
  }));

  rectangleGroup.add(new Konva.Text({
    text: '123',
    fontSize: 18,
    fontFamily: 'Calibri',
    fill: '#000',
    width: 130,
    padding: 5,
    align: 'center'
  }));

  //function tied to an on drag move event
  rectangleGroup.on('dragmove', (e) => {
    //shrink rectangle hitbox for use in placeholder intersection
    var dimensions = {
      "height": 3,
      "width": 5,
      "x": e.target.attrs.x,
      "y": e.target.attrs.y,
      'rotation': e.target.attrs.rotation
    };
    //loop over holding slots to see if there is an intersection.
    for (var i = 0; holdingSlotsLayer.children.length > i; i++) {
      //if true, change the look of the slot we are hovering
      if (haveIntersection(holdingSlotsLayer.children[i].attrs, dimensions, false)) {
        holdingSlotsLayer.children[i].attrs.fill = '#C41230';
        holdingSlotsLayer.children[i].attrs.dash = [10, 3];
        holdingSlotsLayer.children[i].attrs.stroke = '#000';
        //set attributes back to normal otherwise
      } else {
        holdingSlotsLayer.children[i].attrs.fill = '#fff';
        holdingSlotsLayer.children[i].attrs.dash = null;
        holdingSlotsLayer.children[i].attrs.stroke = null;
      }
    }

    //check to see if we are in a zone that requires the rectangle to be flipped 90 degrees 
    if (haveIntersection(controlLayer.children[0].attrs, dimensions, true)) {
      if (rectangleGroup.attrs.rotation != 90) {
        rectangleGroup.attrs.rotation = 90;
      }
    } else {
      rectangleGroup.attrs.rotation = 0;
    }

    stage.batchDraw();
  });

  rectangleGroup.on('dragend', (e) => {

    for (var i = 0; holdingSlotsLayer.children.length > i; i++) {
      //If the parking layer has an element that is lit up, then snap to position.. 
      if (holdingSlotsLayer.children[i].attrs.fill == '#C41230') {
        rectangleGroup.position({
          x: holdingSlotsLayer.children[i].attrs.x,
          y: holdingSlotsLayer.children[i].attrs.y
        });
        holdingSlotsLayer.children[i].attrs.fill = '#fff';
        holdingSlotsLayer.children[i].attrs.dash = null;
        holdingSlotsLayer.children[i].attrs.stroke = null;
      }
    }

    stage.batchDraw();
  });


  rectangleLayer.add(rectangleGroup);
  stage.add(rectangleLayer);
}
body {
  margin: 0;
  padding: 0;
  overflow: hidden;
  background-color: #D3D3D3;
  background-size: cover;
}

#desc {
  position: absolute;
  top: 5px;
  left: 5px;
}
<script src="https://unpkg.com/konva@4.0.18/konva.min.js"></script>

<body>
  <div id="container"></div>
  <div id="desc">
    <button onclick="spawnRectangle(0)">spawn rectangle</button>
  </div>
</body>

这是一个简单的函数,可以在不使用 konva offset() 的情况下旋转鼠标下的矩形。我使用补间来应用运动,但如果你更喜欢在没有补间的情况下使用它,只需应用 rect.rotate() 然后应用 newPos x & y 作为位置。

编辑:OP 指出,如果您在矩形完成动画时单击并按住鼠标,然后拖动,则矩形会跳开。是什么赋予了 ?好吧,当 mousedown 事件 运行s Konva 在其内部拖动函数中记下形状的初始位置。然后,当我们开始实际拖动鼠标时,Konva 尽职尽责地在它计算的位置重新绘制形状。现在,'we' 知道我们在代码中移动了形状,但我们没有让 Konva 参与我们的把戏。

修复方法是调用

 rect.stopDrag(); 
 rect.startDrag();

在设置新位置后立即。因为我正在使用补间,所以我在其中一个补间的 onFinish() 回调函数中执行此操作 - 如果您应用多个补间,您可能希望确保它是最终补间。我逃脱了,因为我的补间 运行 在同一时期。如果您不使用补间,只需在形状上应用最后一次 rotate() 或 position() 调用后立即调用上述方法。

function rotateUnderMouse(){


  // Get the stage position of the mouse
  var mousePos = stage.getPointerPosition();

  // get the stage position of the mouse
  var shapePos = rect.position();

  // compute the vector for the difference
  var rel = {x: mousePos.x - shapePos.x, y: mousePos.y - shapePos.y} 

  // Now apply the rotation
  angle = angle + 90;


  // and reposition the shape to keep the same point in the shape under the mouse 
  var newPos = ({x: mousePos.x  + rel.y , y: mousePos.y - rel.x}) 

  // Just for fun, a tween to apply the move: See https://konvajs.org/docs/tweens/Linear_Easing.html
  var tween1 = new Konva.Tween({
    node: rect,
    duration: 0.25,
    x:  newPos.x,
    y: newPos.y,
    easing: Konva.Easings.Linear,
    onFinish: function() { rect.stopDrag(); rect.startDrag();}
  });
  
  // and a tween to apply the rotation 
  tween2 = new Konva.Tween({
    node: rect,
    duration: 0.25,
    rotation: angle,
    easing: Konva.Easings.Linear
  });
    
  
  tween2.play();
  tween1.play();


  
}


function setup() {

// Set up a stage and a shape
stage = new Konva.Stage({
  container: 'canvas-container',
  width: 650,
  height: 300
});


layer = new Konva.Layer();
stage.add(layer);

newPos = {x: 80, y: 40};
rect = new Konva.Rect({
   width: 140, height: 50, x: newPos.x, y: newPos.y, draggable: true, stroke: 'cyan', fill: 'cyan'
  })
layer.add(rect);

stage.draw()

rect.on('mousedown', function(){
  rotateUnderMouse()
})

}

var stage, layer, rect, angle = 0;

setup()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>

<p>Click the rectangle - it will rotate under the mouse.</p>

<div id="canvas-container"></div>