овые фичи в Стайлусе
Полгода назад я стал мейнтейнеромGo to a sidenote Стайлуса — отличного препроцессора для CSS.
Side note: О том, как это произошло и чем именно я там занимаюсь, я напишу как-нибудь в другой раз, тут стоит заметить только то, что я являюсь именно мейнтейнером, а основным разработчиком сейчас является мой коллега Миша Корепанов. Jump to this sidenote’s context.
Side note: Можете сразу промотать до его пошагового описания, либо до результирующего кода. Jump to this sidenote’s context.
На прошлой неделе мы зарелизили новую версию — 0.41.0, в которой добавили пару важных фич. А в двух релизах до этого мы добавили поддержку хешей и отполировали её, в результате этих трёх последних релизов теперь можно делать много всего интересного. В этой заметке я опишу один подходGo to a sidenote, который теперь можно применять в Стайлусе, но для начала я напишу немного про новые фичи.
лочные миксины
Наконец-то! То, чего так давно не хватало в Стайлусе (и что уже давно есть в Sass) — возможность передавать в миксин блок кода.
Синтаксис передачи блока довольно простой: вызываем миксин, используя префикс «+», после чего передаём соответствующий блок либо в фигурных скобках, либо через блок с новым отступом (как всё обычно делается в Стайлусе):
+foo()
// Блок, который мы хотим передать
width: 10px
height: 10px
После того как мы передали блок в миксин, этот блок стал доступен внутри миксина как именованный аргумент — block
. После чего его можно вывестиGo to a sidenote через интерполяциюGo to a sidenote:
Side note: Также мы можем передать эту переменную в другой миксин, или воспользоваться ей как-то иначе, пример в конце статьи завязан именно на этом Jump to this sidenote’s context.
Side note: В будущем, возможно, добавится возможность использовать его без интерполяции Jump to this sidenote’s context.
foo()
width: 20px
.foo
{block}
Если вызвать этот миксин, например, так:
.bar
+foo()
padding: 0
.baz
height: 20px
Мы получим следующее:
.bar {
width: 20px;
}
.bar .foo {
padding: 0;
}
.bar .foo .baz {
height: 20px;
}
Мы получили возможность обрамлять блоки с помощью миксинов во что угодно (а в будущем, возможно, добавим и возможность изменять переданные блоки). Обычно это используют для работы с медиакверями, — мой пример, который будет ниже в статье, как раз из той же области.
еши
Хеши — объекты вида «свойство-значение». Выглядят они довольно просто:
foo = {
bar: 10px,
raz: #fff,
baz: {
blah: blah
'10%': yeah
}
}
Как видно из примера, синтаксис похож на обычные яваскриптовые объекты: ключом может быть или идентификатор, или строка, а значением может быть почти что угодно, в том числе и вложенный хеш. Из важного: в отличие от обычных блоков Стайлуса, фигурные скобки для хешей обязательны, но вот запятые — нетGo to a sidenote.
После того, как вы объявили хеш, можно в него добавить новые свойства или перезаписать старые либо через точку, либо через квадратные скобки:
foo.bar = 20px
foo['whatever'] = 'hello'
Отличия довольно простые: через точку можно писать только идентификаторы, тогда как в квадратных скобках можно использовать любые строки, либо передавать переменные. В общем, с квадратными скобками получается более гибко, а через точку — короче.
Получать свойства можно аналогично — либо через точку, либо через квадратные скобки.
Не буду описывать остальные возможности хешей — их довольно много, отмечу, что с ними нормально работает встроенная функция Стайлуса length()
, по ним можно итерироваться, можно проверять наличие ключей в условиях (if baz in foo
), а также есть несколько встроенных функций для работы с хешами (keys()
, values()
, merge()
) и интерполяция хешей в CSS-код.
selector()
ункция В новом Стайлусе появилась небольшая, но важная фича — функция selector()
. До неё в Стайлусе не было возможности получить текущий селектор: его можно было составлять из вложенных блоков, интерполировать в него, но узнать какой же в итоге получается селектор было нельзя.
Теперь же есть функция selector()
, которая возвращает текущий скомпилированный селектор. Его можно использовать либо для различных проверок, либо для каких-либо иных целей. Уже сейчас эта функция будет очень полезна в разных ситуациях, а в будущих релизах она станет ещё мощнее.
В качестве примера я приведу вот такой небольшой кусок кода:
if match(':(before|after)', selector())
content: ''
Здесь мы проверяем есть ли в селекторе указание на псевдоэлемент, и если так — выводим content
. Это может пригодиться, если у вас есть миксин, целиком отвечающий за какое-то поведение, и который можно применить как к обычному элементу, так и к псевдо-элементу.
ример с кешируемыми медиакверями
В качестве примера использования новых фич я приведу решение одной из проблем модного нынче отзывчивого дизайна: огромного количества переопределений, которые нужно расставлять для разных вьюпортов. Проблема заключается в том, что синтаксис вызова медиакверей довольно развесистый, поэтому приходится либо не обращать на это внимание и использовать «всплытие медиакверей»Go to a sidenote, либо, в погоне за оптимизацией, писать все переопределения рядом, что во многих ситуациях будет менее удобно.
Однако, с блочными миксинами, хешами и функцией selector()
в Стайлусе теперь можно обойти эти проблемы (и попутно решить ещё пару других).
Если кратко описать решение: мы создадим миксин, заменяющий вызовы медиакверей и кеширующий их, объединяя по условиям, после чего даём возможность вывести весь закешированный таким образом код.
Единственным недостатком такого подхода будет то, что если несколько условий медиакверей будут пересекаться, то, из-за группировки всех правил по объединённым медиакверям, порядок применения этих правил может поменяться.
Для начала нам понадобится объект, в который мы будем сохранять вызванный в будущем код:
$media_cache = {}
После этого нам будет нужен миксин, который мы и будем использовать вместо медиакверей, в первом приближении он будет выглядеть как-то так:
media($condition)
unless $media_cache[$condition]
$media_cache[$condition] = ()
push($media_cache[$condition], block)
Миксин довольно простой: если у нас ещё нет в кеше списка по переданному в миксин ключу, мы инициируем этот список, после чего пушим в него переданный в миксин блок — это будет наш кеш.
На самом деле нам этого не будет достаточно: такой миксин можно будет использовать только вот так:
+media('(max-width:640px)')
.foo
display: block;
Мы сможем прокидывать внутрь только полноценные блоки, у нас не получится использовать всплытие:
.foo
+media('(max-width:640px)')
display: block;
Всё из-за того, что миксин пока ничего не знает о своём контексте — он знает только о блоке, который в него передали. Тут-то нам и поможет функция selector()
, да ещё один миксин-помощник — вместе с ними наш миксин будет выглядеть так:
media($condition)
helper($condition)
unless $media_cache[$condition]
$media_cache[$condition] = ()
push($media_cache[$condition], block)
+helper($condition)
{selector()}
{block}
Для того, чтобы сохранять контекст, мы выносим изначальный код, помещающий переданный блок в кеш, в миксин helper
, который тут же и вызываем, обрамляя переданный блок в текущий селектор.
Так как теперь при вызове нашего миксина всё будет помещаться в кеш, оно не будет выводиться само по себе. Значит, нам нужна функция, которая возьмёт содержимое кеша и выплеснет его там, где мы эту функцию применим (логично будет вызывать её в конце файла):
apply_media_cache()
for $media, $blocks in $media_cache
@media $media
for $block in $blocks
{$block}
Всё довольно просто: сначала итерируемся по кешу, получая по очереди условия ($media
) и список вызванных с таким условием блоков ($blocks
), после чего создаём соответствующую медиаквери, внутри которой уже итерируемся по всем блокам, выводя их один за другим.
Теперь, если мы в конце документа вызовем эту функцию:
apply_media_cache()
мы получим то, ради чего всё затевали.
Однако, и эту функцию можно улучшить: ведь мы не хотим каждый раз писать при вызове скобочки, да и, на самом деле, хорошо бы всегда иметь в условиях only screen and
. Кроме того, мы и вовсе можем захотеть использовать вместо конкретных значений ключевые словаGo to a sidenote, типа palm
, portable
, desk
и т.п. Вместе с дополнениями и всеми предыдущими шагами мы получаем вот такой код:
тоговый код
// Определяем объект кеша и объект с алиасами
$media_cache = {}
$media_aliases = {
palm: '(max-width: 480px)'
lap: '(min-width: 481px) and (max-width: 1023px)'
lap-and-up: '(min-width: 481px)'
portable: '(max-width: 1023px)'
desk: '(min-width: 1024px)'
desk-wide: '(min-width: 1200px)'
}
// Миксин, кеширующий медиаквери
media($condition)
helper($condition)
unless $media_cache[$condition]
$media_cache[$condition] = ()
push($media_cache[$condition], block)
+helper($condition)
{selector() + ''}
{block}
// Функция, вызывающая закешированные медиаквери
apply_media_cache()
for $media, $blocks in $media_cache
$media = unquote($media_aliases[$media] || $media)
$media = '(%s)' % $media unless match('\(', $media)
$media = 'only screen and %s' % $media
@media $media
for $block in $blocks
{$block}
// Здесь будет основной код с вызовами миксина
// …
// Вызываем все закешированные медиаквери
apply_media_cache()
Теперь мы можем писать код примерно так:
.foo
width: 10px
+media('lap')
width: 20px
+media('desk')
width: 30px
+media('min-width: 200px')
width: 60px
.bar
height: 10px
+media('lap')
height: 20px
+media('desk')
height: 30px
+media('min-width: 200px')
height: 50px
+media('(min-width: 500px) and (max-width: 700px)')
height: 50px
И в результате получим следующее:
.foo {
width: 10px;
}
.bar {
height: 10px;
}
@media only screen and (min-width: 481px) and (max-width: 1023px) {
.foo {
width: 20px;
}
.bar {
height: 20px;
}
}
@media only screen and (min-width: 1024px) {
.foo {
width: 30px;
}
.bar {
height: 30px;
}
}
@media only screen and (min-width: 200px) {
.foo {
width: 60px;
}
.bar {
height: 50px;
}
}
@media only screen and (min-width: 500px) and (max-width: 700px) {
.bar {
height: 50px;
}
}
В последнем варианте функции apply_media_cache
можно увидеть, что мы добавили объект с алиасами. Кроме того, мы теперь можем вызывать миксин как в сокращённом варианте, без скобок, так и со скобками — все варианты будут работать.
В итоге, благодаря новым возможностям, появившимся в последних версиях Стайлуса, мы получили возможность быстро и удобно использовать всплывающие медиаквери в коде, с алиасами на ключевые слова и с группировкой результирующего кода по медиакверям.
Наверняка этот код не идеален, его можно улучшать и улучшать, но моей целью было показать новые фичи, а получившияся функция — лишь результат.
Вы можете прокомментировать эту статью в Мастодоне.
Опубликовано с метками: #Blog #Project #Preprocessing
Спасибо Артёму Сапегину за уместные замечания.