如何将一个元素附加到另一个旋转和定位的元素,同时保持其在屏幕上的位置?

问题描述 投票:0回答:1
javascript html geometry
1个回答
0
投票

就涉及旋转而言,公式非常简单:

newBlueRotation = originalBlueRotation - redRotation - greenRotation;

然而,对于元素的新位置来说,却出乎意料地复杂。这是因为每次旋转都是围绕与用于定位的参考点不同的中心进行的。中心是 默认情况下 div 的中心,但可以使用更改

transform-origin
CSS 属性。

因此,为了找到

element
的实际旋转中心,可以使用:

window.getComputedStyle(element).transformOrigin

旋转中心和参考点之间的差异意味着,为了计算沿线包含的元素的位置,对于每个包含的元素,必须从参考点平移到中心,应用旋转,然后平移回来到参考点。

例如,只有一个旋转矩形的参考点,即红色矩形 通过这个操作找到(我希望这个伪代码符号是不言自明的):

redReferencePoint = redOffset + redCenter - redCenter * rotate(-red)

偏移量是元素相对于其父元素的位置(均假设 绝对定位以便计算工作)。

对红色->绿色->新蓝色元素顺序应用这些计算, 设定新的蓝色元素的位置与原来的蓝色元素的位置相同的条件,就可以得到一个可以解出新的蓝色偏移量的方程,这个方程是巨大的:

newBlueOffset = greenCenter - blueCenter +
( blueOffset + blueCenter - redOffset - redCenter - 
    (greenOffset - redCenter + greenCenter) * rotate(-red)
) * rotate(red+green)

复杂的计算也导致结果为 稍微不精确,可能是由于舍入错误 - 有时 蓝色元素的微小“子像素”重新定位,

也许在代码中可以更好地看到公式;此片段中的一些代码来自问题中的代码笔。

const appendBlueToGreen = () => {
   // Get the elements
   const blueElement = document.getElementById('blue');
   const greenElement = document.getElementById('green');
   const redElement = document.getElementById('red');

   // Function to convert degree to radian
   const radToDeg = (deg) => (deg * 180) / Math.PI;

   // get the rotation center
   const getCenter = element => {
      try{
         const [x, y] = window.getComputedStyle(element).transformOrigin.split(' ').map(parseFloat);
         return {x, y};
      }
      catch(err){
         // default is element center
         return {x: element.offsetWidth / 2, y: element.offsetHeight / 2};
      }
   }

   // Function to get the rotation angle of an element (in radians)
   const getRotation = (element) => {
      const transform = window.getComputedStyle(element).transform;
      const matrix = transform.match(/^matrix\((.+)\)$/);
      if (matrix) {
         const values = matrix[1].split(', ');
         const a = values[0];
         const b = values[1];
         return Math.atan2(b, a);
      } else {
         return 0;
      }
   };

   const getOffset = (element) => {
      //return {x: parseFloat(window.getComputedStyle(element).left),
      //   y: parseFloat(window.getComputedStyle(element).top)};
      return {x: element.offsetLeft, y: element.offsetTop};
   }

   const rotateVec = ({x, y}, theta) => { // rotate vector {x, y} by theta clockwise
      const cosTheta = Math.cos(theta),
         sinTheta = Math.sin(theta);
      return {x: x * cosTheta + y * sinTheta, y: -x * sinTheta + y * cosTheta};
   };

   const addVec = ({x: x1, y: y1}, {x: x2, y: y2}, ...rest) => {
      const sum12 = {x: x1 + x2, y: y1 + y2};
      return rest.length === 0 ? sum12 : addVec(sum12, ...rest);
   }

   const negVec = ({x, y}) => ({x: -x, y: -y});

   const roundToFixed = (x, digits) => x.toFixed(digits).replace(/\.?0+$/, '');

   // Calculate the rotations
   const redRotation = getRotation(redElement);
   const greenRotation = getRotation(greenElement);
   const blueRotation = getRotation(blueElement);

   // calculate the offset positions of elements wrt their parents
   const redOffset = getOffset(redElement);
   const greenOffset = getOffset(greenElement);
   const blueOffset = getOffset(blueElement);

   // calculate the centers
   const redCenter = getCenter(redElement);
   const greenCenter = getCenter(greenElement);
   const blueCenter = getCenter(blueElement);

   let newOffset = addVec(negVec(greenOffset), negVec(greenCenter), redCenter);
   newOffset = rotateVec(newOffset, -redRotation);
   newOffset = addVec(newOffset, blueOffset, blueCenter, negVec(redOffset), negVec(redCenter));
   newOffset = rotateVec(newOffset, greenRotation + redRotation);
   newOffset = addVec(newOffset, greenCenter, negVec(blueCenter));

   // Set the new position and rotation for the blue element
   blueElement.style.position = 'absolute';
   blueElement.style.left = `${roundToFixed(newOffset.x, 2)}px`;
   blueElement.style.top = `${roundToFixed(newOffset.y, 2)}px`;


   const newBlueRotation = roundToFixed(radToDeg(blueRotation - redRotation - greenRotation), 2);
   blueElement.style.transform = `rotate(${newBlueRotation}deg)`;

   // Append the blue element to the green element
   greenElement.appendChild(blueElement);

   console.log(blueElement.style.left, blueElement.style.top, blueElement.style.transform)

   this.event.target.setAttribute("disabled","d");
};

window.appendBlueToGreen = appendBlueToGreen;
<button onclick={appendBlueToGreen()}>Append To Green</button>
<div id="red" style="transform: rotate(20deg);width: 100px;height: 100px;position: absolute;top: 50px;left: 50px;background-color: red;">
    <div id="green" style="transform: rotate(20deg);width: 80px;height: 80px;position: absolute;top: 13px;left: 11px;background-color: green;">
    </div>
</div>
<div id="blue" style="transform: rotate(45deg);width: 50px;height: 50px;position: absolute;top: 29px;left: 313px;background-color: blue;"></div>

© www.soinside.com 2019 - 2024. All rights reserved.