Матрица CSS трансформаций

Источник: «CSS Transforms and the Matrix»
Глубокое погружение в матрицу CSS трансформаций, её взаимосвязь с отдельными свойствами CSS трансформаций, взгляд на скрытые возможности форм функций.

Краткий обзор

До появления индивидуальных свойств CSS трансформации использовали свойство transform для применения нескольких преобразований к элементу. Это выглядело так:

svg {
transform: translate(0, 25rem) rotate(180deg) scale(0.5);
}

Теперь этого можно сделать с помощью следующего CSS:

svg {
translate: 0 25rem;
rotate: 180deg;
scale: 0.5;
}

Хотя на первый взгляд это верно, есть некоторые нюансы, о которых необходимо знать. В формах функций этих свойств есть скрытые силы, о которых стоит упомянуть. Однако прежде чем углубиться в этот вопрос, необходимо заглянуть в матрицу.

Матрица CSS трансформаций

CSS Transforms Module определяет матрицу CSS преобразований следующим образом (цитата из спецификации):

  1. Начинается с единичной матрицы.
  2. Сдвиг на вычисленные значения X, Y и Z для transform-origin.
  3. Сдвиг на вычисленные значения X, Y и Z для translate.
  4. Поворот на вычисленный <angle> вокруг указанной оси rotate.
  5. Масштабирование по вычисленным значениям X, Y и Z для scale.
  6. Сдвиг и поворот с помощью преобразования, заданного offset.
  7. Умножение на каждую из функций преобразования в transform слева направо.
  8. Сдвиг по отрицательным вычисленным значениям X, Y и Z transform-origin.

Рассмотрим каждый из пунктов последовательно.

Начинается с единичной матрицы

Единичная матрица — это квадратная матрица, в которой все элементы главной диагонали — единицы, а все остальные элементы — нули.

И что теперь? Я тоже так отреагировал, когда прочитал это в первый раз. Если захотите покопаться в математике, лежащей в основе единичной матрицы, смело начинайте с этой статьи из Википедии. Для наших целей, в контексте матрицы CSS трансформации, это означает, что элемент никак не трансформируется.

<div class="square"></div>

Сдвиг на вычисленные значения X, Y и Z для transform-origin

Если не указано свойство transform-origin, по умолчанию используется значение 50% 50% 0. Это означает, что элемент трансформируется вокруг своего центра. Если указать свойство transform-origin, элемент будет трансформироваться вокруг этой точки.

.square {
background-color: rebeccapurple;
block-size: 5rem;
inline-size: 5rem;
animation: spin 1s linear infinite;
}

.transform-top-left {
transform-origin: top left; /* идентично transform-origin: 0% 0%; */
}

@keyframes spin {
from {
rotate: 0deg;
}

to {
rotate: 360deg;
}
}

Трансформируется вокруг центра по умолчанию:

<div class="square"></div>

Трансформируется вокруг левого верхнего угла:

<div class="square transform-top-left"></div>

See the Pen

Сдвиг на вычисленные значения X, Y и Z для translate

Это позволит переместить элемент по осям X, Y, Z или по комбинации всех трёх.

.static-square,
.square
{
background-color: hotpink;
block-size: 5rem;
display: grid;
font-size: 1.3rem;
inline-size: 5rem;
place-items: center;
}

.square {
background-color: rebeccapurple;
block-size: 5rem;
color: #fff;
inline-size: 5rem;
translate: 1rem 2rem;
}

.transform-top-left {
transform-origin: top left; /* идентично transform-origin: 0% 0%; */
}

See the Pen

Поворот на вычисленный <angle> вокруг указанной оси rotate

Как следует из названия, это позволит повернуть элемент на указанный угол.

.static-square,
.square
{
background-color: hotpink;
block-size: 5rem;
inline-size: 5rem;
}

.square {
background-color: rebeccapurple;
rotate: 135deg;
}

.transform-top-left {
transform-origin: top left; /* идентично transform-origin: 0% 0%; */
}

See the Pen

Масштабирование по вычисленным значениям X, Y и Z для scale

Изменит масштаб элемента на указанный коэффициент. Хотя можно выполнять неравномерное масштабирование, сосредоточимся на равномерном масштабировании.

.static-square,
.square
{
background-color: hotpink;
block-size: 5rem;
inline-size: 5rem;
}

.square {
background-color: rebeccapurple;
block-size: 5rem;
inline-size: 5rem;
scale: 2;
}

.transform-top-left {
transform-origin: top left; /* идентично transform-origin: 0% 0%; */
}

See the Pen

Умножение на каждую из функций преобразования в transform слева направо

Пропустим шестой пункт. Не то чтобы он был неактуален или не важен, но в наших целях он добавляет немного шума без особой пользы. Вышеприведённое утверждение гласит, что каждое преобразование применяется к результату предыдущего шага слева направо. Это важный момент, о котором следует помнить, когда применяется несколько преобразований к элементу. Подробнее об этом поговорим чуть позже.

transform: translate(100px, 50px) rotate(30deg);

В приведённом примере элемент сначала перемещается вправо на 100 пикселей и вниз на 50 пикселей и только потом поворачивается на 30 градусов.

Сдвиг по отрицательным вычисленным значениям X, Y и Z transform-origin

Отрицательный шаг обеспечивает применение трансформаций вокруг указанного transform-origin без появления нежелательных дополнительных смещений. Если задать собственный transform-origin, элемент будет вращаться вокруг новой точки вращения, что может привести к заметному смещению. Однако отрицательный шаг предотвращает двойное смещение, обеспечивая предсказуемое поведение трансформации.

Собираем всё воедино

Получилось немало всего, но что всё это значит практически?

Контроль над порядком трансформаций

Прежде всего, необходимо понять, что независимо от того, в каком порядке заданы отдельные свойства трансформации, они всегда будут применяться в одном и том же порядке.

  1. Применяется translate.
  2. Применяется rotate.
  3. Применяется указанный коэффициент scale.

Плюс в том, что это очень предсказуемо. Однако если конечный результат окажется не таким, как предполагалось, вы не контролируете порядок. Но, конечно, с помощью свойства transform можно вызывать те же свойства в их функциональных формах в любом порядке, и они всегда будут применяться слева направо.

Ниже приведён Codepen, с которым можно взаимодействовать, чтобы увидеть разницу в применении одних и тех же значений для каждого свойства, но в одном случае в качестве отдельных свойств, а в другом — в качестве свойства transform.

See the Pen

Повторное использование с CSS переменными

Хотя это можно в некоторой степени повторить, используя разные CSS классы, порядок матрицы преобразования всё равно соблюдается. Таким образом, можно не только вызывать функции в любом порядке, но и вызывать одну и ту же функцию несколько раз.

--translate-rotate: translate(100px, 50px) rotate(30deg);
--scale-1-5: scale(1.5);
--scale3: scale(3);
--complex-transform: var(--scale-3) var(--translate-rotate) var(
--translate-rotate
) var(--scale-1-5);

transform: var(--translate-rotate) var(--scale-1-5) var(--translate-rotate);
transform: var(--translate-rotate) var(--scale-3) var(--translate-rotate);
transform: var(--complex-transform);

Драконы в деталях

Выше приведены две веские причины, по которым вы по-прежнему будете обращаться к функциональным формам отдельных свойств transform. Конечно, можно использовать и отдельные свойства, и свойство transform в одном наборе правил. Однако именно здесь и обитают драконы. 🐲

.rotate {
rotate: 45deg;
}

.rotate-more {
rotate: 65deg;
}

.rotate-a-lot {
transform: rotate(295deg);
}

Кликните на примере ниже. При каждом клике к элементу будет применяться один из трёх классов. Перед каждым кликом попробуйте предсказать, каким будет результат. Внизу фрейма видно, как выглядит каждое вращение само по себе.

See the Pen

Третье вращение вас удивило?

Когда вы используете как отдельные свойства трансформации (rotate, scale, translate), так и сокращение transform для одного и того же элемента, они накапливаются, а не отменяют друг друга. Давайте ещё раз перечислим шаги матрицы трансформации с третьего по пятый и седьмой для удобства.

  1. Сдвиг на вычисленные значения X, Y и Z для translate.
  2. Поворот на вычисленный <angle> вокруг указанной оси rotate.
  3. Масштабирование по вычисленным значениям X, Y и Z для scale.
  4. Сдвиг и поворот с помощью преобразования, заданного offset.
  5. Умножение на каждую из функций преобразования в transform слева направо. 🐲

Итак, что происходит? При первом клике на кнопку к элементу добавляется класс rotate:

.rotate {
rotate: 45deg;
}

Теперь элемент повёрнут на 45deg. При втором клике на кнопку к элементу добавляется класс rotate-more:

.rotate-more {
rotate: 65deg;
}

Из-за CSS каскада значение в rotate-more переопределяет значение в rotate, поэтому теперь элемент повёрнут на 65deg. В третий раз, когда мы кликнем по кнопке, к элементу будет добавлен класс rotate-a-lot:

.rotate-a-lot {
transform: rotate(295deg);
}

В данном случае значение функции rotate добавляется к значению (умножается на результат последнего преобразования), и в результате элемент поворачивается на 360deg (65 + 295 = 360). То же самое справедливо для функций scale и translate. Теперь вы знаете, где водятся драконы. 🐲

Комментарии


Дополнительные материалы

Предыдущая Статья

Гиф в 2025 году

Следующая Статья

Что такое TypeScript