Sass/SCSS
Sass/SCSS за 13 минут: переменные, вложенность, миксины, функции, @extend, циклы, мапы, интерполяция и партиалы — весь препроцессор в комментированном коде.
Sass — это препроцессор CSS: вы пишете на расширенном языке с переменными, вложенностью, миксинами и циклами, а компилятор превращает его в обычный CSS, который понимают браузеры. SCSS — самый популярный синтаксис Sass: это надмножество CSS, поэтому любой валидный CSS — уже валидный SCSS. Весь препроцессор уместился на одной странице — читайте код сверху вниз.
Что такое Sass/SCSS
SCSS-файлы имеют расширение .scss и компилируются в .css. У SCSS два вида комментариев.
// Однострочный комментарий: НЕ попадёт в скомпилированный CSS.
/* Многострочный комментарий: попадёт в CSS (если не сжатие). */
// SCSS — надмножество CSS: обычные правила работают как есть.
body {
margin: 0;
font-family: sans-serif;
}
// Компиляция через CLI Dart Sass:
// sass input.scss output.css
// sass --watch src:dist // следить за изменениями
Переменные
Переменная начинается с $. Значения бывают разных типов: числа, строки, цвета, булевы, списки, мапы, null.
// Объявление переменных разных типов:
$primary: #3498db; // цвет
$base-font: 16px; // число с единицей
$ratio: 1.5; // число
$font-stack: "Inter", sans-serif; // строка/список
$enabled: true; // булево
$gap: null; // пустое значение
// Список (через пробел или запятую):
$sizes: 8px 16px 24px;
// Использование:
.button {
background: $primary;
font: #{$base-font} / $ratio $font-stack;
}
// !default — задать значение, только если переменная ещё не определена
// (удобно для настраиваемых библиотек):
$radius: 4px !default;
Вложенность (nesting)
Селекторы можно вкладывать друг в друга. Символ & ссылается на родительский селектор.
.card {
padding: 16px;
border: 1px solid #ddd;
// Вложенный селектор → .card .title
.title {
font-weight: bold;
}
// & = родитель. Это → .card:hover
&:hover {
border-color: #999;
}
// & справа → .theme-dark .card
.theme-dark & {
background: #222;
}
// Конкатенация имени → .card__footer (БЭМ)
&__footer {
margin-top: 8px;
}
// Вложенность свойств с общим префиксом:
font: {
size: 14px;
weight: 400;
}
}
Миксины (@mixin / @include)
Миксин — переиспользуемый блок объявлений. Объявляется через @mixin, подключается через @include, принимает аргументы.
// Миксин без аргументов:
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
// С аргументами и значением по умолчанию:
@mixin button($bg, $color: white, $pad: 8px 16px) {
background: $bg;
color: $color;
padding: $pad;
border: none;
}
.modal {
@include flex-center;
}
.btn-primary {
// По позиции и по имени:
@include button(#3498db, $pad: 12px 24px);
}
// @content — вставить произвольный блок внутрь миксина:
@mixin on-hover {
&:hover { @content; }
}
.link {
@include on-hover {
color: red; // этот блок попадёт внутрь &:hover
}
}
Функции (@function и встроенные)
Функция возвращает значение через @return. Sass даёт много встроенных функций для цветов, чисел и строк.
// Своя функция:
@function rem($px, $base: 16px) {
@return ($px / $base) * 1rem;
}
.text {
font-size: rem(24px); // → 1.5rem
}
// Встроенные функции для цвета:
$base: #3498db;
.box {
border-color: darken($base, 10%); // темнее
background: lighten($base, 30%); // светлее
color: rgba($base, 0.5); // прозрачность
outline-color: mix($base, red, 50%); // смешать два цвета
}
// Числовые и строковые:
// percentage(0.4) → 40%
// round(4.6) → 5
// to-upper-case("abc") → "ABC"
Наследование (@extend и плейсхолдеры)
@extend переносит стили одного селектора в другой. Плейсхолдер %name не компилируется сам по себе — только когда его расширяют.
// Плейсхолдер: в CSS попадёт, только если его @extend'нут
%card-base {
padding: 16px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.info-card {
@extend %card-base;
background: #e8f4fd;
}
.warn-card {
@extend %card-base; // селекторы сгруппируются в общем правиле
background: #fff3cd;
}
// @extend можно применять и к обычным классам:
.message { padding: 10px; }
.error {
@extend .message;
color: red;
}
Управляющие директивы (@if, @for, @each, @while)
Условия и циклы позволяют генерировать стили программно.
// Условие:
@mixin theme($dark) {
@if $dark {
background: #111;
color: #eee;
} @else {
background: #fff;
color: #111;
}
}
// Цикл @for (генерируем сетку отступов):
@for $i from 1 through 5 {
.m-#{$i} { margin: #{$i * 4}px; }
}
// Цикл @each по списку:
$colors: red, green, blue;
@each $name in $colors {
.text-#{$name} { color: $name; }
}
// @each с распаковкой пар:
@each $name, $hex in ("primary": #3498db, "danger": #e74c3c) {
.bg-#{$name} { background: $hex; }
}
// Цикл @while:
$i: 1;
@while $i <= 3 {
.col-#{$i} { width: $i * 10%; }
$i: $i + 1;
}
Операции
Sass умеет арифметику с числами и единицами, сравнения и конкатенацию строк.
.layout {
// Математика:
width: 100% - 20px; // вычитание
height: 200px / 2; // деление → 100px
margin: 8px * 3; // умножение → 24px
padding: 10px + 5px; // сложение → 15px
// Сравнения дают true/false:
// 10px > 5px → true, $a == $b, $a != $b
// Логические операторы: and, or, not
}
// Конкатенация строк через +:
$dir: "images";
.hero {
background: url("/" + $dir + "/bg.png");
}
// Внимание: в современном Dart Sass деление / устарело —
// используйте функцию math.div($a, $b) из модуля sass:math.
Партиалы и импорт (@use / @import)
Код разбивают на частичные файлы — партиалы, имена которых начинаются с подчёркивания (_colors.scss). Подключают современным @use (или устаревшим @import).
// Файл _variables.scss (партиал, отдельно не компилируется):
$primary: #3498db;
$radius: 4px;
// Файл main.scss — современный способ @use:
@use "variables"; // подключаем партиал
@use "sass:math"; // встроенный модуль
.button {
// К членам модуля обращаемся через пространство имён:
background: variables.$primary;
border-radius: variables.$radius;
width: math.div(100%, 3);
}
// Можно задать псевдоним или убрать префикс:
@use "variables" as v; // v.$primary
@use "variables" as *; // без префикса: $primary
// Устаревший способ (всё в глобальной области, дубли):
// @import "variables";
Мапы (map)
Мапа — набор пар ключ: значение. Доступ к значению — через map-get (или map.get из модуля sass:map).
// Объявление мапы:
$breakpoints: (
"mobile": 480px,
"tablet": 768px,
"desktop": 1200px,
);
$theme: (
"bg": #ffffff,
"text": #222222,
);
.container {
// Достаём значение по ключу:
background: map-get($theme, "bg");
color: map-get($theme, "text");
}
// Перебор мапы циклом:
@each $name, $size in $breakpoints {
// ...используем $name и $size
}
// Полезные функции:
// map-has-key($map, "mobile") → true/false
// map-keys($map) → список ключей
// map-values($map) → список значений
Интерполяция #{}
Конструкция #{ } вставляет значение переменной или выражения в строку — в имена селекторов, свойств, медиа-запросов и URL.
$side: "left";
$size: 16;
.box {
// Интерполяция в имени свойства:
margin-#{$side}: 8px; // → margin-left
// В значении со строкой:
content: "Размер: #{$size}px";
// Склейка числа с единицей:
width: #{$size}px; // → 16px
}
// В имени селектора:
$name: "alert";
.#{$name}-box { padding: 12px; } // → .alert-box
// В медиа-запросе (без интерполяции переменная не подставится):
$bp: 768px;
@media (min-width: #{$bp}) {
.nav { display: flex; }
}
Практический пример: адаптивные миксины и тема
Соберём всё вместе: мапа брейкпоинтов, миксин медиа-запроса, мапа темы и генерация утилит циклом.
@use "sass:map";
// 1) Брейкпоинты и миксин адаптивности:
$breakpoints: (
"sm": 576px,
"md": 768px,
"lg": 992px,
);
@mixin respond($key) {
$value: map.get($breakpoints, $key);
@if $value {
@media (min-width: #{$value}) {
@content; // блок-стили попадут внутрь медиа-запроса
}
} @else {
@warn "Нет брейкпоинта: #{$key}";
}
}
// 2) Тема как мапа + функция-аксессор:
$theme: (
"primary": #3498db,
"danger": #e74c3c,
"text": #222222,
);
@function color($key) {
@return map.get($theme, $key);
}
// 3) Применяем адаптивность и тему:
.container {
padding: 16px;
color: color("text");
@include respond("md") {
padding: 32px; // только на экранах >= 768px
}
}
// 4) Генерируем кнопки по теме циклом:
@each $name, $hex in $theme {
.btn-#{$name} {
background: $hex;
color: white;
&:hover {
background: darken($hex, 8%);
}
}
}