![](https://habrastorage.org/webt/bp/1k/-4/bp1k-4erofbdlognlu5tmvlfjuq.jpeg)
Автор материала, перевод которого мы сегодня публикуем, расскажет о том, с какими проблемами ей довелось столкнуться, о том, как она с ними справлялась, и о том, почему она считает, что без Sass в наши дни всё ещё обойтись нельзя.
Ошибки
Если вы экспериментировали с CSS-функциями
min()
и max()
, то, используя разные единицы измерения, могли столкнуться с сообщениями об ошибках, наподобие такой: Incompatible units: vh and em
.![](https://habrastorage.org/getpro/habr/post_images/d4b/cfb/eea/d4bcfbeeab55e57b224c44fd13bfdc33.png)
Сообщение об ошибке, возникающее при использовании различных единиц измерения в функциях 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
.![](https://habrastorage.org/getpro/habr/post_images/f52/803/38f/f5280338f962c55d31a21695f3cebeb9.png)
Сообщение об ошибке, возникающее при попытке использования calc() внутри min()
Ещё одна проблема появляется в ситуации, когда пытаются использовать CSS-переменную или результат работы математических CSS-функций (вроде
calc()
, min()
, max()
) в CSS-фильтрах наподобие invert()
.Вот сообщение об ошибке, которую можно увидеть в подобных обстоятельствах:
$color: 'var(--p, 0.85) is not a color for invert
![](https://habrastorage.org/getpro/habr/post_images/056/5e3/a2e/0565e3a2e6e03d6af30598d6b45cd861.png)
Использование var() в filter: invert() приводит к ошибке
То же самое происходит и с
grayscale()
: $color: ‘calc(.2 + var(--d, .3))‘ is not a color for grayscale
.![](https://habrastorage.org/getpro/habr/post_images/663/a95/f28/663a95f286e24c1ba43f79aa538a8592.png)
Использование calc() в filter: grayscale() приводит к ошибке
Конструкция
filter: opacity()
тоже подвержена подобным проблемам: $color: ‘var(--p, 0.8)‘ is not a color for opacity
.![](https://habrastorage.org/getpro/habr/post_images/d0b/fb0/bb3/d0bfb0bb35c89f1a863a43ffc8b42a4e.png)
Использование 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()
-значения.![](https://habrastorage.org/getpro/habr/post_images/315/90b/aa8/31590baa82067ce70ad5e8c6e6251195.png)
Ошибка при использовании var() в color: hsl()
С другой стороны, без использования Sass конструкция
color: hsl(9, var(--sl, 95%, 65%))
— это совершенно правильная и совершенно нормально работающая CSS-конструкция.То же самое справедливо и для таких функций, как
rgb()
и rgba()
:![](https://habrastorage.org/getpro/habr/post_images/1ef/98c/741/1ef98c74165b2cb483d4fdacfcc8bbb9.png)
Ошибка при использовании var() в color: rgba()
Кроме того, если импортировать Compass и попытаться использовать CSS-переменную внутри
linear-gradient()
или radial-gradient()
, можно столкнуться с ещё одной ошибкой. Но, в то же время, в conic-gradient()
переменными можно пользоваться без всяких проблем (конечно, если браузер эту функцию поддерживает).![](https://habrastorage.org/getpro/habr/post_images/6c7/dd4/c1b/6c7dd4c1b1190a9d32938325ab50cbf0.png)
Ошибка при использовании 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 переменных, чтобы не попадать в ситуации, в которых их минусы показывают себя сильнее, чем их плюсы.![](https://habrastorage.org/getpro/habr/post_images/4ab/e45/233/4abe45233944bb1cd051026ac13970ff.png)
Не так-то легко понять то, какими будут результаты вычисления этих выражений 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-коде в том случае, если изменилось бы количество элементов. В коде, кроме того, появляется много повторений.
![](https://habrastorage.org/getpro/habr/post_images/c24/70d/891/c2470d8914f00b1bdd332f20408b404b.png)
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) }
}
Здесь можно поэкспериментировать с этим кодом.
![](https://habrastorage.org/getpro/habr/post_images/4ab/0d6/5b9/4ab0d65b9f5f77788230ba369f259f8a.png)
Элементы, сгенерированные и стилизованные с использованием циклов
Применение такого подхода значительно уменьшило объём CSS-кода, сгенерированного автоматически.
![](https://habrastorage.org/getpro/habr/post_images/71a/9e0/07e/71a9e007e4a5da811a901e0f62139834.png)
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()) }
Вот рабочий вариант этого примера.
![](https://habrastorage.org/getpro/habr/post_images/341/16f/349/34116f349f96071d3e2674d761749be5.png)
Многоцветный фон
Конечно, это можно сгенерировать и средствами переменных Pug, но у такого подхода нет преимуществ перед динамической природой CSS-переменных, и он не позволит уменьшить объём кода, передаваемого браузеру. В результате мне нет смысла отказываться от того, к чему я привыкла.
Я много использую встроенные математические функции Sass (и Compass), такие, как тригонометрические функции. В наши дни подобные функции являются частью спецификации CSS, но их поддержка реализована пока не во всех браузерах. В Sass таких функций нет, но в Compass они есть, и именно поэтому мне часто приходится применять Compass.
И, конечно, я могу написать в Sass собственные функции такого рода. Я так делала в самом начале, до того, как в Compass появилась поддержка обратных тригонометрических функций. Мне эти функции очень нужны, поэтому я написала их сама, пользуясь рядами Тейлора. Но в наши дни эти функции есть в Compass. Они лучше и производительнее тех, которые я писала сама.
Математические функции очень важны для меня по той причине, что я — программист, а не художник. Значения в моём CSS-коде обычно формируются на основе математических вычислений. Это — не какие-то «магические числа», или что-то такое, что играет чисто эстетическую роль. В качестве примера их использования можно привести генерирование списка правильных или квазиправильных многоугольников для
clip-path
. Подобное применяется, например, при создании чего-то вроде аватаров или стикеров, форма которых отличается от прямоугольной.Рассмотрим правильный многоугольник, вершины которого лежат на окружности. Перетаскивание слайдера в следующем примере, с которым можно поэкспериментировать здесь, позволяет нам увидеть то, где размещаются точки для фигур с разным количеством вершин.
![](https://habrastorage.org/getpro/habr/post_images/be0/565/32d/be056532d4cdf3a2d203ce7e49941703.png)
Фигура с тремя вершинами
Вот как выглядит соответствующий 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%
).![](https://habrastorage.org/getpro/habr/post_images/623/0ef/207/6230ef2072e55ddc5a79829fc8aa1637.png)
Звезда
Можно наделать и таких интересных звёздочек.
![](https://habrastorage.org/getpro/habr/post_images/4a6/575/6fa/4a65756fa98d22b345bbd0b1bb88c0f9.png)
Звёзды
Можно создавать стикеры с границами (
border
), созданными с использованием необычных шаблонов. В данном примере стикер создаётся из единственного HTML-элемента, а шаблон, используемый для настройки border
, создаётся с применением clip-path
, циклов и математических вычислений в Sass. На самом деле, вычислений тут довольно много.![](https://habrastorage.org/getpro/habr/post_images/b5a/bde/e07/b5abdee0799240ca86f014b8a8141dc6.png)
Стикеры со сложными границами
Ещё один пример представлен созданием фона для карточек. Здесь, в цикле, с помощью оператора получения остатка от деления и экспоненциальных функций, создаётся фон с имитацией эффекта дизеринга.
![](https://habrastorage.org/getpro/habr/post_images/3d0/1c7/a75/3d01c7a75b0b0480681df24d4a06fdc3.png)
Эффект дизеринга
Здесь тоже интенсивно используются 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?
![](https://habrastorage.org/webt/de/0y/l-/de0yl-6ppopvisr_a80b4yuhjj8.png)
anonymous
Как часто при верстке вы используете данный набор возможностей? Я верстаю сайты с использованием препроцессоров как минимум год, и такие штуки (я говорю о сокращенном коде, SCSS), как «Фигура с тремя вершинами» никогда не приходилось использовать.
markilfin
Я использую часто.
Тем, кто работает с данными, которые надо представить в визуальном формате, постоянно приходится делать что-то подобное: