旋转一组包含文本的形状,同时保持文本居中和水平

这可能只是数学。


我使用 Konva 动态生成形状,并将其存储为标签。因此有一个标签包含一个 textElement 和一个矩形。我想确保该矩形中的文本始终a)水平和垂直居中,b)朝上。


因此,矩形可以进行任何旋转,但我总是希望文本居中且朝上。


创建代码;宽度、高度、旋转、x 和 y 都有从数据库中提取的值。


var table = new Konva.Label({

                x: pos_x,

                y: pos_y,

                width: tableWidth,

                height: tableHeight,

                draggable:true

              });


table.add(new Konva.Rect({

                    width: tableWidth,

                    height: tableHeight,

                    rotation: rotation,

                    fill: fillColor,

                    stroke: strokeColor,

                    strokeWidth: 4

                }));


table.add(new Konva.Text({

                width: tableWidth,

                height: tableHeight,

                x: pos_x, //Defaults to zero

                y: pos_y, //Default to zero

                text: tableNumber, 

                verticalAlign: 'middle',

                align: 'center',

                fontSize: 30,

                fontFamily: 'Calibri',

                fill: 'black'

            }))


tableLayer.add(table);

问题是,如果旋转到位,文本就会偏离中心,如下图所示:

https://img3.mukewang.com/64d496070001523d03040288.jpg

在某些情况下,我会手动更正 - 例如,如果旋转 = 45 度:

pos_x = -tableWidth/2;
pos_y = tableHeight/5;

但这并不是永久的解决办法。我希望文本的 x 和 y 坐标位于形状本身的中心点。

我尝试了几种方法(例如对标签本身应用旋转,然后对文本应用负旋转值)


慕哥6287543
浏览 117回答 0
0回答

慕工程0101907

不同之处在于需要使用更复杂的分组形状并保持该组中的某些元素不旋转。不在OP的问题中,但我通过将文本作为一个组在文本中设置了一个背景矩形。这样做的目的是表明文本矩形将在某些旋转点延伸到标签矩形之外。这不是一个关键问题,但看到它发生是有用的。编码人员面临的基本挑战是理解形状在旋转时如何移动,因为我们通常希望将它们绕其中心旋转,但 Konva(以及所有 HTML5 画布包装器)遵循的基本 2D 画布模式是从左上角旋转,至少对于问题中的形状的矩形。移动旋转点(称为偏移量)是可能的,但这对开发人员来说是一个概念上的挑战,对以后尝试支持代码的任何人来说都是一个很好的陷阱。这个答案中有很多代码是为了设置一些动态的东西,你可以用它来可视化正在发生的事情。然而,症结在于: // This is the important call ! Cross is the rotation point as illustrated by crosshairs.  rotateAroundPoint(shape, rotateBy, {x: cross.x(), y: cross.y()});    // The label is a special case because we need to keep the text unrotated.  if (shape.name() === 'label'){    let text = shape.find('.text')[0];    rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});  }rotateAroundPoint() 函数将要旋转的 Konva 形状、顺时针旋转角度(不是弧度,良好的 ole 度)以及画布/父级上旋转点的 x 和 y 位置作为参数。我构建了一组形状作为我的标签,由矩形和文本形状组成。我将这个命名为“标签”。实际上,我将文本形状切换为另一组矩形+文本,这样我就可以显示文本所在的矩形。您可以省略额外的组。我将这个命名为“文本”。第一次调用rotateAroundPoint() 会旋转名为“label”的组。所以该组在画布上旋转。由于“文本”是“标签”组的子级,这将使“文本”旋转,因此下一行检查我们是否正在使用“标签”组,如果是,我们需要获取“文本”形状,这就是这一行的作用:let text = shape.find('.text')[0];在 Konva 中,find() 的结果是一个列表,因此我们采用列表中的第一个。现在我要做的就是通过将负旋转度数应用到其中心点,将“标签”组上的文本再次旋转回来。下面的代码行实现了这一点。rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});值得一提的是 - 我使用了一组作为我的“文本”形状。Konva 组自然没有宽度或高度 - 它更多的是一种将形状收集在一起的方法,但没有“物理”容器。因此,为了获取中心点计算的宽度和高度,我使用 group.getClientRect() 方法,该方法给出包含组中所有形状的最小边界框的大小,并生成一个形成为 {width: , height 的对象:}。第二个注意事项 - 第一次使用rotateAroundPoint()会影响以画布为父级的“标签”组。该函数的第二次使用会影响以“标签”组作为其父级的“文本”组。它微妙但值得了解。这是片段。我强烈建议您全屏运行它并围绕几个不同的点旋转一些形状。// 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: 'lime', strokeWidth: 1}),    lineH = new Konva.Line({points: [-20, 0,  20, 0], stroke: 'lime', strokeWidth: 1}),    circle = new Konva.Circle({x: 0, y: 0, radius: 10, fill: 'transparent', stroke: 'lime', strokeWidth: 1}),    cross = new Konva.Group({draggable: true, x: startPos.x, y: startPos.y}),    labelRect, labelText;// Add the elements to the cross-hair groupcross.add(lineV, lineH, circle);layer.add(cross);// Add the layer to the stagestage.add(layer);$('#shapeName').on('change', function(){  shapeName = $('#shapeName').val();  shape.destroy();  shape = null;  reset();})// Draw whatever shape the user selectedfunction 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;                       case "label":         shape = new Konva.Group({name: 'label'});        labelRect = new Konva.Rect({x: 0, y: 0, width: 120, height: 80, fill: 'magenta', stroke: 'black', strokeWidth: 4, name: 'rect'})        shape.add(labelRect);        labelText = new Konva.Group({name: 'text'});        labelText.add(new Konva.Rect({x: 0, y: 0, width: 100, height: 40, fill: 'cyan', stroke: 'black', strokeWidth: 2}))        labelText.add(new Konva.Text({x: 0, y: 0, width: 100, height: 40, text: 'Wombat',fontSize: 20, fontFamily: 'Calibri', align: 'center', padding: 10}))                shape.add(labelText)                labelText.position({x: (labelRect.width() - labelText.getClientRect().width) /2, y:  (labelRect.height() - labelText.getClientRect().height) /2})                                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 pointstage.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()});    // The label is a special case because we need to keep the text unrotated.  if (shape.name() === 'label'){    let text = shape.find('.text')[0];    rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});  }    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 shapesfunction 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 & repeat</p><p><button id = 'rotate'>Rotate</button>  <button id = 'reset'>Reset</button>   <select id='shapeName'>    <option value='label' selected='selected'>Label</option>    <option value='rectangle'>Rectangle</option>    <option value='hexagon'>Polygon</option>    <option value='ellipse' >Ellipse</option>    <option value='circle' >Circle</option>    <option value='star'>Star</option>      </select>  Angle :    <span id='angle'>0</span>Position :   <span id='position'></span></p><div id="container"></div>
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

JavaScript