Как стать автором
Обновить
2119.67
Timeweb Cloud
То самое облако

Sass: разрабатываем дизайн-систему

Время на прочтение9 мин
Количество просмотров13K


Привет, друзья!


В данной статье мы разработаем простую, но относительно полноценную дизайн-систему для веб-приложения средствами Sass.


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


При разработке дизайн-системы в части терминологии, названий, значений переменных и т.п. я буду ориентироваться, в основном, на Bootstrap и немного на Tailwind.


Код проекта на GitHub.


Если вам это интересно, прошу под кат.


Для работы с зависимостями будет использоваться 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-канале

Теги:
Хабы:
Всего голосов 11: ↑11 и ↓0+11
Комментарии9

Публикации

Информация

Сайт
timeweb.cloud
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия
Представитель
Timeweb Cloud

Истории