就涉及旋转而言,公式非常简单:
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>