Трюк: раздельные функции CSS-трансформации

Перевод статьи  A Trick: Individual CSS Transform Functions с сайта danielcwilson.com, опубликовано на css-live.ru с разрешения автора — Дэна Уилсона.

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

Часто бывает нужно применять разные трансформации для разных состояний элемента. Скажем, у нас есть кнопка, которая всегда будет смещена по вертикали на -150%. Когда пользователь наводит на нее мышкой, мы ее чуть уменьшим, а при нажатии (в активном состоянии) перевернем на 180 градусов. В этом примере для кнопки «My Button» приведен CSS-код, который первым приходит на ум по такому описанию, а кнопка «Expected» показывает, как добиться описанного поведения на самом деле.

See the Pen Basics with Transform Functions by Dan Wilson (@danwilson) on CodePen.

При наведении мы не просто добавляем масштаб к начальному состоянию кнопки… мы еще и переопределяем исходный сдвиг, так что она масштабируется и возвращается к translateY(0).

Почему так? Из-за линейной алгебры. То, как происходят эти трансформации, зависит от других трансформаций и их порядка (так что translate(-50%, -50%) scale(.4) rotate(50deg) — не то же самое, что rotate(50deg) translate(-50%, -50%) scale(.4)), в сухом остатке там умножение матриц. Но обычно нам не надо знать настолько глубокие «внутренности» трансформаций. Обычно веб-разработчики просто хотят знать, как управлять этими функциями трансформации по отдельности.

See the Pen Order of Transform Functions by Dan Wilson (@danwilson) on CodePen.

Chrome начал реализовывать отдельные свойства, так что translate, rotate, and scale стали полноправными свойствами, как видно в предыдущем примере (на момент публикации требует Chrome Canary). Но у этого есть свои ограничения:

  • Отдельные x-, y‑ и z-компоненты каждого из них по-прежнему привязаны к единому свойству.
  • Преобразуются в матрицы они всегда в одном порядке: translate scale rotate.
  • В ближайшее время — только в Chrome Canary.

Так что же делать?

Использовать CSS-переменные.

Когда я слушал доклад Дэвида Хуршида о CSS-переменых, моим глазам вдруг открылась масса возможностей для применения их в анимации. А когда я начал совать переменные куда только можно, их мощь стала еще яснее. Без дальнейших церемоний… вот трюк, который даст нам больше гибкости (все «как», «что» и «почему» — сразу после примера).

See the Pen CSS Variables + Transform = Individual Properties (with Inputs) by Dan Wilson (@danwilson) on CodePen.

Мы задаем ключевой начальный transform нашему элементу с помощью всех переменных, которые мы собираемся менять. Модифицируя значение переменной для другого состояния, мы можем получить CSS-правило, более похожее на наш первоначальный код, но с гораздо большей гибкостью при усложнении. В этом примере мы обрабатываем намного больше наших первоначальных трех состояний, подключив к делу JavaScript (но это не обязательно: вот версия нашего первого примера с кнопкой на одном CSS). Фактически, тут по-прежнему определено одно CSS-свойство, и мы меняем только одну функцию трансформации за раз (без разницы, в JS или в CSS).

Без CSS-переменных мы бы не обошлись без расчетов (причем далеко не всегда тривиальных) для текущего перехода при изменении каждой функции трансформации. Тогда мы могли бы узнать текущее значение каждой из двух других функций, чтобы убедиться, что переход останется плавным.

Чем это лучше будущих отдельных свойств из Chrome Canary?

  • Можно как угодно комбинировать x, y и z, поскольку мы сами решаем, как задать transform изначально.
  • Аналогично, мы можем задавать какой нам угодно порядок функций трансформации (хотя и теряем не столь часто нужную, наверное, возможность менять этот порядок от одного состояния к другому).
  • Я проверил, что это хорошо работает в новейшем Chrome (56), Firefox (50) и Safari (только в Technical Preview — Safari 10 поддерживает переменные, но без плавных переходов). Я пока не смог проверить в Edge Insider Preview (первой версии, которая поддерживает CSS-переменные) — так что, пожалуйста, сообщите, если у вас будут новости с того фронта.

И, пожалуйста, сообщите, если у вас есть креативное применение для этого приема.

P.S. Это тоже может быть интересно:

7 Комментарии
  1. Алексей

    Мне кажется тут не раскрыто какие ВОЗМОЖНОСТИ даёт разный порядок трансформации.
    Вот допустим:
    Квадратик 100x100px.
    Увеличиваем по горизонтали в полтора раза поворачиваем на 45 градусов. Получаем прямоугольник, под углом 45 градусов.
    Поворачиваем на угол 45 градусов и растягиваем по горизонтали на в полтора раза. Получаем приплюснутый ромбик.
    Правда в CSS реализация будет обратной.
    для первого случая код будет

    transform: scaleX(1.5) rotate(-45deg)

    а для второго случая:

    transform: rotate(-45deg) scaleX(1.5)

    Почему такая рокировка?
    Всё дело в пресловутой линейной алгебре координатной сетке:
    мы сначала меняем КООРДИНАТНУЮ СЕТКУ в которой лежит пресловутый квадратик, а потом вращаем в новой координатной сетке. В результате, при вращении прямоугольника, вылезает приплюснутый ромбик — всё дело в том, что вращаем мы квадрат, поэтому и получается робмик, просто в изменённой координатной сетке и квадрат и ромбик растянуты.
    А в случае когда у нас получается повёрнутый прямоугольник, ситуация другая, мы СНАЧАЛА вращаем координатную сетку и когда она становится у нас под углом 45 градусов, изменяем масштабирование по координате.
    У меня встречный вопрос: а есть ли способы, проводить подобные преобразования, когда одна из осей задана в абсолютных единицах, а другая — в относительных?…

    1. SelenIT (Автор записи)

      Всё верно — каждая последующая трансформация происходит в координатной системе, измененной предыдущими трансформациями. У нас когда-то давно был перевод статьи, поясняющей стоящую за этим «линейную алгебру» максимально наглядными примерами. Просто здесь у автора явно был акцент не на возможностях этого для понимающих, а на неочевидности этого для новичков.

      Вопрос я не совсем понял. Как я понимаю, единицы координат в любом случае считаются абсолютными (CSS-пиксели), просто после трансформации они иначе пересчитываются в пиксели экрана. Можно уточнить вопрос?

      1. Алексей

        Всё верно — каждая последующая трансформация происходит в координатной системе, измененной предыдущими трансформациями.

        Кстати, классное определение! Я всё думал как же обобщить :)

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

        Да, согласен.
        Между прочим, я тоже обратил внимание, что с точки зрения человека, у преобразований, не совсем интуитивная логика. Привычная логика, это: взял, растянул перевернул, а там получается растянул, а начал вращать, изменения происходят словно в кривом зеркале. И понять, почему они так происходят и как сделать нужные изменения, не вполне тривиально, хотя логика не такая уж сложная, но не совсем интуитивная.
        А то, что все трансформации для объекта должны быть в одном месте, иначе они просто перезапишутся — автор привёл довольно наглядный пример, в котором демонстрируется некая избыточность применяемого по-умолчанию подхода(хотя в этом, по видимому, есть логика с точки зрения рендеринга страницы).
        Вопрос такой: нужно сделать ромбик, чтоб по вертикали высота была фиксированная, а ширина, зависала от родителя. Хотелось бы понять, такое в принципе возможно? При этом ромбик должен быть цельным, то есть заливаться градиентом, откидывать тень и т.п.

      2. Алексей

        У нас когда-то давно был перевод статьи, поясняющей стоящую за этим «линейную алгебру» максимально наглядными примерами.

        По статье пробежался, ну не совсем понятно :) В первом приближении, я вникал в матрицы и задавался вопросами «почему этот параметр влияет на вот это»… Похоже, матричный подход наиболее универсален, и, видимо, к этому вопросу стоит ещё вернуться…
        В математических выкладках, одна из основных трудностей — сложность языка. Объясняют КАК это работает, но не объясняют ЧЕМ удобна именно такая реализация, какие в этом скрываются возможности. Не «как легко можно сделать то-то и то-то», а именно что на что влияет и почему. Если показать это достаточно наглядно, пусть и подробно, то вникнув в метод, он может превратится из препятствия в инструмент…

  2. Алексей

    Да и ещё, извините за дремучесть, но всё же, что это за переменные на чистом CSS (я так понял начинающиеся с --), и где про это почитать?..
    я так понимаю вот характерный пример:

    .via-variables {
    --y: -150%;
    --scale: 1;
    --rotation: 0deg;

    transform: translateY(var(--y)) scale(var(--scale)) rotate(var(--rotation));
    }
    .via-variables:hover {
    --scale: .8;
    }
    .via-variables:active {
    --rotation: 180deg;
    }

    1. SelenIT (Автор записи)

      Это возможность из нового CSS-модуля (уже больше года как кандидат в рекомендации), правильнее называть их пользовательскими/кастомными CSS-свойствами, но старое название «CSS-переменные» успело прижиться в обиходе. Про них есть хорошая статья на frontender.info, ну и у нас на сайте есть несколько статей по теме:)

      1. Алексей

        хорошая статья на frontender.info

        — статья и правда замечательная, а возможность просто чудесная! :-)

Добавить комментарий для Алексей Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.