Stylus - препроцессор CSS

Знакомимся со Stylus - синтаксис и базовые возможности

bg

В одной из предыдущих статей мы установили и запустили Gulp

В предыдущей статье рассмотрели первый инструмент для ускорения процесса верстки - Pug

Второй инструмент для ускорения процесса верстки - Stylus

Что такое Stylus?

Stylus - препроцессор CSS - имеет свой синтаксис и может быть сгенерирован в CSS

Существует несколько популярных препроцессоров CSS - Sass (Scss), Less, Stylus

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

Как и другие препроцессоры, Stylus имеет много преимуществ над обычным CSS - переменные, миксины, операторы, функции, импорты и так далее. В статье расскажу о базовых возможностях, которые очень упрощают и ускоряют написание стилей



Синтаксис Stylus

Сравним CSS и Stylus - слева синтаксис Stylus, справа синтаксис CSS

1

Пример на Codepen


Визуальные отличия от синтаксиса CSS
  1. Не нужны фигурные скобки
  2. Не нужны точки с запятой после каждого свойства
  3. Не нужны двоеточия после названия свойства

Тем не менее Stylus воспринимает обычный синтаксис CSS, то есть рядом с синтаксисом Stylus можно вставить обычный CSS


Логические отличия от синтаксиса CSS
  1. Вложенность селекторов осуществляется через отступ Tab
  2. Возможность зависимости от названия родительского класса

Пример 1: Стилизуем ненумерованный список

На CSS это выглядит следующим образом

CSS

ul {
	padding: 0;
	margin: 0;
}
ul li {
	list-style: none;
}
ul li a {
	font-size: 18px;
	text-decoration: underline;
}

Чтобы не писать цепочку селекторов от родительского до дочернего, в Stylus используем отступы Tab

Stylus

ul
	padding 0
	margin 0
	li // генерируется селектор ul li
		list-style none
		a // генерируется селектор ul li a
			font-size 18px
			text-decoration underline

Пример 2: Стилизуем навигацию

CSS

.nav {
	background: #fafafa;
}
.nav__flex {
	display: flex;
}
.nav__item {
	background: #fff;
}
.nav__item:not(:last-child) {
	margin-right: 30px;
}
.nav__item a {
	font-size: 18px;
	text-transform: uppercase;
	text-decoration: underline;
	color: #0d3b66;
}
.nav__item a:hover {
	text-decoration: none;
}
.nav__item_active {
	background: #0d3b66;
}
.nav__item_active a {
	color: #fff;
}

Если в обычном CSS решим переименовать родительский селектор .nav в .navigation, то необходимо будет искать все селекторы, которые зависят от него, и каждый переименовывать вручную.

В Stylus существует специальный служебный символ - &, который указывает на родительский селектор.

Stylus

.nav
	background #fafafa
	&__flex	// генерируется селектор .nav__flex (Элемент по методологии БЭМ)
		display flex
	&__item	// генерируется селектор .nav__item (Элемент по методологии БЭМ)
		background #fff
		&:not(:last-child)	// .nav__item:not(:last-child)
			margin-right 30px
		a	// .nav__item a
			font-size 18px
			text-transform uppercase
			text-decoration underline
			color #0D3B66
			&:hover	// .nav__item a:hover
				text-decoration none
		&_active	// генерируется селектор .nav__item_active (Модификатор по методологии БЭМ)
			background #0D3B66
			a	// .nav__item_active a
				color #fff

Если в Stylus решим переименовать родительский селектор .nav в .navigation, то нужно будет переименовать только его, а так как дочерние селекторы зависят от наименования родительского селектора, то будут переименованы автоматически



Дополнительные возможности


Переменные

Переменные объявляются следующим образом - название_переменной = значение переменной. Название переменной может состоять просто из слова на английском языке, но предпочитаю имена переменных начинать со знака $, чтобы не было пересечений с CSS свойствами и визуально было легче воспринимать переменные в файле стилей

Значением переменной может быть любое значение CSS-свойства, например - цвет, размер шрифта, размер отступа и так далее.

$main = #0D3B66
$second = #FF5964
$title-size = 48px
$padding = 30px

Используем объявленные переменные

$main = #0D3B66
$second = #FF5964
$title-size = 48px
$padding = 30px

body
	background $main // генерируется background: #0d3b66;

.section
	&__body
		padding $padding // генерируется padding: 30px;
	&__title
		color $second // генерируется color: #ff5964;
		font-size $title-size // генерируется font-size: 48px;

Будут сгенерированы следующие CSS стили

body {
  background: #0d3b66;
}
.section__body {
  padding: 30px;
}
.section__title {
  color: #ff5964;
  font-size: 48px;
}


Миксины

Миксины позволяют писать намного меньше стилей вручную. Они могут содержать в себе целый блок свойств, которые при верстке повторяются неоднократно, а также при необходимости принимать параметры

Объявление миксинов - название_миксина(параметр, параметр), и на следующей строке после отступа Tab указываем свойство или несколько свойств (каждое свойство на отдельной строке)

// Плавный переход из одного состояния элемента в другое
tr()
	transition all .3s ease

// Масштабируем изображение на всю область родительского элемента
fit()
	display block
	width 100%
	height 100%
	object-fit cover

Используем объявленные миксины

tr()
	transition all .3s ease

fit()
	display block
	width 100%
	height 100%
	object-fit cover

a
	color red
	tr() // генерируется свойство transition: all .3s ease;
	&:hover
		color blue

.image
	width 400px
	height 300px
	img
		fit() // генерируются все свойства, которые прописаны в миксине fit()

Сгенерированный CSS

a {
  color: #f00;
  transition: all 0.3s ease;
}
a:hover {
  color: #00f;
}
.image {
  width: 400px;
  height: 300px;
}
.image img {
  display: block;
  width: 100%;
  height: 100%;
  -o-object-fit: cover;
  object-fit: cover;
}


Также в миксинах можно передавать параметры. Название параметра может быть произвольным словом на английском языке

tr(duration) // duration - продолжительность
	transition all duration ease

a
	color red
	tr(.5s) // генерируется свойство transition: all 0.5s ease;
	&:hover
		color blue

Сгененированный CSS

a {
  color: #f00;
  transition: all 0.5s ease;
}
a:hover {
  color: #00f;
}


Можно передавать несколько параметров через запятую

tr(prop, duration) // prop - свойство, для которого будем применять плавный переход, duration - продолжительность
	transition prop duration ease

a
	color red
	tr(color,.5s) // генерируется свойство transition: color 0.5s ease;
	&:hover
		color blue

Сгененированный CSS

a {
  color: #f00;
  transition: color 0.5s ease;
}
a:hover {
  color: #00f;
}


Можно передавать параметры без указания единиц измерения, а единицы измерения указать внутри миксина

tr(duration) // duration - продолжительность
	transition all (duration s) ease // в скобках на первом месте - параметр, на втором месте через пробел - единица измерения

fontSize(val)
	font-size val em // если у свойства только одно значение, то можно писать без скобок - на первом месте - параметр, на втором месте через пробел - единица измерения

a
	color red
	fontSize(2) // генерируется свойство font-size: 2em;
	tr(.5) // генерируется свойство transition: all 0.5s ease;
	&:hover
		color blue

Сгененированный CSS

a {
  color: #f00;
  font-size: 2em;
  transition: all 0.5s ease;
}
a:hover {
  color: #00f;
}


В миксинах можно использовать объявленные переменные и другие объявленные миксины

$main = #0D3B66

fontSize(val)
	font-size val em

button()
	fontSize(2)
	color $main

a
	button()

Из примера выше генерируется следующий CSS

a {
  font-size: 2em;
  color: #0d3b66;
}


За счет такой гибкости, можно настроить множество миксинов, с которыми вам будет комфортно работать

Пока приводил простые примеры, на которых проще понять принцип работы миксинов.

Рассмотрим пример посложнее - сделаем миксин кнопки

$main = #0D3B66
$second = #FF5964

tr()
	transition all .3s ease

btn(height,fontSize) // принимаем два параметра - высота кнопки без единиц измерения, размер шрифта кнопки
	height height px // подставляем значение параметра высоты и указываем единицу измерения - пиксели
	font-size fontSize // подставляем значение параметра размера шрифта, параметр будет передаваться с единицами измерения
	display inline-flex
	align-items center
	justify-content center
	text-align center
	text-decoration none
	text-transform uppercase
	font-weight bold
	color $second // подставляем значение переменной $second
	background $main // подставляем значение переменной $main
	padding 0 (height px) // внутренние боковые отступы будут равняться высоте, подставляем значение параметра высоты
	border-radius (height/2) px // используем оператор деления чтобы высчитать закругление углов (о них чуть позже)
	tr() // вызываем миксин плавного перехода tr() который сгенерирует transition: all 0.3s ease;
	&:hover
		background lighten($main,10%) // используем встроенную функцию lighten(), чтобы при наведении фон кнопки становился чуть светлее

// Теперь используя миксин btn(height,fontSize) можем создавать шаблонные кнопки одной строкой, введя только два параметра - высоты и размера шрифта

a.button-large
	btn(48,18px)

a.button-small
	btn(30, .7rem)

Данный миксин можно будет использовать во всем проекте при создании любого количества кнопок, передавая только необходимые параметры. Миксин можно настроить максимально гибко, передавая и другие параметры, например цвет фона, цвет текста, закругление углов и так далее

В итоге будет сгенерирован следующий CSS

a.button-large {
  height: 48px;
  font-size: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
  font-weight: bold;
  color: #ff5964;
  background: #0d3b66;
  padding: 0 48px;
  border-radius: 24px;
  transition: all 0.3s ease;
}
a.button-large:hover {
  background: #114f89;
}
a.button-small {
  height: 30px;
  font-size: 0.7rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
  font-weight: bold;
  color: #ff5964;
  background: #0d3b66;
  padding: 0 30px;
  border-radius: 15px;
  transition: all 0.3s ease;
}
a.button-small:hover {
  background: #114f89;
}

Пример на Codepen



Операторы Stylus

Stylus имеет самые различные операторы для вычислений - сложение, вычитание, деление, умножение, отрицание, сравнение, логические операторы и так далее. Все перечислять не буду, покажу пару примеров, как их можно использовать

Если межстрочный интервал line-height в макете указан в пикселях, а в CSS наиболее корректно указывать его относительное значение в зависимости от размера шрифта, то с помощью оператора деления можно быстро вычислить относительное значение - значение межстрочного интервала делим на значение размера шрифта.

Расчеты желательно производить в круглых скобках

p
	font-size 18px
	line-height (30/18) // например, в макете line-height равен 30px

p.accent
	font-size 36px
	line-height (48/36) // например, в макете line-height равен 48px

Сгененированный CSS

p {
  font-size: 18px;
  line-height: 1.666666666666667;
}
p.accent {
  font-size: 36px;
  line-height: 1.333333333333333;
}


Операторы удобно использовать в миксинах для динамически вычисляемых значений

btn(height)
	height height px
	font-size (height / 2) px // размер шрифта будет в два раза меньше высоты
	padding 0 ((height + 15) px) // размер внутренних бокобых отступов будет суммой значения высоты + 15px
	border-radius (height / 2) px // каждый угол будет закруглен ровно на половину от значения высоты

a.button-large
	btn(100) // задаем кнопку высотой 100, остальные значения для свойств расчитываются автоматически

Сгененированный CSS

a.button-large {
  height: 100px;
  font-size: 50px;
  padding: 0 115px;
  border-radius: 50px;
}



Встроенные функции Stylus

В Stylus большое количество встроенных функций. Рассмотрим некоторые из них

Встроенные функци round() и floor() для округления чисел.

В круглых скобках указываются параметры: первое значение - чаще всего результат деления, второе значение - количество знаков дробной части (после точки/запятой)

p
	font-size 18px
	line-height round(30/18, 3) // округляет до трех знаков дробной части в большую или меньшую сторону в зависимости от следующего знака дробной части

p.accent
	font-size 36px
	line-height floor(48/36, 2) // округляет в меньшую сторону до двух знаков дробной части

Сгененированный CSS

p {
  font-size: 18px;
  line-height: 1.667;
}
p.accent {
  font-size: 36px;
  line-height: 1.33;
}


Встроенные функции darken() и lighten() для вычисления цвета. Чаще всего применяется при вычислении цвета при наведении на элемент или выделении активного элемента.

В круглых скобках указываются параметры: первое значение - цвет, который будет преобразован, второе значение - насколько процентов цвет будет преобразован

$main = #0D3B66

.circle
	background red
	&:hover
		background darken(red, 15%) // при наведении красный цвет фона становится на 15% темнее

.square
	background $main
	&:hover
		background lighten($main, 20%) // при наведении цвет фона указанный в переменной $main становится на 20% светлее

Сгененированный CSS

.circle {
  background: #f00;
}
.circle:hover {
  background: #d90000;
}
.square {
  background: #0d3b66;
}
.square:hover {
  background: #14599b;
}


Импортирование файлов стилей

В больших проектах основной файл CSS становится довольно большим. На этапе разработки Stylus предоставляет возможность разбивать стили на отдельные фрагменты, сохранять их в отдельных *.styl файлах и подключать эти файлы в основной файл стилей

Рассмотрим простой пример - вынесем часть стилей для адаптивности в отдельный файл media.styl и импортируем его в основной файл стилей main.styl

main.styl

.container
	max-width 1170px
	margin 0 auto
	padding 0 15px

@media (max-width: 1199.98px)
	.container
		max-width 920px

Создадим рядом с main.styl файл media.styl и перенесем в него часть стилей для адаптивности

media.styl

@media (max-width: 1199.98px)
	.container
		max-width 920px

Импортируем файл media.styl в основной файл стилей main.styl, для этого используем @import

.container
	max-width 1170px
	margin 0 auto
	padding 0 15px

@import media

В итоге генерируется main.css включая стили прописанные в файле media.styl

.container {
  max-width: 1170px;
  margin: 0 auto;
  padding: 0 15px;
}
@media (max-width: 1199.98px) {
  .container {
    max-width: 920px;
  }
}

В больших проектах можно отдельно создавать и импортировать файлы для переменных, миксинов, даже определенных секций. Все это настраивается индивидуально для комфортной разработки в зависимости от задач

В шаблоне gulp-dev, который рассматривали в одной из предыдущих статей, в CSS генерируется только основной файл стилей main.styl (находится в папке app/styl/main.styl) в файл main.css (dist/css/main.css), любые другие *.styl файлы генерироваться не будут - они должны быть импортированы в основной файл main.styl



Возможные проблемы и их решения

Как и в случае с Pug, основная ошибка - это использование в отступах Пробелы вместо Tab. В качестве отступов можно использовать или только Пробелы или только Tab.

Решить эту проблему очень просто. В редакторе кода, в файле main.styl или в любом другом *.styl файле выделяем весь код сочетанием клавиш Ctrl+A и ищем настройку Tab size (в Sublime Text и VS Code справа внизу), нажимаем и выбираем пункт Convert Indentation to Tabs - все отступы конвентируются в Tab



Итоги

Stylus значительно ускоряет процесс верстки, за счет краткого, логичного и понятного синтаксиса, использования переменных, миксинов, операторов, встроенных функций, импортов и прочего. В статье рассмотрены только базовые возможности, но даже эти возможности экономят много времени

Итоговый CSS файл получается чистым, корректно сформатированым, где не будет разных отступов, пропущенных фигурных скобок, двоеточий и точек с запятой ;)



Полезные ссылки

Официальный сайт Stylus

Страница плагина gulp-stylus