Матрица 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 преобразований следующим образом (цитата из спецификации):
- Начинается с единичной матрицы.
- Сдвиг на вычисленные значения X, Y и Z для
transform-origin
. - Сдвиг на вычисленные значения X, Y и Z для
translate
. - Поворот на вычисленный
<angle>
вокруг указанной осиrotate
. - Масштабирование по вычисленным значениям X, Y и Z для
scale
. - Сдвиг и поворот с помощью преобразования, заданного
offset
. - Умножение на каждую из функций преобразования в
transform
слева направо. - Сдвиг по отрицательным вычисленным значениям 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>
Сдвиг на вычисленные значения 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%; */
}
Поворот на вычисленный <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%; */
}
Масштабирование по вычисленным значениям 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%; */
}
Умножение на каждую из функций преобразования в transform
слева направо
Пропустим шестой пункт. Не то чтобы он был неактуален или не важен, но в наших целях он добавляет немного шума без особой пользы. Вышеприведённое утверждение гласит, что каждое преобразование применяется к результату предыдущего шага слева направо. Это важный момент, о котором следует помнить, когда применяется несколько преобразований к элементу. Подробнее об этом поговорим чуть позже.
transform: translate(100px, 50px) rotate(30deg);
В приведённом примере элемент сначала перемещается вправо на 100 пикселей и вниз на 50 пикселей и только потом поворачивается на 30 градусов.
Сдвиг по отрицательным вычисленным значениям X, Y и Z transform-origin
Отрицательный шаг обеспечивает применение трансформаций вокруг указанного transform-origin
без появления нежелательных дополнительных смещений. Если задать собственный transform-origin
, элемент будет вращаться вокруг новой точки вращения, что может привести к заметному смещению. Однако отрицательный шаг предотвращает двойное смещение, обеспечивая предсказуемое поведение трансформации.
Собираем всё воедино
Получилось немало всего, но что всё это значит практически?
Контроль над порядком трансформаций
Прежде всего, необходимо понять, что независимо от того, в каком порядке заданы отдельные свойства трансформации, они всегда будут применяться в одном и том же порядке.
- Применяется
translate
. - Применяется
rotate
. - Применяется указанный коэффициент
scale
.
Плюс в том, что это очень предсказуемо. Однако если конечный результат окажется не таким, как предполагалось, вы не контролируете порядок. Но, конечно, с помощью свойства transform
можно вызывать те же свойства в их функциональных формах в любом порядке, и они всегда будут применяться слева направо.
Ниже приведён Codepen, с которым можно взаимодействовать, чтобы увидеть разницу в применении одних и тех же значений для каждого свойства, но в одном случае в качестве отдельных свойств, а в другом — в качестве свойства transform
.
Повторное использование с 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);
}
Кликните на примере ниже. При каждом клике к элементу будет применяться один из трёх классов. Перед каждым кликом попробуйте предсказать, каким будет результат. Внизу фрейма видно, как выглядит каждое вращение само по себе.
Третье вращение вас удивило?
Когда вы используете как отдельные свойства трансформации (rotate
, scale
, translate
), так и сокращение transform
для одного и того же элемента, они накапливаются, а не отменяют друг друга. Давайте ещё раз перечислим шаги матрицы трансформации с третьего по пятый и седьмой для удобства.
- Сдвиг на вычисленные значения X, Y и Z для
translate
. - Поворот на вычисленный
<angle>
вокруг указанной осиrotate
. - Масштабирование по вычисленным значениям X, Y и Z для
scale
. - Сдвиг и поворот с помощью преобразования, заданного
offset
. - Умножение на каждую из функций преобразования в
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
. Теперь вы знаете, где водятся драконы. 🐲
- CSS: Свойства трансформации
translate
,rotate
иscale
- Спецификация CSS Transforms Module Level 2
transform
на MDN