О конфликтах Sass и сравнительно новых возможностей CSS

в 13:16, , рубрики: Блог компании RUVDS.com

Сравнительно недавно в CSS появилось много интересных возможностей, таких, как CSS-переменные и новые функции. Хотя всё это и может сильно упростить жизнь веб-дизайнерам, эти возможности способны неожиданными способами взаимодействовать с CSS-препроцессорами вроде Sass.

О конфликтах Sass и сравнительно новых возможностей CSS - 1

Автор материала, перевод которого мы сегодня публикуем, расскажет о том, с какими проблемами ей довелось столкнуться, о том, как она с ними справлялась, и о том, почему она считает, что без Sass в наши дни всё ещё обойтись нельзя.

Ошибки

Если вы экспериментировали с CSS-функциями min() и max(), то, используя разные единицы измерения, могли столкнуться с сообщениями об ошибках, наподобие такой: Incompatible units: vh and em.

О конфликтах Sass и сравнительно новых возможностей CSS - 2

Сообщение об ошибке, возникающее при использовании различных единиц измерения в функциях 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.

О конфликтах Sass и сравнительно новых возможностей CSS - 3

Сообщение об ошибке, возникающее при попытке использования calc() внутри min()

Ещё одна проблема появляется в ситуации, когда пытаются использовать CSS-переменную или результат работы математических CSS-функций (вроде calc(), min(), max()) в CSS-фильтрах наподобие invert().

Вот сообщение об ошибке, которую можно увидеть в подобных обстоятельствах: $color: 'var(--p, 0.85) is not a color for invert

О конфликтах Sass и сравнительно новых возможностей CSS - 4

Использование var() в filter: invert() приводит к ошибке

То же самое происходит и с grayscale(): $color: ‘calc(.2 + var(--d, .3))‘ is not a color for grayscale.

О конфликтах Sass и сравнительно новых возможностей CSS - 5

Использование calc() в filter: grayscale() приводит к ошибке

Конструкция filter: opacity() тоже подвержена подобным проблемам: $color: ‘var(--p, 0.8)‘ is not a color for opacity.

О конфликтах Sass и сравнительно новых возможностей CSS - 6

Использование 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()-значения.

О конфликтах Sass и сравнительно новых возможностей CSS - 7

Ошибка при использовании var() в color: hsl()

С другой стороны, без использования Sass конструкция color: hsl(9, var(--sl, 95%, 65%)) — это совершенно правильная и совершенно нормально работающая CSS-конструкция.

То же самое справедливо и для таких функций, как rgb() и rgba():

О конфликтах Sass и сравнительно новых возможностей CSS - 8

Ошибка при использовании var() в color: rgba()

Кроме того, если импортировать Compass и попытаться использовать CSS-переменную внутри linear-gradient() или radial-gradient(), можно столкнуться с ещё одной ошибкой. Но, в то же время, в conic-gradient() переменными можно пользоваться без всяких проблем (конечно, если браузер эту функцию поддерживает).

О конфликтах Sass и сравнительно новых возможностей CSS - 9

Ошибка при использовании 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 переменных, чтобы не попадать в ситуации, в которых их минусы показывают себя сильнее, чем их плюсы.

О конфликтах Sass и сравнительно новых возможностей CSS - 10

Не так-то легко понять то, какими будут результаты вычисления этих выражений 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-коде в том случае, если изменилось бы количество элементов. В коде, кроме того, появляется много повторений.

О конфликтах Sass и сравнительно новых возможностей CSS - 11

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) }
}

Здесь можно поэкспериментировать с этим кодом.

О конфликтах Sass и сравнительно новых возможностей CSS - 12

Элементы, сгенерированные и стилизованные с использованием циклов

Применение такого подхода значительно уменьшило объём CSS-кода, сгенерированного автоматически.

О конфликтах Sass и сравнительно новых возможностей CSS - 13

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()) }

Вот рабочий вариант этого примера.

О конфликтах Sass и сравнительно новых возможностей CSS - 14

Многоцветный фон

Конечно, это можно сгенерировать и средствами переменных Pug, но у такого подхода нет преимуществ перед динамической природой CSS-переменных, и он не позволит уменьшить объём кода, передаваемого браузеру. В результате мне нет смысла отказываться от того, к чему я привыкла.

Я много использую встроенные математические функции Sass (и Compass), такие, как тригонометрические функции. В наши дни подобные функции являются частью спецификации CSS, но их поддержка реализована пока не во всех браузерах. В Sass таких функций нет, но в Compass они есть, и именно поэтому мне часто приходится применять Compass.

И, конечно, я могу написать в Sass собственные функции такого рода. Я так делала в самом начале, до того, как в Compass появилась поддержка обратных тригонометрических функций. Мне эти функции очень нужны, поэтому я написала их сама, пользуясь рядами Тейлора. Но в наши дни эти функции есть в Compass. Они лучше и производительнее тех, которые я писала сама.

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

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

О конфликтах Sass и сравнительно новых возможностей CSS - 15

Фигура с тремя вершинами

Вот как выглядит соответствующий 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%).

О конфликтах Sass и сравнительно новых возможностей CSS - 16

Звезда

Можно наделать и таких интересных звёздочек.

О конфликтах Sass и сравнительно новых возможностей CSS - 17

Звёзды

Можно создавать стикеры с границами (border), созданными с использованием необычных шаблонов. В данном примере стикер создаётся из единственного HTML-элемента, а шаблон, используемый для настройки border, создаётся с применением clip-path, циклов и математических вычислений в Sass. На самом деле, вычислений тут довольно много.

О конфликтах Sass и сравнительно новых возможностей CSS - 18

Стикеры со сложными границами

Ещё один пример представлен созданием фона для карточек. Здесь, в цикле, с помощью оператора получения остатка от деления и экспоненциальных функций, создаётся фон с имитацией эффекта дизеринга.

О конфликтах Sass и сравнительно новых возможностей CSS - 19

Эффект дизеринга

Здесь тоже интенсивно используются 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?

О конфликтах Sass и сравнительно новых возможностей CSS - 20

Автор: ru_vds

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js