Когда в одном transform перечислено несколько функций через пробел, порядок между ними важен: браузер применяет их не слева направо, а наоборот - сначала крайнюю правую, затем следующую и так далее до крайней левой. Поэтому transform: translate(80px, 0) rotate(45deg) и transform: rotate(45deg) translate(80px, 0) дают разные результаты.
В этом уроке
Попробуй прямо сейчас: пунктирная рамка - это исходное положение блока без трансформаций. Сейчас transform: translate(80px, 0) rotate(45deg) - блок сначала поворачивается на месте, а затем сдвигается ровно на 80px вправо. Поменяй порядок на rotate(45deg) translate(80px, 0) - блок окажется совсем в другом месте: сначала он сдвигается на 80px, а потом ЭТА УЖЕ СДВИНУТАЯ точка поворачивается вокруг исходного центра - в итоге блок смещается не только вправо, но и вниз, как будто проехал по дуге.
Порядок функций в transform: почему он важен
Собственная проверка через getComputedStyle().transform подтвердила именно такую механику: браузер применяет функции внутри одного transform не в том порядке, в котором они написаны (слева направо), а в ОБРАТНОМ - сначала крайняя правая функция, потом следующая слева от неё, и так далее. Это работает как вложенные математические функции: transform: A B C равносильно "сначала C, потом B, потом A" - так же, как в записи f(g(x)) сначала вычисляется g, а потом f. Именно поэтому translate(80px, 0) rotate(45deg) и rotate(45deg) translate(80px, 0) дают разные результаты: в первом случае поворот (правая функция) применяется первым - к ещё не сдвинутому блоку, а сдвиг (левая функция) - последним, как плоское смещение уже повёрнutой фигуры. Во втором случае всё наоборот.
.card {
transform: translate(80px, 0) rotate(45deg);
} - блок поворачивается на месте, потом сдвигается вправо ровно на 80px.
Неправильное ожидание - что порядок функций можно менять местами без последствий, как в обычной сумме:
.card {
transform: rotate(45deg) translate(80px, 0);
} - те же самые числа, но блок окажется в другом месте: сдвиг здесь применяется первым, а поворот - вторым, поворачивая уже сдвинутую точку вокруг исходного центра.
Из истории
Даже сама спецификация CSS Transforms когда-то ошибалась в этом вопросе: в одном из примеров текст спецификации описывал transform: translate(80px, 80px) scale(1.5, 1.5) rotate(45deg) как применяющийся слева направо ("сначала сдвиг, потом масштаб, потом поворот") - хотя на самом деле верно ровно наоборот. Ошибку заметили и обсудили в официальном багтрекере W3C в сентябре 2015 года, после чего формулировку в спецификации поправили на корректную. Обсуждение бага в архиве W3C.
Комбинирование: transform не суммируется само по себе
Частая неожиданность: если задать transform в обычном состоянии элемента, а другой transform - в :hover, второй НЕ добавляется к первому, а полностью его заменяет. Собственная проверка это подтвердила: у кнопки с transform: translateX(-50%) в обычном состоянии и transform: rotate(20deg) на :hover, при наведении матрица трансформации становится ЧИСТЫМ поворотом - смещение translateX(-50%) полностью исчезает, и кнопка заметно "прыгает" в сторону. Чтобы оба эффекта работали вместе, нужно перечислить ОБЕ функции в одном transform внутри :hover: transform: translateX(-50%) rotate(20deg). Отдельно можно вспомнить приём из урока про translate: отдельные свойства translate, rotate и scale (не функции внутри transform, а именно свойства) устроены иначе - собственная проверка показала, что translate: -50px 0 в обычном состоянии и rotate: 20deg на :hover прекрасно работают вместе, потому что это два РАЗНЫХ CSS-свойства, и одно не перезаписывает другое.
.btn:hover {
transform: translateX(-50%) rotate(20deg);
} - обе функции перечислены вместе, сдвиг сохраняется вместе с поворотом.
Неправильное ожидание - что transform на :hover добавится к transform из обычного состояния:
.btn {
transform: translateX(-50%);
}
.btn:hover {
transform: rotate(20deg);
} - при наведении сдвиг translateX(-50%) полностью пропадёт, останется только поворот - кнопка сместится неожиданным образом.
Частые ошибки новичков
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| Думать, что порядок функций в transform не важен | Функции применяются не слева направо, а наоборот - разный порядок обычно даёт разный результат | Проверять результат в блокноте при любой смене порядка, не полагаться на интуицию |
| Считать, что порядок ВСЕГДА меняет результат, на основе одного примера с rotate() и scale() | Редкое исключение: РАВНОМЕРНЫЙ scale() (одно число или два одинаковых) на самом деле коммутирует с rotate() - именно для этой пары порядок не важен | Как только в цепочке появляется translate() или НЕравномерный scale(sx, sy), порядок снова становится важным - проверять каждый случай отдельно |
| Задать transform на :hover и ожидать, что он добавится к transform из обычного состояния | transform на :hover полностью заменяет transform из базового правила, значения не суммируются | Перечислять все нужные функции вместе в каждом правиле, где меняется transform |
| Каждый раз переписывать длинный список функций в transform, чтобы ничего не потерять между состояниями | Работает, но громоздко, и легко забыть одну из функций при следующем изменении | Использовать отдельные свойства translate/rotate/scale вместо функций внутри transform - они не перезаписывают друг друга между состояниями |
Шпаргалка
| Запись | Что означает |
|---|---|
| transform: A B C; | Применяются справа налево: сначала C, потом B, потом A |
| translate(80px,0) rotate(45deg) | Сначала поворот на месте, потом сдвиг на ровные 80px |
| rotate(45deg) translate(80px,0) | Сначала сдвиг, потом поворот всей конструкции вокруг центра - результат другой |
| rotate() + равномерный scale() | Редкое исключение: эти двое коммутируют, порядок не важен |
| transform в :hover | Полностью заменяет transform из обычного состояния, а не суммируется с ним |
| Отдельные translate/rotate/scale | Не перезаписывают друг друга между состояниями - альтернатива ручному перечислению |
Коротко: отвечаем на вопросы
Почему rotate() и scale() вместе дают эффект, похожий на перекос (skew), а не просто поворот с масштабом?
Скорее всего дело в порядке: функции внутри transform применяются справа налево, а не в том порядке, в котором написаны. Если порядок не тот, что задумывался, результат может выглядеть искажённым, похожим на наклон. Решение - поменять функции местами и сравнить результат в живом предпросмотре, а не угадывать.
Почему при наведении transform из :hover как будто "стирает" transform из обычного состояния?
Потому что transform - это одно CSS-свойство, и его новое значение в :hover полностью заменяет старое, а не добавляется к нему. Если в обычном состоянии был, например, сдвиг для центрирования, а в :hover задан только поворот, сдвиг исчезнет. Нужно писать обе функции вместе в правиле :hover: transform: translateX(-50%) rotate(20deg).