3D CSS3 меняет обработку преобразований с помощью проблемы DOMMatrix

Контекст

Я пытаюсь обрабатывать геометрические операции, применяемые к элементам 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-преобразования, то все работает идеально, независимо от глубины элемента в преобразованном дереве элементов...

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
80
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Одна вещь, которая мешает, это «где находятся ваши угловые точки». Если у вас есть прямоугольник 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, могут действительно мешать расположению этих точек, поэтому обычно вам нужно убедиться, что вы работаете в «стерильном» родительском контейнере по отношению к ним.

Учитывая преобразование 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-преобразованиями.

Xeyes 11.06.2024 14:08

Я неправильно понял вас относительно сопоставления: ваше (0,200,0) уже было начальной координатой экрана, так что да, это умножение на обычную матрицу, а не на ее обратную. Я обновил сообщение и удалил неактуальные комментарии на основе старого ответа.

Mike 'Pomax' Kamermans 11.06.2024 17:43

Я прекрасно понимаю ваш пример, все кажется ясным... но все же я что-то упускаю, и когда я пытаюсь получить координаты нижней правой точки в виде черного круга, изменив строку "const ttp = new DOMPoint(tx/2, ty/2 , -tz);" в "const ttp = new DOMPoint(tx, ty, -tz);" который для меня дает координаты нижнего правого угла, ничего больше не работает.. ага, я чувствую себя немного глупо, не могли бы вы потратить немного времени, чтобы объяснить мне, почему и как? может на другой платформе? Я куплю тебе кофе за проведенное время..

Xeyes 11.06.2024 20:15

Кроме того, если я добавлю «translateX(250px)» (случайное значение перевода не важно) в строку преобразования перед «перспективой», ничего больше не будет работать, и это то же самое, я не понимаю, почему, потому что матрица должна быть обработка этого перевода «с моей точки зрения»

Xeyes 11.06.2024 20:30

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

Mike 'Pomax' Kamermans 11.06.2024 20:53

Ух ты, глупая ошибка: я забыл деструктурировать компонент w, который является коэффициентом масштабирования, связанным с перспективой («однородный делитель»). Если вы сделаете это, а затем не забудете использовать x/w и y/w, то получите правильный результат. Я обновил код, и теперь он должен делать именно то, что вы ожидаете.

Mike 'Pomax' Kamermans 11.06.2024 23:11

Черт возьми, спасибо вам огромное! трюк, который заставил меня работать, заключался в том, чтобы просто разделить значения каждой точки x и y на точку point.w. Я не знал об этом факторе «перспективы» непосредственно в атрибутах DOMPoint!

Xeyes 12.06.2024 10:14

Другие вопросы по теме