От автора: пользовательские свойства CSS (или чтобы легче понять, CSS-переменные) позволяют сократить код, а также открывают новые способы работы с CSS, которые раньше были недоступны. Они сопоставимы с переменными в препроцессорах… но также умеют много чего нового. Неважно, нравится ли вам декларативная природа CSS, или вы предпочитаете большую часть логики обрабатывать в JS, каждый найдет в пользовательских свойствах что-то полезное.
Мощь пользовательских свойств заключается в двух уникальных способностях:
каскадирование;
возможность модифицировать значения с помощью JS.
Пользовательские свойства становятся еще более сильным оружием, если их объединить с другими уже существующими концепциями в CSS типа calc().
Основы
С помощью пользовательских свойств можно эффективно делать то, что умеют переменные в препроцессорах типа Sass. Можно устанавливать глобальные и локальные значения переменных и потом использовать их в коде. Но благодаря каскадированию можно задавать новые значения свойств в более специфичных правилах.
Каскадирование может привести к нескольким интересным подходам, описанным Violet Peña в обзоре ключевых преимуществ переменных и Chris в обзоре вариантов темизации сайта.
Люди уже несколько лет обсуждают эти преимущества каскадирования, однако зачастую про них забывают, несмотря на то, что это ключевой функционал, отличающий их от препроцессоров. Amelia Bellamy-Royds говорила об этом и использовала еще в 2014 с SVG, Philip Walton заметил почти все главные плюсы каскадирования в 2015, а Gregor Adams показал, как их можно использовать в минимальном фреймворке по grid. Эти преимущества каскадирования – самый легкий способ начать работать с пользовательскими свойствами, не забывая о прогрессивном улучшении.
ОК. Мы поняли, что пользовательские свойства дают нам нативный функционал препроцессоров, а также новые кейсы использования благодаря каскадированию. Но есть ли что-то в них, чего мы раньше вообще делать не могли? Конечно!
Индивидуализация свойств
Все свойства, состоящие из нескольких частей, теперь можно использовать по-разному. Несколько background можно разделить, а несколько transition-duration можно разбить. Вместо transform: translateX(10vmin) rotate(90deg) scale(.8) translateY(5vmin) можно задать одно правило с несколькими пользовательскими свойствами, после чего отдельно менять их значения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.view { transform: translateX(var(--tx, 0)) rotate(var(--deg, 0)) scale(var(--scale, 1)) translateY(var(--ty, 0)); } .view.activated { --tx: 10vmin; --deg: 90deg; } .view.minimize { --scale: .8; } .view.priority { --ty: 10vmin; } |
Понадобится некоторое время на установку параметров, но после вам не нужно будет изменять каждую функцию transform вручную под нужный класс/селектор. После этого в разметку можно добавлять любой или вообще все классы для элемента .view, и свойство transform будет меняться само по себе, как показано в демо по коду сверху.
Независимые свойства трансформации только на подходе (и первыми будут translaye, scale и rotate), пока что они работают с флагом только в Chrome. В пользовательских свойствах данный функционал уже доступен с большей поддержкой (также вы сможете задавать порядок функций. Например, rotate(90deg) translateX(10vmin) отличается от translateX(10vmin) rotate(90deg)).
Если вас не смущает, что все функции делят одну тайминг опцию, то их можно плавно анимировать с помощью свойства transition при изменении любых переменных. Почти магия.
Переходим от значений без единиц к значениям с любыми единицами
Концепции можно расширить, если соединить их с calc(). Можно задавать переменные без единиц измерения (—card-width: 10vmin или —rotation-amount: 1turn) и использовать их в большем количестве мест. Так значения в пользовательских свойствах станут еще более динамичными.
Функции calc() уже несколько лет, и, возможно, это самый полезный инструмент при сложении значений с разными единицами измерения. Например, если у вас есть жидкая width в процентах, которую нужно сократить на 50px (width: calc(100% — 50px)). Но calc() может гораздо больше.
В calc можно использовать умножение для формирования значения. Код ниже полностью валидный и дает нам понять, что transform и filter связаны, так как в них используется число 10.
1 2 3 4 5 6 |
.colorful { transform: translateX(calc(10 * 1vw)) translateY(calc(10 * 1vh)); filter: hue-rotate(calc(10 * 4.5deg)); } |
Это не самый удачный пример использования, обычно в браузер не посылают такие вычисления. 10*1vw всегда будет 10vw, здесь функция calc бесполезна. Ее можно использовать в препроцессоре с циклами, а тут можно обойтись и без calc().
А если заменить повторяющееся 10 переменной? Вы сможете задавать множество значений из одной точки, даже если значения будут с разными единицами измерения. Вы сможете в любой момент изменить значения. Благодаря переменным без единиц измерения и calc код ниже полностью валидный:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.colorful { --translation: 10; transform: translateX(calc(var(--translation) * 1vw)) translateY(calc(var(--translation) * 1vh)); filter: hue-rotate(calc(var(--translation) * 4.5deg)); will-change: transform, filter; transition: transform 5000ms ease-in-out, filter 5000ms linear; } .colorful.go { --translation: 80; } |
Можно взять одно значение (изначально 10 и позже меняется на 80… или любое другое) и применить отдельно к vw или vh в трансформациях. Можно конвертировать в deg для вращения или filter: hue-rotate().
Единицы измерения отбрасывать не обязательно, но пока они есть в calc, у вас есть такая возможность. Это открывает дополнительные возможности. Изменяя базовое значение в разных правилах, можно реализовать анимацию со сдвигами в продолжительности и задержке. В примере ниже ms будут всегда нашими конечными единицами измерения, но результат для нашего delay должен быть всегда равен половине duration анимации. Для этого нам нужно будет изменить всего лишь —duration-base.
В пользовательских свойствах можно даже использовать кубические функции Безье. В примере ниже создано несколько вертикально расположенных блоков. Каждый чуть меньше предыдущего, и каждому передан множитель кубической функции Безье. Этот множитель будет отдельно применяться к 4 частям базовой функции Безье. Так у каждого блока будет своя индивидуальная функция, но между собой функции будут связаны. Добавьте или удалите блок, чтобы посмотреть, как они взаимодействуют. Кликните по экрану в любом месте, чтобы изменить направление анимации.
Для рандомизации базовой функции при каждом клике используется JS, через который также задается множитель для блоков. Основной CSS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
.x { transform: translateX(calc(var(--x) * 1px)); /* базовое значение обновляется через JS по нажатию */ transition-timing-function: cubic-bezier( var(--cubic1-1), var(--cubic1-2), var(--cubic1-3), var(--cubic1-4)); } .advanced-calc .x { transition-timing-function: cubic-bezier( calc(var(--cubic1-1) * var(--cubic1-change)), calc(var(--cubic1-2) * var(--cubic1-change)), calc(var(--cubic1-3) * var(--cubic1-change)), calc(var(--cubic1-4) * var(--cubic1-change))); } |
Если вы смотрите этот пример в определенном браузере (или думаете, зачем в примере класс .advanced-calc), то вы должны были заподозрить, что в подходе есть проблемы. Есть очень важная загвоздка… calc не всегда одинаково работает во всех браузерах. Ana Tudor очень долго обсуждала различия в поддержке в браузерах для calc, и написал пару дополнительных тестов для других упрощенных кейсов с calc.
Хорошая новость: все браузеры, поддерживающие пользовательские свойства, также работают с calc при конвертации единиц типа px, vmin, rem и других линейных единиц в свойствах типа width и transform: translate().
Не самые хорошие новости: в Firefox и Edge часто возникают проблемы с другими типами единиц измерения типа deg, ms и даже % в отдельных случаях. То есть предыдущие filter: hue-rotate() и –rotation будут игнорироваться. В отдельных случаях эти браузеры даже не понимают calc(1 * 1), т.е. даже значение без единиц измерения (как в rgb()) может стать проблемой.
Все браузеры с поддержкой пользовательских свойств разрешают использовать переменные внутри cubic-bezier, но не все из них разрешают использовать calc на любом уровне. Сейчас это основные ограничения с calc, на мой взгляд… и они даже не связаны с пользовательскими свойствами.
По этим проблемам в браузерах заведены баги, их можно обойти с помощью прогрессивного улучшения. Ранние демо выполняют cubic-bezier только в случае, если знают, что смогут их обработать. В противном случае вы получите базовую функцию. Они будут ошибочно передавать CSS @supports, поэтому нужна проверка типа JS Modernizr:
1 2 3 4 5 |
function isAdvancedCalcSupported() { document.body.style.transitionTimingFunction = 'cubic-bezier(calc(1 * 1),1,1,1)'; return getComputedStyle(document.body).transitionTimingFunction != 'ease'; //если браузеры не понимают, то вычисленное значение станет значением по умолчанию (у нас это "ease") } |
Взаимодействие через JavaScript
Пользовательские свойства – замечательный инструмент CSS, но их мощь раскрывается в комбинации с JS. В демо с cubic-bezier мы показали, что новое значение свойства можно записать в JS:
1 2 |
var element = document.documentElement; element.style.setProperty('--name', value); |
Код сверху установит новое значение для глобального свойства (в CSS задано в правиле :root). Или можно пойти прямо и задать новое значение для заданного элемента (тем самым дать элементу максимальную специфичность и оставить переменную без изменений для других элементов, использующих ее). Так удобно делать, когда нужно управлять состоянием и изменить стили по заданным значениям.
David Khourshid обсудил мощные способы взаимодействия с пользовательскими свойствами через JS в контексте Observables, и они очень хорошо сочетаются друг с другом. Открыт широкий выбор способов взаимодействия пользовательских свойств и JS, будь-то Observables, изменения состояний в React или обработчики событий.
Взаимодействие особенно важно для свойств, которые принимают несколько значений. В JS уже очень давно используется объект style для изменения стилей, но так довольно сложно менять какую-то отдельную часть длинного значения. Если нужно изменить один фоновый рисунок из 10, то нам нужно знать, какой из них мы будем менять, а остальные оставить нетронутыми. С transform все еще сложнее, когда нужно изменить только rotate(), а scale() оставить без изменений. С помощью JS в пользовательских свойствах можно изменять каждую часть значения отдельно, что упрощает работу со значением для всего свойства transform.
Здесь также хорошо сработает способ без единиц измерения. Функция setProperty() может передавать в CSS только числа, что упростит JS-код в некоторых случаях.
Пора использовать?
Пользовательские свойства доступны в последних версиях браузеров Mozilla, Google, Opera, Apple и Microsoft. Сейчас самое время поэкспериментировать. Большая часть описанного в этой статье уже сейчас может использоваться с хорошими фолбеками. Скоро выйдут обновления в браузерах для calc, но пользовательские свойства уже сейчас можно использовать в отдельных ситуациях. Например, если вы работаете над гибридным мобильным приложением для последних версий iOS, Android или Windows, у вас будет гораздо больше возможностей.
Пользовательские свойства – большое дополнение к CSS, и вам потребуется какое-то время на то, чтобы понять принцип их работы. Сначала промочите ножки, и если понравилось, ныряйте с головой.
Автор: Dan Wilson
Источник: //css-tricks.com/
Редакция: Команда webformyself.