Как я могу повернуть любую фигуру или точку на холсте HTML5 вокруг произвольной точки?

#javascript #konvajs

#javascript #конвайс

Вопрос:

Я видел несколько случаев вопросов о том, как повернуть фигуру вокруг заданной точки, и решил задать этот вопрос для самостоятельного ответа самому. Итак, что касается холста HTML5 или фактически любой двумерной поверхности, как я могу повернуть фигуру вокруг произвольной точки x, y ?

Ответ №1:

Оказывается, ответ довольно прост, но включает в себя немного математики, которая может отпугнуть некоторых людей. Я использую библиотеку холста Konvajs HTML5, но код легко переносится в вашу собственную библиотеку. Кроме того, этот пример описывается как поворот фигуры, но на самом деле он вращает точку — начало формы — так что вы можете использовать его для любого случая поворота точки вокруг точки.

Функция rotateAroundPoint() выполняет работу — остальной код во фрагменте предназначен для того, чтобы сделать его рабочим примером.

Извлекая эту функцию, мы можем видеть, что входными данными являются форма — хотя это может быть любой объект со свойствами x, y и rotation, углом поворота в градусах и точкой поворота — снова объект со значениями x и y.

Когда мы поворачиваем вокруг точки, мы выполняем эквивалент вращения на месте, за которым следует перевод (или перемещение). Это должно быть сделано в такой последовательности. Кроме того, из-за того, как работает 2d-рисование, мы должны определить новую позицию для перемещения, и это зависит от источника рисования фигуры.

Вычисление новых положений x и y требует использования функций синуса и косинуса, для которых требуются радианы, а не градусы. Итак, мы умножаем градусы на число ПИ / 180, чтобы получить это.

 // Rotate a shape around any point.
// shape is a Konva shape
// angleDegrees is the angle to rotate by, in degrees
// point is an object {x: posX, y: posY}
function rotateAroundPoint(shape, angleDegrees, point) {
  let angleRadians = angleDegrees * Math.PI / 180; // sin   cos require radians
  
  const x =
    point.x  
    (shape.x() - point.x) * Math.cos(angleRadians) -
    (shape.y() - point.y) * Math.sin(angleRadians);
  const y =
    point.y  
    (shape.x() - point.x) * Math.sin(angleRadians)  
    (shape.y() - point.y) * Math.cos(angleRadians);
   
  shape.rotation(shape.rotation()   angleDegrees); // rotate the shape in place
  shape.x(x);  // move the rotated shape in relation to the rotation point.
  shape.y(y);

}
  

Вот и все! Поиграйте с фрагментом, который лучше всего просматривать в полноэкранном режиме. Выберите фигуру для поворота, затем нажмите кнопку поворота несколько раз, чтобы посмотреть, как она вращается вокруг своего источника (естественной точки вращения, если мы просто изменим угол поворота и ничего больше). Затем нажмите кнопку сброса и щелкните холст, чтобы переместить синюю цель в другое место на холсте или фигуре, и поверните еще немного, чтобы увидеть эффект.

Здесь также есть версия codepen.

 // Code to illustrate rotation of a shape around any given point. The important functions here is rotateAroundPoint() which does the rotation and movement math ! 

let 
    angle = 0, // display value of angle
    startPos = {x: 80, y: 45},    
    shapes = [],    // array of shape ghosts / tails
    rotateBy = 20,  // per-step angle of rotation 
    shapeName = $('#shapeName').val(),  // what shape are we drawing
    shape = null,
    ghostLimit = 10,

    // Set up a stage
    stage = new Konva.Stage({
        container: 'container',
        width: window.innerWidth,
        height: window.innerHeight
      }),

  
    // add a layer to draw on
    layer = new Konva.Layer(),
    
    // create the rotation target point cross-hair marker
    lineV = new Konva.Line({points: [0, -20, 0, 20], stroke: 'cyan', strokeWidth: 1}),
    lineH = new Konva.Line({points: [-20, 0,  20, 0], stroke: 'cyan', strokeWidth: 1}),
    circle = new Konva.Circle({x: 0, y: 0, radius: 10, fill: 'transparent', stroke: 'cyan', strokeWidth: 1}),
    cross = new Konva.Group({draggable: true, x: startPos.x, y: startPos.y});

// Add the elements to the cross-hair group
cross.add(lineV, lineH, circle);
layer.add(cross);

// Add the layer to the stage
stage.add(layer);


$('#shapeName').on('change', function(){
  shapeName = $('#shapeName').val();
  shape.destroy();
  shape = null;
  reset();
})


// Draw whatever shape the user selected
function drawShape(){
  
    // Add a shape to rotate
    if (shape !== null){
      shape.destroy();
    }
   
    switch (shapeName){
      case "rectangle":
        shape = new Konva.Rect({x: startPos.x, y: startPos.y, width: 120, height: 80, fill: 'magenta', stroke: 'black', strokeWidth: 4});
        break;

      case "hexagon":
        shape = new Konva.RegularPolygon({x: startPos.x, y: startPos.y, sides: 6, radius: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4}); 
        break;
        
      case "ellipse":
        shape = new Konva.Ellipse({x: startPos.x, y: startPos.y, radiusX: 40, radiusY: 20, fill: 'magenta', stroke: 'black', strokeWidth: 4});     
        break;
        
      case "circle":
        shape = new Konva.Ellipse({x: startPos.x, y: startPos.y, radiusX: 40, radiusY: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});     
        break;

      case "star":
        shape = new Konva.Star({x: startPos.x, y: startPos.y, numPoints: 5, innerRadius: 20, outerRadius: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});     
        break;        
    };
    layer.add(shape);
    
    cross.moveToTop();

  }



// Reset the shape position etc.
function reset(){

  drawShape();  // draw the current shape
  
  // Set to starting position, etc.
  shape.position(startPos)
  cross.position(startPos);
  angle = 0;
  $('#angle').html(angle);
  $('#position').html('('   shape.x()   ', '   shape.y()   ')');
  
  clearTails(); // clear the tail shapes
  
  stage.draw();  // refresh / draw the stage.
}




// Click the stage to move the rotation point
stage.on('click', function (e) {
  cross.position(stage.getPointerPosition());
  stage.draw();
});

// Rotate a shape around any point.
// shape is a Konva shape
// angleRadians is the angle to rotate by, in radians
// point is an object {x: posX, y: posY}
function rotateAroundPoint(shape, angleDegrees, point) {
  let angleRadians = angleDegrees * Math.PI / 180; // sin   cos require radians
  
  const x =
    point.x  
    (shape.x() - point.x) * Math.cos(angleRadians) -
    (shape.y() - point.y) * Math.sin(angleRadians);
  const y =
    point.y  
    (shape.x() - point.x) * Math.sin(angleRadians)  
    (shape.y() - point.y) * Math.cos(angleRadians);
   
  shape.rotation(shape.rotation()   angleDegrees); // rotate the shape in place
  shape.x(x);  // move the rotated shape in relation to the rotation point.
  shape.y(y);
  
  shape.moveToTop(); // 
}



$('#rotate').on('click', function(){
  
  let newShape = shape.clone();
  shapes.push(newShape);
  layer.add(newShape);
  
  // This ghost / tails stuff is just for fun.
  if (shapes.length >= ghostLimit){
    shapes[0].destroy();   
    shapes = shapes.slice(1);
  }
  for (var i = shapes.length - 1; i >= 0; i--){
    shapes[i].opacity((i   1) * (1/(shapes.length   2)))
  };

  // This is the important call ! Cross is the rotation point as illustrated by crosshairs.
  rotateAroundPoint(shape, rotateBy, {x: cross.x(), y: cross.y()});
  
  cross.moveToTop();
  
  stage.draw();
  
  angle = angle   10;
  $('#angle').html(angle);
  $('#position').html('('   Math.round(shape.x() * 10) / 10   ', '   Math.round(shape.y() * 10) / 10   ')');
})



// Function to clear the ghost / tail shapes
function clearTails(){

  for (var i = shapes.length - 1; i >= 0; i--){
    shapes[i].destroy();
  };
  shapes = [];
  
}

// User cicks the reset button.
$('#reset').on('click', function(){

  reset();

})

// Force first draw!
reset();  
 body {
  margin: 10;
  padding: 10;
  overflow: hidden;
  background-color: #f0f0f0;
}  
 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva@^3/konva.min.js"></script>
<p>1. Click the rotate button to see what happens when rotating around shape origin.</p>
<p>2. Reset then click stage to move rotation point and click rotate button again - rinse amp; repeat</p>
<p>
<button id = 'rotate'>Rotate</button>
  <button id = 'reset'>Reset</button> 
  <select id='shapeName'>
    <option value='rectangle'>Rectangle</option>
    <option value='hexagon'>Polygon</option>
    <option value='ellipse' >Ellipse</option>
    <option value='circle' >Circle</option>
    <option value='star' selected='selected'>Star</option>    
    
  </select>
  Angle :    <span id='angle'>0</span>
Position :   <span id='position'></span>
</p>
<div id="container"></div>