Абсолютное позиционирование испорчено вращением CSS

Я сделал крошечный пример ниже, демонстрирующий поведение, которое я получаю в настоящее время, и поведение, которое я хочу.

// Rotated div
rotated.style.left = "50px";
rotated.style.top = "100px";
// Original "untouched" div
original.style.left = "50px";
original.style.top = "100px";
// Where the rotated div *should* be
expected.style.left = "-10px";
expected.style.top = "160px";
div {
  position: absolute;
  height: 80px;
  width: 200px;
  opacity: 0.5;
  mix-blend-mode: overlay;
}

#rotated {
  transform: rotateZ(90deg);
  background: blue;
}

#original {
  background: red;
}

#expected {
  transform: rotateZ(90deg);
  background: green;
}
<div id = "rotated"></div>
<div id = "original"></div>
<div id = "expected"></div>

Красный div — это «исходный» div, к которому я не применял никаких преобразований. Синий div повернут на 90 градусов. Красный и синий элементы div сдвинуты на одинаковые значения, но их углы явно не совпадают. Зеленый div — это ожидаемая (желаемая) позиция синего div.

Как видите, left и top на самом деле не работают так, как хотелось бы. Я понимаю, почему это не так, но я ищу некоторые решения или обходные пути. Я искал в Интернете и нашел свойство transform-origin, но у меня возникли некоторые проблемы с его использованием. Это потому, что элементы, которые я хочу переместить, создаются динамически. У них неизвестная ширина и высота, и, кроме того, ширина и высота позже изменятся!

Я знаю, что для этого статического примера я могу просто добавить transform-origin: 40px 40px; к (это просто высота / 2 дважды) div#rotated, и это сработает, но в моем проекте это означает, что мне придется устанавливать это свойство для каждого элемента и обновлять его каждый раз. время, когда я обновляю размеры элемента.

Я просто не думаю, что это так здорово, и я ищу одно из двух возможных решений:

  • Решение на чистом CSS, которое каким-то образом получает высоту выбранного элемента и использует его в качестве источника преобразования (или просто любое работающее решение на чистом CSS).

  • Использование JavaScript для вычисления исправленной позиции (в этом статическом примере я должен получить -10, 160 как позицию элемента) каждый раз, когда я хочу переместить элемент.

--- обновлять ---

Эта проблема еще более усложняется, потому что, если поворот составляет 180 градусов или 270 градусов, то transform-origin из 40px 40px больше не работает. Мне пришлось бы вычислять новое начало преобразования каждый раз, когда я хочу переместить элемент... Это то, чего я действительно хотел бы избежать...

Поведение ключевого слова "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
57
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы можете добавить перевод в ожидаемое преобразование

.wrapper {
  position: relative;
  margin: 200px 0 0 200px;
  width: 200px;
  height: 200px;
  border: 1px solid black;
}

div {
  position: absolute;
  height: 80px;
  width: 200px;
  opacity: 0.5;
  mix-blend-mode: overlay;
}

#rotated {
  transform: rotateZ(90deg);
  background: blue;
}

#original {
  background: red;
}

#expected {
  transform: rotateZ(90deg) translateY(-100%);
  background: green;
  transform-origin: 0 0;
}
<div class = "wrapper">
  <div id = "rotated"></div>
  <div id = "original"></div>
  <div id = "expected"></div>
</div>

Укладываю все в обертку с каймой. Начало преобразования из ограничивающей рамки

Синий div должен быть там, где зеленый div. Прошу прощения, если неправильно сформулировал задачу.

vera. 10.12.2022 09:37

второй фрагмент с несколькими повернутыми преобразованиями + перевод

если я до сих пор не понял, какой именно поворот вам нужен, вы можете поиграть с параметрами translate:

  • перевестиY
  • перевестиX
  • перевести с 2 значениями

всегда проверяйте оболочку вокруг ограничивающей рамки: 0 0 относится к левому верхнему углу

.wrapper1 {
  position: relative;
  top: 100px;
  left: 100px;
  width: 200px;
  height: 200px;
  border: 1px solid black;
}

.wrapper2 {
  position: relative;
  top: 250px;
  left: 100px;
  width: 200px;
  height: 200px;
  border: 1px solid black;
}

.wrapper3 {
  position: relative;
  top: 400px;
  left: 100px;
  width: 200px;
  height: 200px;
  border: 1px solid black;
}

.wrapper4 {
  position: relative;
  top: 550px;
  left: 100px;
  width: 200px;
  height: 200px;
  border: 1px solid black;
}

div {
  position: absolute;
  height: 80px;
  width: 200px;
  opacity: 0.5;
  mix-blend-mode: overlay;
}

.rotated1 {
  transform: rotateZ(90deg);
  background: blue;
}

.rotated2 {
  transform: rotateZ(90deg) translateY(-100%);
  transform-origin: 0 0;
  background: blue;
}

.rotated3 {
  transform: rotateZ(90deg) translateY(-50%);
  transform-origin: 0 0;
  background: blue;
}

.rotated4 {
  transform: rotateZ(90deg) translate(-50%, -50%);
  transform-origin: 0 0;
  background: blue;
}

.original {
  background: red;
}
<div class = "wrapper1">
  <div class = "rotated1"></div>
  <div class = "original"></div>
</div>
<div class = "wrapper2">
  <div class = "rotated2"></div>
  <div class = "original"></div>
</div>

<div class = "wrapper3">
  <div class = "rotated3"></div>
  <div class = "original"></div>
</div>
<div class = "wrapper4">
  <div class = "rotated4"></div>
  <div class = "original"></div>
</div>
Ответ принят как подходящий

На данный момент я решил это, вычислив правильный transform-origin из угла поворота (который всегда равен 0, 90, 180 или 270). Код на TypeScript:

export function computeTransformOrigin(element: HTMLElement) {
    const { width, height, transform } = getComputedStyle(element);

    if (transform && transform !== "none") {
        const values = transform.match(/^matrix\((.+)\)$/)?.[1].split(", ");

        if (values) {
            element.style.translate = "";

            const [a, b] = values.map(Number);

            const angle = (Math.round(Math.atan2(b, a) * (180 / Math.PI)) + 360) % 360;

            if (angle === 0 || angle === 90) return parseFloat(height) / 2 + "px " + parseFloat(height) / 2 + "px";

            if (angle === 180) return "center";

            element.style.translate = "0 " + (parseFloat(width) - parseFloat(height)) + "px";

            return parseFloat(height) / 2 + "px " + parseFloat(height) / 2 + "px";
        }
    }

    return "center";
}

Для отсутствия поворота или 90 градусов мы можем получить начало преобразования как высоту элемента, деленную на 2 (40px 40px). При повороте на 180 градусов мы используем center, а если это 270, нам нужно проделать дополнительную магию. Начало преобразования такое же, как 90 градусов, но мы также должны перевести элемент вниз на ширину минус высоту.

Затем, когда я обновляю угол для элемента, мне нужно только обновить начало преобразования в конце:

     set angle(v: number) {
        this.#angle = v % 360;

        this.element.style.transform = `rotateZ(${v}deg)`;

        if (v === 180) {
            this.name.style.transform = `rotateZ(${v}deg)`;
        } else {
            this.name.style.transform = "";
        }

        this.element.style.transformOrigin = computeTransformOrigin(this.element);
    }

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