Контекст
Я пытаюсь обрабатывать геометрические операции, применяемые к элементам HTML с помощью 3D-преобразований CSS3 через API DOMMatrix, чтобы получить любую координату, которую я хочу, в любой системе координат, которую я хочу.
Проблема
С 2D-преобразованиями все работает нормально (перемещение, поворот, масштабирование, наклон), но когда я начинаю работать в 3D, например, с преобразованием «перспектива» или преобразованием «rotateX», тогда ничего больше не работает.
И я не очень понимаю, почему, потому что на самом деле все должно обрабатываться операциями DOMMatrix, такими как последовательное умножение и т. д.
Небольшой пример
// 2D working transforms --->
//const selfTransform = 'rotate(25deg) skewY(50deg) scale(0.6)';
// const selfTransform = 'rotate(45deg) skewX(50deg) scale(0.6)';
// <---
// 3D not working transforms --->
const selfTransform = 'perspective(500px) rotateX(60deg) rotateY(15deg)';
// <---
const DOM_a = document.getElementById('a');
DOM_a.style.transform = selfTransform;
// Generate the transform string
const transform =
// Translate to #a 0, 0
'translate3d(100px, 100px, 0px) '
// Translate to #a center, center
+ 'translate3d(100px, 100px, 0px) '
// Apply #a transform
+ selfTransform
// Translate back to #a 0, 0
+ 'translate3d(-100px, -100px, 0px)';
// Create the 3d projective transform matrix
const tm = new DOMMatrix(transform);
// Create the point representing the #a bottom left point coords in #A landmark
const blCoordsInA = new DOMPoint(
0,
200
);
// Find the #a bottom left point coords in the window landmrk by applying the transform
const blCoordsInWindow = blCoordsInA.matrixTransform(tm);
console.info(blCoordsInWindow);
const blLeft = blCoordsInWindow.x;
const blTop = blCoordsInWindow.y;
// Visualize the point by moving the black circle
const DOM_marker = document.getElementById('marker');
DOM_marker.style.left = blLeft + 'px';
DOM_marker.style.top = blTop + 'px';#a {
position: absolute;
top: 100px;
left: 100px;
width: 200px;
height: 200px;
background-color: red;
transform-origin: center center;
}
#marker {
position: absolute;
top: 0px;
left: 0px;
width: 6px;
height: 6px;
margin-left: -3px;
margin-top: -3px;
background-color: black;
border-radius: 50%;
}<div id = "a">
</div>
<div id = "marker">
</div>Как вы можете видеть в этом примере, я пытаюсь найти координаты нижнего левого угла красного div в «общем ориентире окна». Зная преобразование красного div и положение точки в красном div, я обычно должен быть в состоянии узнать координаты этой точки в окне, просто умножив координаты точки на матрицу преобразования, которую я генерирую.
В моем примере, если вы определяете 2D-преобразование для переменной «selfTransform», все работает так, как должно, черная точка расположена в левом нижнем углу. Но если вы установите 3D-преобразование с некоторой перспективой, оно больше не будет работать, и я не понимаю, почему.
Есть идеи ?
Поворот сюжета
В этом примере вы можете обнаружить, что некоторые трехмерные преобразования работают хорошо, например «rotateX({randomRadianValue})», но они больше не работают в тот момент, когда в дереве DOM есть накопительные преобразования, я имею в виду, если я хочу получить координаты точка, которая находится в div, которая также находится в дереве, где другие div имеют 3d-преобразования, все результаты испорчены. Но если я использую только 2D-преобразования, то все работает идеально, независимо от глубины элемента в преобразованном дереве элементов...



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Одна вещь, которая мешает, это «где находятся ваши угловые точки». Если у вас есть прямоугольник 200x200 и вы вращаете его по центру, то углы (с точки зрения 3D-преобразования) не равны (0,0), (0,200), (200,200) и (200,0). но вместо этого это (-100,-100), (-100,100), (100,100) и (100,-100).
Поэтому, если мы захватим эти исходные значения преобразования, прежде чем что-либо делать, а затем обязательно введем это смещение в позицию нашей точки-маркера, все будет работать намного лучше:
// capture our "starting offset":
const [tx=0, ty=0, tz=0] = getComputedStyle(plane)[`transform-origin`]
.split(` `)
.map(parseFloat);
function redraw() {
a = angle.value;
label.textContent = a;
// Set up a transform...
const transform = `
perspective(250px)
rotateX(${2*a }deg)
rotateY(${ a/3}deg)
rotateZ(${ a}deg)
`;
// And get the matrix equivalent.
const M = new DOMMatrix(transform);
// Apply the transform to our plane...
plane.style.transform = transform;
// Draw all four corner points
document.querySelectorAll(`.point`).forEach(p => p.remove());
[[-tx, -ty], [-tx,ty], [tx,ty], [tx,-ty]].forEach(coords => {
// First, transform the 2D corner point into a 4D homogeneous coordinate:
const { x, y, z, w } = M.transformPoint(new DOMPoint(...coords, -tz));
// then create a div that we're going to place at the
// corner point's projected location:
const p = document.createElement(`div`);
p.classList.add(`point`);
content.appendChild(p);
// x and y (as well as z but we won't use that) are
// still homogeneous values, so we need to perform a
// homogeneous divide to turn them into "real" x and y,
// and we also need to remember to compensate for
// the fact that (0,0) is actually at (tx, ty).
const { style } = p;
style.setProperty(`--x`, `${x/w + tx}px`);
style.setProperty(`--y`, `${y/w + ty}px`);
});
}
angle.addEventListener(`input`, () => redraw());
redraw();body {
padding: 2em;
padding-left: 4em;
div:has(#slider) {
margin-bottom: 2em;
#slider {
position: relative;
margin-right: 14em;
#angle {
position: absolute;
width: 15em;
}
label[for = "angle"] {
position: absolute;
top: 1.5em;
left: 7em;
}
}
}
#content {
position: relative;
width: 200px;
height: 200px;
background: yellow;
#plane {
position: absolute;
width: 200px;
height: 200px;
background-color: red;
}
.point {
position: absolute;
--x: 0px;
--y: 0px;
top: calc(var(--y) - 3px);
left: calc(var(--x) - 3px);
width: 6px;
height: 6px;
background-color: black;
border-radius: 50%;
}
}
}<div>
0 degrees
<span id = "slider">
<label id = "label" for = "angle">0</label>
<input id = "angle" type = "range" min = "0" max = "360" step = "0.1" value = "25">
</span> 360 degrees
</div>
<div id = "content">
<div id = "plane"></div>
</div>Но обратите внимание, что такие вещи, как отступы и поля CSS, могут действительно мешать расположению этих точек, поэтому обычно вам нужно убедиться, что вы работаете в «стерильном» родительском контейнере по отношению к ним.
Я неправильно понял вас относительно сопоставления: ваше (0,200,0) уже было начальной координатой экрана, так что да, это умножение на обычную матрицу, а не на ее обратную. Я обновил сообщение и удалил неактуальные комментарии на основе старого ответа.
Я прекрасно понимаю ваш пример, все кажется ясным... но все же я что-то упускаю, и когда я пытаюсь получить координаты нижней правой точки в виде черного круга, изменив строку "const ttp = new DOMPoint(tx/2, ty/2 , -tz);" в "const ttp = new DOMPoint(tx, ty, -tz);" который для меня дает координаты нижнего правого угла, ничего больше не работает.. ага, я чувствую себя немного глупо, не могли бы вы потратить немного времени, чтобы объяснить мне, почему и как? может на другой платформе? Я куплю тебе кофе за проведенное время..
Кроме того, если я добавлю «translateX(250px)» (случайное значение перевода не важно) в строку преобразования перед «перспективой», ничего больше не будет работать, и это то же самое, я не понимаю, почему, потому что матрица должна быть обработка этого перевода «с моей точки зрения»
Я не думаю, что это вы: я обновил код, чтобы показать, что это работает для всех четырех угловых точек... при условии, что мы не добавим инструкцию перспективы. Как только мы это сделаем, все перестанет работать — чего не должно быть, поскольку это всего лишь фактор искажения в m43, но они работают. Я попробую посмотреть, смогу ли я это понять, потому что это просто странно.
Ух ты, глупая ошибка: я забыл деструктурировать компонент w, который является коэффициентом масштабирования, связанным с перспективой («однородный делитель»). Если вы сделаете это, а затем не забудете использовать x/w и y/w, то получите правильный результат. Я обновил код, и теперь он должен делать именно то, что вы ожидаете.
Черт возьми, спасибо вам огромное! трюк, который заставил меня работать, заключался в том, чтобы просто разделить значения каждой точки x и y на точку point.w. Я не знал об этом факторе «перспективы» непосредственно в атрибутах DOMPoint!
Учитывая преобразование div: «perspective(500px) RotateX(25deg)». И DOMMatrix, созданный с помощью «new DOMMatrix(perspective(500px) RotateX(25deg)). Я знаю, что нижний левый угол div имеет координаты: (0, 200, 0) в системе координат div. Я хочу получить координаты точки в системе координат окна, какой расчет мне следует сделать? new DOMPoint(0, 200, 0).matrixTransform(M) или new DOMPoint(0, 200, 0).matrixTransform(M.inverseSelf())? Потому что в моем коде я на самом деле использую P.matrixTransform(M), и он отлично работает с 2D-преобразованиями.