Привет, друзья!
В данной статье мы разработаем простую, но относительно полноценную дизайн-систему для веб-приложения средствами Sass.
Почему Sass
? Потому что, кроме полной поддержки CSS
, Sass
предоставляет несколько интересных инструментов, позволяющих существенно сократить шаблонный код, в чем вы сами скоро убедитесь. На мой взгляд, несмотря на стремительное развитие CSS
в последние годы, Sass
продолжает оставаться актуальным, по крайней мере, при работе над серьезными проектами.
При разработке дизайн-системы в части терминологии, названий, значений переменных и т.п. я буду ориентироваться, в основном, на Bootstrap и немного на Tailwind.
Если вам это интересно, прошу под кат.
Для работы с зависимостями будет использоваться Yarn.
Для тестирования дизайн-системы можно воспользоваться одним из готовых шаблонов приложений, предоставляемых Vite, например:
# создаем шаблон приложения
# sass-style - название приложения и директории проекта
# --template vanilla - используемый шаблон
yarn create vite sass-style --template vanilla
# переходим в созданную директорию и устанавливаем зависимости
cd sass-style && yarn
# устанавливаем `sass` в качестве зависимости для разработки
yarn add -D sass
# запускаем приложение в режиме разработки
yarn dev
Создаем в корне проекта директорию styles
и в ней — файл index.scss
. Это основной файл стилей нашего приложения. Убедитесь, что он импортируется в файле main.js
:
import "./styles/index.scss";
Сброс стилей
Реализуем минимальный сброс стилей.
Создаем директорию styles/utils
и в ней — файл _reset.scss
следующего содержания:
*,
*::before,
*::after {
box-sizing: border-box;
}
body,
h1,
h2,
h3,
h4,
h5,
p,
figure,
picture {
margin: 0;
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
font-weight: 400;
}
img,
picture {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
}
}
Импортируем этот файл в index.scss
и определяем еще парочку дефолтных стилей для html
и body
:
@import "./utils/reset";
html {
// о переменных см. ниже
font-size: $fs-base;
}
body {
font-family: Helvetica, sans-serif;
color: $dark;
line-height: 1.5;
}
Переменные / Variables
Создаем файл styles/_vars.scss
и определяем в нем несколько переменных:
// минимальная и максимальная ширина области просмотра
$min-w: 375px;
$max-w: 1536px;
// отступы
$spacer-base: 30px;
$spacer-xxs: 0.25 * $spacer-base;
$spacer-xs: 0.5 * $spacer-base;
$spacer-s: 0.75 * $spacer-base;
$spacer-m: $spacer-base;
$spacer-l: 1.5 * $spacer-base;
$spacer-xl: 2 * $spacer-base;
$spacer-xxl: 2.25 * $spacer-base;
// шрифты
$fs-base: 16px;
$fs-xxs: 11px;
$fs-xs: 12px;
$fs-s: 14px;
$fs-m: $fs-base;
$fs-l: 22px;
$fs-xl: 24px;
$fs-xxl: 26px;
// цвета
// bootstrap
$primary: #0275d8;
$success: #5cb85c;
$info: #5bc0de;
$warning: #f0ad4e;
$danger: #d9534f;
$light: #f7f7f7;
$dark: #292b2c;
// дополнительно
$gray100: #fefefe;
$gray200: #fbfbfb;
$gray300: #fafafa;
$gray400: #f3f3f3;
$gray500: #eeeeee;
$gray600: #a3a3a3;
$gray700: #8c8c8c;
$gray800: #555555;
$gray900: #333333;
$white: #fff;
$black: #000;
// контрольные точки
// bootstrap
$sm: 576px;
$md: 768px;
$lg: 992px;
$xl: 1200px;
$xxl: 1400px;
Импортируем этот файл в начале index.scss
:
@import "./vars";
Это делает переменные доступными глобально — переменные будут доступны всем импортируемым в index.scss
модулям.
Подробнее о переменных Sass
можно почитать здесь.
Отступы
Создаем файл utils/_spacing.scss
следующего содержания:
// перечисление отступов
$spacers: (
"0": 0,
xxs: $spacer-xxs,
xs: $spacer-xs,
s: $spacer-s,
m: $spacer-m,
l: $spacer-l,
xl: $spacer-xl,
xxl: $spacer-xxl
);
// типы отступов
$types: (
"m": "margin",
"p": "padding"
);
// стороны
$sides: (
"": "",
t: "-top",
r: "-right",
b: "-bottom",
l: "-left"
);
// перебираем перечисление
@each $key-spacer, $factor in $spacers {
// перебираем типы
@each $key-type, $type in $types {
// перебираем стороны
@each $key-side, $side in $sides {
// для всех и каждой стороны
.#{$key-type}#{$key-side}-#{$key-spacer} {
#{$type}#{$side}: $factor;
}
}
// для горизонтального отступа
.#{$key-type}x-#{$key-spacer} {
#{$type}-left: $factor;
#{$type}-right: $factor;
}
// для вертикального отступа
.#{$key-type}y-#{$key-spacer} {
#{$type}-bottom: $factor;
#{$type}-top: $factor;
}
}
// дополнительно
// пространство между гридами или флексами
.gap-#{$key-spacer} {
gap: $factor;
}
}
О директиве @each
можно почитать здесь.
Это позволяет определять внешние (margin) и внутренние (padding) отступы следующим образом:
m
илиp
(в дальнейшем паддинг предполагается) — для отступов по всем сторонам, внешним или внутренним, соответственно. Например,m-xs
компилируется в:
margin-top: 15px;
margin-right: 15px;
margin-bottom: 15px;
margin-left: 15px;
mx/my
— для отступов по горизонтали/вертикали. Например,mx-s
компилируется в:
margin-right: 15px;
margin-left: 15px;
mt/mr/mb/ml
— для отступов с соответствующей стороны. Например,mt-s
компилируется в:
margin-top: 15px;
Цвета
Создаем файл utils/_colors.scss
следующего содержания:
// перечисление цветов
$colors: (
'gray100': $gray100,
'gray200': $gray200,
'gray300': $gray300,
'gray400': $gray400,
'gray500': $gray500,
'gray600': $gray600,
'gray700': $gray700,
'gray800': $gray800,
'gray900': $gray900,
'primary': $primary,
'success': $success,
'info': $info,
'warning': $warning,
'danger': $danger,
'light': $light,
'dark': $dark,
'white': $white,
'black': $black
);
// перебираем перечисление
@each $name, $color in $colors {
// цвет текста
.color-#{$name} {
color: $color;
}
// цвет фона
.bg-#{$name} {
background-color: $color;
}
// заливка `svg`
.fill-#{$name} {
svg {
color: $color;
}
}
}
Это позволяет определять цвет текста, фона или заливки для SVG
. Например, color-primary
компилируется в:
color: #0275d8;
bg-success
в:
background-color: #5cb85c;
Размеры шрифтов и блоков
Создаем файл utils/_font.scss
следующего содержания:
// перечисление размеров шрифта
$font-sizes: (
"0": 0,
xxs: $fs-xxs,
xs: $fs-xs,
s: $fs-s,
m: $fs-m,
l: $fs-l,
xl: $fs-xl,
xxl: $fs-xxl
);
// перебираем перечисление
@each $size, $factor in $font-sizes {
.fs-#{$size} {
font-size: $factor;
}
}
// перебираем возможные значения свойства `font-weight`
@each $v in 100, 200, 300, 400, 500, 600, 700, 800, 900 {
.fw-#{$v} {
font-weight: $v;
}
}
fs-s
компилируется в:
font-size: 14px;
fw-600
в:
font-weight: 600;
Создаем файл utils/_sizing.scss
следующего содержания:
@each $v in 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 {
.w-#{$v} {
width: calc(100% / 12 * $v);
}
.h-#{$v} {
height: calc(100% / 12 * $v);
}
}
// дополнительно
.w-full {
width: 100vw;
}
.h-full {
height: 100vh;
}
Это позволяет распределять ширину и высоту блоков по сетке, состоящей из 12 равных частей. Об этом можно думать так: сколько колонок/строк должен занимать блок? Например, w-4
компилируется в:
width: 33.3333333333%;
h-2
в:
height: 16.6666666667%;
Пример:
<div style="display: flex" class="w-full h-full">
<div class="w-2 h-4 bg-primary"></div>
<div class="w-6 h-4 bg-success"></div>
<div class="w-4 h-4 bg-danger"></div>
</div>
Результат:
Флекс
Создаем файл utils/_flex.scss
следующего содержания:
// justify-content
@each $v in start, end, center, stretch, between, around, evenly {
.justify-#{$v} {
@if ($v == start) or ($v == end) {
justify-content: flex-#{$v};
} @else if ($v == between) or ($v == around) or ($v == evenly) {
justify-content: space-#{$v};
} @else {
justify-content: $v;
}
}
}
// align-items
@each $v in start, end, center, stretch {
.items-#{$v} {
@if ($v == start) or ($v == end) {
align-items: flex-#{$v};
} @else {
align-items: $v;
}
}
}
// дополнительно
.flex-center {
// о миксинах см. ниже
@include flex-center();
}
.flex-center-column {
@include flex-center(column);
}
.flex-wrap {
flex-wrap: wrap;
}
.flex {
flex: 1;
}
При желании, таким же способом можно определить justify-items
, align-content
, justify-self
и align-self
.
Утилиты
Создаем файл utils/_utils.scss
следующего содержания:
// display
@each $v in none, inline, block, inline-block, flex, inline-flex, grid,
inline-grid
{
.d-#{$v} {
display: $v;
}
}
// position
@each $v in relative, absolute, fixed, sticky {
.p-#{$v} {
position: $v;
}
}
// контент для читалок
// visually-hidden
.sr-only {
background: none;
border: none;
color: none;
cursor: none;
height: 0;
margin: 0;
opacity: 0;
outline: none;
overflow: hidden;
padding: 0;
pointer-events: none;
position: absolute;
user-select: none;
visibility: hidden;
white-space: nowrap;
width: 0;
z-index: -1;
}
// дополнительно
.text-overflow {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.text-center {
text-align: center;
}
// ваши классы-утилиты
Перепишем последний пример:
<div class="w-full h-full d-flex gap-xxs">
<div class="w-2 h-4 bg-primary flex-center">
<p class="fs-xl color-success">box#1</p>
</div>
<div class="w-6 h-4 bg-success d-flex justify-end items-end">
<p class="pr-s fs-xl color-primary">box#2</p>
</div>
<div class="w-4 h-4 bg-danger d-flex items-end">
<p class="pl-s fs-xl color-gray300">box#3</p>
</div>
</div>
Результат:
Не забываем импортировать все утилиты в index.scss
.
На этом с основами дизайн-системы мы закончили. Остальное зависит от потребностей конкретного приложения и личных предпочтений разработчика.
Бонус
Функции / Functions
В числе прочего, Sass
позволяет создавать функции для выполнения операций над значениями свойств CSS
.
Определим функцию для преобразования px
в rem
.
Создаем файл styles/_fns.scss
следующего содержания:
@function strip-unit($v) {
@return $v / ($v * 0 + 1);
}
@function rem($v) {
@return #{strip-unit($v / $fs-base)} + rem;
}
Функция strip-unit
позволяет игнорировать px
в передаваемом функции значении.
Пример использования функции rem
:
p {
font-size: rem(16);
// или
font-size: rem(16px);
// или
font-size: rem($fs-m);
}
// после компиляции
p {
font-size: 0.8888888889rem
}
Подробнее о функциях можно почитать здесь.
Миксины / Mixins
Миксин — это блок готовых стилей, которые включаются в другие стили.
Миксин определяется с помощью директивы @mixin
и внедряется с помощью директивы @include
.
Создаем файл styles/_mixins.scss
.
Определяем миксин для выравнивания элемента по центру с помощью display: flex
:
@mixin flex-center($d: row) {
display: flex;
justify-content: center;
align-items: center;
@if ($d == column) {
flex-direction: column;
}
}
Мы видели пример использования данного миксина в utils/_flex.scss
.
О директивах @if
и @else
можно почитать здесь.
Определяем миксин для стилизации плейсхолдера:
@mixin placeholder {
&.placeholder {
@content;
}
&:-moz-placeholder {
@content;
}
&::-moz-placeholder {
@content;
}
&:-ms-input-placeholder {
@content;
}
&::-webkit-input-placeholder {
@content;
}
}
Директива-переменная @content
содержит стили, передаваемые миксину.
Пример использования:
input {
@include placeholder {
color: $primary;
font-size: 0.8rem;
opacity: 0.8;
}
}
Результат после компиляции:
input.placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}
input:-moz-placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}
input::-moz-placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}
input:-ms-input-placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}
input::-webkit-input-placeholder {
color: #0275d8;
font-size: 0.8rem;
opacity: 0.8;
}
Определяем миксин для стилизации прокрутки:
@mixin scrollbar($scrollbar-width, $thumb-color, $track-color) {
&::-webkit-scrollbar {
width: #{$scrollbar-width} + px;
}
&::-webkit-scrollbar-track {
background-color: $track-color;
}
&::-webkit-scrollbar-thumb {
background-color: $thumb-color;
}
// firefox
& {
@if ($scrollbar-width == 0) {
scrollbar-width: none;
// приблизительное значение
} @else if ($scrollbar-width < 19) {
scrollbar-width: thin;
} @else {
scrollbar-width: auto;
}
scrollbar-color: $thumb-color $track-color;
}
}
Пример использования:
@use "sass:color";
html {
@include scrollbar(12, lighten($primary, 25), $gray500);
}
Функция lighten
из встроенного модуля sass:color
позволяет осветлять цвета.
Определяем миксины — альтернативу медиа-запросам:
// перечисление контрольных точек
$breakpoints: (
"sm": $sm,
"md": $md,
"lg": $lg,
"xl": $xl,
"xxl": $xxl
);
// от указанной точки и шире
@mixin up($b) {
@if map-has-key($breakpoints, $b) {
$v: map-get($breakpoints, $b);
@media (min-width: $v) {
@content;
}
}
}
// от указанной точки и уже
@mixin down($b) {
@if map-has-key($breakpoints, $b) {
$v: map-get($breakpoints, $b);
@media (max-width: $v) {
@content;
}
}
}
Пример использования up
:
h1 {
font-size: rem(18);
// > 768px
@include up(md) {
font-size: rem(24);
font-weight: bold;
}
// > 1200px
@include up(xl) {
font-size: rem(30);
}
}
В качестве последнего упражнения — миксин для изменения размера шрифта пропорционально изменению ширины области просмотра:
@mixin fluid-fs($min-w, $max-w, $min-fs, $max-fs) {
$u1: unit($min-w);
$u2: unit($max-w);
$u3: unit($min-fs);
$u4: unit($max-fs);
@if $u1 == $u2 and $u1 == $u3 and $u1 == $u4 {
& {
font-size: $min-fs;
@media (min-width: $min-w) {
font-size: calc(
#{$min-fs} + #{strip-unit($max-fs - $min-fs)} *
((100vw - #{$min-w}) / #{strip-unit($max-w - $min-w)})
);
}
@media (min-width: $max-w) {
font-size: $max-fs;
}
}
}
}
Пример использования:
html {
@include fluid-fs($min-w, $max-w, $fs-xs, $fs-xl);
}
При изменении ширины области просмотра размер шрифта элемента html
(:root
) будет пропорционально меняться от 12
до 24px
. В случае применения единиц измерения rem
, размер шрифта вычисляется от базового, т.е. размера шрифта html
. Таким образом, заранее позаботившись об установке размеров в rem
или применении одноименной функции, можно автоматически получить гибкую систему шрифтов во всем приложении.
Пожалуй, это все, чем я хотел поделиться с вами в данной статье. Надеюсь, вы узнали для себя что-то новое и не зря потратили время.
Благодарю за внимание и happy coding!
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩