Автор материала, перевод которого мы сегодня публикуем, расскажет о том, с какими проблемами ей довелось столкнуться, о том, как она с ними справлялась, и о том, почему она считает, что без Sass в наши дни всё ещё обойтись нельзя.
Ошибки
Если вы экспериментировали с CSS-функциями
min()
и
max()
, то, используя разные единицы измерения, могли
столкнуться с сообщениями об ошибках, наподобие такой:
Incompatible units: vh and em
.Сообщение об ошибке, возникающее при использовании различных единиц измерения в функциях min() и max()
Это сообщение выводится из-за того, что в Sass есть собственная функция
min()
. CSS-функция min()
, в
результате, игнорируется. Кроме того, Sass не может выполнять
вычисления, используя единицы измерения, между которыми нет чётко
зафиксированной взаимосвязи.Например, взаимосвязь единиц измерения
cm
и
in
определена чётко, поэтому Sass может найти
результат функции min(20in, 50cm)
и не выдаёт ошибку в
том случае, если чем-то подобным воспользоваться в коде.То же самое происходит и с другими единицами измерения. Например, все угловые единицы измерения взаимосвязаны:
1turn
,
1rad
или 1grad
всегда приводятся к одним
и тем же значениям, выраженным в единицах измерения
deg
. То же самое справедливо, например, и в случае,
когда 1s
всегда равно 1000ms
.
1kHz
всегда равно 1000Hz
,
1dppx
всегда равно 96dpi
,
1in
всегда равно 96px
. Именно поэтому
Sass может преобразовывать друг в друга значения, выраженные в этих
единицах измерения, и смешивать их в вычислениях, используемых в
различных функциях, вроде собственной функции
min()
.Но всё идёт не так, когда между единицами измерения нет чёткой взаимосвязи (как, например, выше, у
em
и
vh
).И такое происходит не только при использовании значений, выраженных в разных единицах измерения. Попытка использования функции
calc()
внутри min()
тоже приводит к
появлению ошибки. Если попробовать, в min()
, поместить
что-то вроде calc(20em + 7px)
, то выводится такая
ошибка: calc(20em + 7px) is not a number for min
.Сообщение об ошибке, возникающее при попытке использования calc() внутри min()
Ещё одна проблема появляется в ситуации, когда пытаются использовать CSS-переменную или результат работы математических CSS-функций (вроде
calc()
, min()
,
max()
) в CSS-фильтрах наподобие
invert()
.Вот сообщение об ошибке, которую можно увидеть в подобных обстоятельствах:
$color: 'var(--p, 0.85) is not a color for
invert
Использование var() в filter: invert() приводит к ошибке
То же самое происходит и с
grayscale()
: $color:
calc(.2 + var(--d, .3)) is not a color for grayscale
.Использование calc() в filter: grayscale() приводит к ошибке
Конструкция
filter: opacity()
тоже подвержена подобным
проблемам: $color: var(--p, 0.8) is not a color for
opacity
.Использование var() в filter: opacity() приводит к ошибке
Но другие функции, используемые в
filter
, включая
sepia()
, blur()
,
drop-shadow()
, brightness()
,
contrast()
и hue-rotate()
, работают с
CSS-переменными совершенно нормально!Оказалось, что причина этой проблемы похожа на ту, которой подвержены функции
min()
и max()
. В Sass
нет встроенных функций sepia()
, blur()
,
drop-shadow()
, brightness()
,
contrast()
, hue-rotate()
. Но там есть
собственные функции
grayscale(),
invert() и
opacity(). Первым аргументом этих функций является значение
$color
. Ошибка появляется из-за того, что при
использовании проблемных конструкций такого аргумента Sass не
находит.По той же причине проблемы возникают и при использовании CSS-переменных, представляющих как минимум два
hsl()
или hsla()
-значения.Ошибка при использовании var() в color: hsl()
С другой стороны, без использования Sass конструкция
color:
hsl(9, var(--sl, 95%, 65%))
это совершенно правильная и
совершенно нормально работающая CSS-конструкция.То же самое справедливо и для таких функций, как
rgb()
и rgba()
:Ошибка при использовании var() в color: rgba()
Кроме того, если импортировать Compass и попытаться использовать CSS-переменную внутри
linear-gradient()
или
radial-gradient()
, можно столкнуться с ещё одной
ошибкой. Но, в то же время, в conic-gradient()
переменными можно пользоваться без всяких проблем (конечно, если
браузер эту функцию поддерживает).Ошибка при использовании var() в background: linear-gradient()
Причина проблемы кроется в том, что в Compass есть собственные функции linear-gradient() и
radial-gradient()
, а вот
функции conic-gradient()
там никогда не было.В целом же, все эти проблемы произрастают из того факта, что в Sass или в Compass есть собственные функции, имена которых совпадают с теми, что есть и в CSS. И Sass, и Compass, встречая эти функции, считают, что мы собираемся пользоваться именно их собственными реализациями этих функций, а не стандартными.
Вот засада!
Решение проблемы
Эту проблему можно решить, если вспомнить о том, что Sass чувствителен к регистру, а CSS нет.
Это означает, что можно написать что-то вроде
Min(20em,
50vh)
и Sass не распознает в этой конструкции свою
собственную функцию min()
. Никаких ошибок при этом
выдано не будет. Эта конструкция будет представлять собой правильно
сформированный CSS-код, который работает именно так, как ожидается.
Аналогично, избавиться от проблем с другими функциями можно,
нестандартным образом записывая их имена: HSL()
,
HSLA()
, RGB()
, RGBA()
,
Invert()
.Если говорить о градиентах, то тут я обычно использую такую форму:
linear-Gradient()
и radial-Gradient()
.
Делаю я это из-за того, что такая запись близка к именам,
используемым в
SVG, но в данной ситуации сработает и любое другое подобное
имя, включающее в себя хотя бы одну заглавную букву.Зачем все эти сложности?
Почти каждый раз, когда я пишу в Твиттере что-нибудь про Sass, мне начинают читать лекции о том, что сейчас, когда есть CSS-переменные, Sass пользоваться уже не нужно. Я решила, что мне стоит на это ответить и объяснить причину моего несогласия с этой идеей.
Во-первых отмечу, что я считаю CSS-переменные чрезвычайно полезными, и то, что я пользовалась ими для решения множества задач последние три года. Но я полагаю, что необходимо помнить о том, что их использование сказывается на производительности. А поиск проблемы, возникшей в лабиринте вызовов
calc()
, может
оказаться пренеприятнейшим занятием. Стандартные браузерные
инструменты разработчика пока не очень хороши в этом деле. Я
стараюсь не увлекаться использованием CSS переменных, чтобы не
попадать в ситуации, в которых их минусы показывают себя сильнее,
чем их плюсы.Не так-то легко понять то, какими будут результаты вычисления этих выражений calc()
В целом, если переменная используется как константа, не изменяется от элемента к элементу, или от состояния к состоянию (а в подобных случаях CSS-переменные, определённо, использовать нужно), или если переменная не уменьшает объёма скомпилированного CSS-кода (решая проблему повторений, создаваемую префиксами), тогда я буду использовать Sass-переменную.
Во-вторых поддержка переменных всегда была довольно-таки незначительной причиной среди тех причин, по которым я использую Sass. Когда я начала пользоваться Sass, а было это во второй половине 2012 года, я сделала это, в основном, ради циклов. Ради той возможности, которой до сих пор нет в CSS. Хотя я перенесла некоторую логику, связанную с циклами, в препроцессор HTML (так как это уменьшает объём сгенерированного кода и позволяет избежать необходимости модифицировать и HTML, и CSS), я всё ещё использую циклы Sass во множестве случаев. Среди них генерирование списков значений, создание значений для настройки градиентов, создание списков точек при работе с функцией
polygon()
,
создание списков трансформаций и так далее.Ниже показан пример того, как я поступила бы раньше при создании некоторого количества HTML-элементов с помощью препроцессора. То, какой именно это препроцессор, особой роли не играет, но я выбрала Pug:
- let n = 12;while n--.item
Затем я бы создала переменную
$n
в Sass (и в этой
переменной должно было бы быть то же значение, что и в HTML) и
запустила бы с её использованием цикл, в котором сгенерировала бы
трансформации, используемые для позиционирования каждого из
элементов:
$n: 12;$ba: 360deg/$n;$d: 2em;.item {position: absolute;top: 50%; left: 50%;margin: -.5*$d;width: $d; height: $d;/* аккуратно оформим стили */@for $i from 0 to $n {&:nth-child(#{$i + 1}) {transform: rotate($i*$ba) translate(2*$d) rotate(-$i*$ba);&::before { content: '#{$i}' }}}}
Минус этого всего заключается в том, что мне пришлось бы менять значения и в Pug-коде, и в Sass-коде в том случае, если изменилось бы количество элементов. В коде, кроме того, появляется много повторений.
CSS-код, сгенерированный на основе вышеприведённого кода
Теперь я перешла к другому подходу. А именно, с помощью Pug я генерирую индексы в виде пользовательских свойств, а затем использую их при объявлении
transform
.Вот код, который планируется обработать с помощью Pug:
- let n = 12;body(style=`--n: ${n}`)- for(let i = 0; i < n; i++).item(style=`--i: ${i}`)
Вот Sass-код:
$d: 2em;.item {position: absolute;top: 50%;left: 50%;margin: -.5*$d;width: $d;height: $d;/* аккуратно оформим стили */--az: calc(var(--i)*1turn/var(--n));transform: rotate(var(--az)) translate(2*$d) rotate(calc(-1*var(--az)));counter-reset: i var(--i);&::before { content: counter(i) }}
Здесь можно поэкспериментировать с этим кодом.
Элементы, сгенерированные и стилизованные с использованием циклов
Применение такого подхода значительно уменьшило объём CSS-кода, сгенерированного автоматически.
CSS-код, сгенерированный на основе вышеприведённого кода
Но если нужно создать что-то вроде радуги, без Sass-циклов не обойтись.
@function get-rainbow($n: 12, $sat: 90%, $lum: 65%) {$unit: 360/$n;$s-list: ();@for $i from 0 through $n {$s-list: $s-list, hsl($i*$unit, $sat, $lum)}@return $s-list}html { background: linear-gradient(90deg, get-rainbow()) }
Вот рабочий вариант этого примера.
Многоцветный фон
Конечно, это можно сгенерировать и средствами переменных Pug, но у такого подхода нет преимуществ перед динамической природой CSS-переменных, и он не позволит уменьшить объём кода, передаваемого браузеру. В результате мне нет смысла отказываться от того, к чему я привыкла.
Я много использую встроенные математические функции Sass (и Compass), такие, как тригонометрические функции. В наши дни подобные функции являются частью спецификации CSS, но их поддержка реализована пока не во всех браузерах. В Sass таких функций нет, но в Compass они есть, и именно поэтому мне часто приходится применять Compass.
И, конечно, я могу написать в Sass собственные функции такого рода. Я так делала в самом начале, до того, как в Compass появилась поддержка обратных тригонометрических функций. Мне эти функции очень нужны, поэтому я написала их сама, пользуясь рядами Тейлора. Но в наши дни эти функции есть в Compass. Они лучше и производительнее тех, которые я писала сама.
Математические функции очень важны для меня по той причине, что я программист, а не художник. Значения в моём CSS-коде обычно формируются на основе математических вычислений. Это не какие-то магические числа, или что-то такое, что играет чисто эстетическую роль. В качестве примера их использования можно привести генерирование списка правильных или квазиправильных многоугольников для
clip-path
. Подобное применяется, например, при
создании чего-то вроде аватаров или стикеров, форма которых
отличается от прямоугольной.Рассмотрим правильный многоугольник, вершины которого лежат на окружности. Перетаскивание слайдера в следующем примере, с которым можно поэкспериментировать здесь, позволяет нам увидеть то, где размещаются точки для фигур с разным количеством вершин.
Фигура с тремя вершинами
Вот как выглядит соответствующий Sass-код:
@mixin reg-poly($n: 3) {$ba: 360deg/$n; // базовый угол$p: (); // список координат точек, изначально пустой@for $i from 0 to $n {$ca: $i*$ba; // текущий угол$x: 50%*(1 + cos($ca)); // координата x текущей точки$y: 50%*(1 + sin($ca)); // координата y текущей точки$p: $p, $x $y // добавление координат текущей точки к списку координат}clip-path: polygon($p) // установка списка точек в качестве значения clip-path}
Обратите внимание на то, что мы тут применяем циклы и другие конструкции, пользоваться которыми, применяя чистый CSS, очень неудобно.
Немного более продвинутая версия этого примера может включать в себя вращение многоугольника, реализованное путём добавления одного и того же смещения (
$oa
) к углу, соответствующему
каждой вершине. Это можно видеть в следующем примере.
Здесь генерируются звёзды, которые устроены похожим образом, но
всегда имеют чётное количество вершин. При этом каждая вершина с
нечётным индексом располагается на окружности, радиус которой
меньше основной окружности ($f*50%
).Звезда
Можно наделать и таких интересных звёздочек.
Звёзды
Можно создавать стикеры с границами (
border
),
созданными с использованием необычных шаблонов. В данном
примере стикер создаётся из единственного HTML-элемента, а шаблон,
используемый для настройки border
, создаётся с
применением clip-path
, циклов и математических
вычислений в Sass. На самом деле, вычислений тут довольно
много.Стикеры со сложными границами
Ещё один пример представлен созданием фона для карточек. Здесь, в цикле, с помощью оператора получения остатка от деления и экспоненциальных функций, создаётся фон с имитацией эффекта дизеринга.
Эффект дизеринга
Здесь тоже интенсивно используются CSS-переменные
Далее, можно вспомнить об использовании миксинов ради избежания необходимости снова и снова писать одинаковые объявления при стилизации чего-то вроде слайдеров. Разные браузеры используют различные псевдоэлементы для стилизации внутренних компонентов подобных элементов управления, поэтому нужно, для каждого компонента, задавать стили, которые управляют их видом с использованием разных псевдоэлементов.
К сожалению, в CSS, каким бы заманчивым это ни выглядело, нельзя поместить что-то вроде следующего кода:
input::-webkit-slider-runnable-track,input::-moz-range-track,input::-ms-track { /* общие стили */ }
Работать это не будет. Весь этот набор правил игнорируется в том случае, если хотя бы один селектор не будет распознан. И, так как ни один браузер не знает о существовании всех трёх селекторов из этого примера, эти стили не будут применены ни в одном браузере.
Если нужно, чтобы стилизация, всё же, заработала, надо будет поступить примерно так:
input::-webkit-slider-runnable-track { /* общие стили */ }input::-moz-range-track { /* общие стили */ }input::-ms-track { /* общие стили */ }
Но это может привести к тому, что одни и те же стили появятся в коде три раза. А если нужно, скажем, поменять у
track
свойство background
, это будет значить, что придётся
редактировать стили в ::-webkit-slider-runnable-track
,
в ::-moz-range-track
и в ::-ms-track
.Единственное вменяемое решение этой задачи заключается в использовании миксинов. Стили повторяются в скомпилированном коде, так как без этого никак не обойтись, но теперь нам, по крайней мере, не приходится три раза вводить в редакторе один и тот же код.
@mixin track() { /* общие стили */ }input {&::-webkit-slider-runnable-track { @include track }&::-moz-range-track { @include track }&::-ms-track { @include track }}
Итоги
Главный вывод, который я могу сделать, получился таким: Sass в это то, без чего нам пока не обойтись.
Пользуетесь ли вы Sass?