← HTML & CSS/SASS/SCSS: препроцессоры CSS#39 из 383← ПредыдущийСледующий →+20 XP
Полезно по теме:Гайд: старт в frontendПрактика: DOM и событияТермин: DOMМаршрут: старт с нуля

SASS/SCSS: препроцессоры CSS

SCSS — это «CSS с суперсилами»: переменные до того, как они появились в CSS, вложение, миксины, циклы, функции. SCSS компилируется в обычный CSS. Несмотря на то, что CSS custom properties закрыли некоторые задачи, SCSS остаётся стандартом в крупных проектах.

Переменные

// SCSS переменные — компайл-таймные, не runtime
$color-primary: #7b2ff7;
$color-text: #1a202c;
$font-size-base: 16px;
$spacing-unit: 8px;

// Использование
.button {
  background: $color-primary;
  font-size: $font-size-base;
  padding: $spacing-unit * 2 $spacing-unit * 3;  // 16px 24px
}

CSS custom properties: изменяются в runtime. SCSS-переменные: подставляются при компиляции. Часто используют оба.

Вложение (Nesting)

.card {
  padding: 16px;
  border-radius: 8px;

  // Дочерние элементы
  &__title {       // & = .card → .card__title
    font-size: 20px;
    font-weight: 700;
  }

  &__body {
    color: #666;
  }

  // Псевдоклассы и псевдоэлементы
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(0,0,0,0.1);
  }

  &::before {
    content: '';
    display: block;
  }

  // Медиа-запросы внутри компонента
  @media (max-width: 768px) {
    padding: 12px;
  }
}

Миксины (@mixin / @include)

// Определение
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

@mixin button-variant($bg, $color: white) {
  background: $bg;
  color: $color;
  border: none;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;

  &:hover {
    background: darken($bg, 10%);
  }
}

// Использование
.hero {
  @include flex-center;
  min-height: 100vh;
}

.button--primary {
  @include button-variant(#7b2ff7);
}

.button--danger {
  @include button-variant(#ef4444, white);
}

@extend — наследование

%button-base {
  display: inline-flex;
  align-items: center;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
}

.button {
  @extend %button-base;
  background: #7b2ff7;
  color: white;
}

.button--outline {
  @extend %button-base;
  background: transparent;
  border: 2px solid #7b2ff7;
  color: #7b2ff7;
}

%placeholder — не попадает в CSS сам по себе, только через @extend.

@use и @forward — модульная система

// _variables.scss
$color-primary: #7b2ff7;

// _mixins.scss
@use 'variables' as vars;

@mixin button {
  background: vars.$color-primary;
}

// main.scss
@use 'variables' as v;
@use 'mixins' as m;

.button {
  background: v.$color-primary;
  @include m.button;
}

Циклы и условия

// @each — перебор значений
$colors: ('primary': #7b2ff7, 'danger': #ef4444, 'success': #10b981);

@each $name, $color in $colors {
  .button--#{$name} {
    background: $color;
    color: white;
  }
}

// @for — числовые циклы
@for $i from 1 through 12 {
  .col-#{$i} {
    width: percentage($i / 12);
  }
}

// Генерирует: .col-1 { width: 8.333% } ... .col-12 { width: 100% }

Функции

@function rem($px, $base: 16) {
  @return #{$px / $base}rem;
}

@function spacing($multiplier) {
  @return $multiplier * 8px;
}

.container {
  padding: rem(24);         // 1.5rem
  margin-bottom: spacing(3); // 24px
}

Примеры

Симуляция SCSS-компилятора: подстановка переменных и выравнивание вложенности

// Мини SCSS → CSS компилятор
function compileSimpleSCSS(scss) {
  const variables = {}
  const output = []

  // Шаг 1: Собираем переменные ($name: value;)
  const varRegex = /\$([\w-]+):\s*([^;]+);/g
  let match
  while ((match = varRegex.exec(scss)) !== null) {
    variables[match[1]] = match[2].trim()
  }

  // Шаг 2: Подставляем переменные
  let processed = scss.replace(/\$([\w-]+)/g, (_, name) => {
    return variables[name] || `\$${name}`
  })

  // Шаг 3: Убираем строки с объявлением переменных
  processed = processed.replace(/\$[\w-]+:\s*[^;]+;\n?/g, '')

  // Шаг 4: Раскрываем однуровневое вложение
  function flattenNesting(css) {
    const result = []
    const lines = css.split('\n')
    let parentSelector = ''
    let inNested = false
    let depth = 0
    let nestedLines = []

    for (const rawLine of lines) {
      const line = rawLine.trim()
      if (!line) continue

      depth += (line.match(/\{/g) || []).length
      depth -= (line.match(/\}/g) || []).length

      if (depth === 0 && line.endsWith('{')) {
        parentSelector = line.slice(0, -1).trim()
        result.push(`${parentSelector} {`)
        inNested = false
      } else if (line.startsWith('&')) {
        // Псевдоэлемент или класс через &
        inNested = true
        const childSel = line.replace('&', parentSelector).replace(' {', '')
        result.push(`  /* nested → ${childSel} */`)
      } else if (depth >= 0 && line === '}') {
        result.push('}')
      } else if (depth > 0) {
        result.push(`  ${line}`)
      } else {
        result.push(line)
      }
    }
    return result.join('\n')
  }

  const flattened = flattenNesting(processed)
  console.log('=== Переменные ===')
  console.log(variables)
  console.log('\n=== Скомпилированный CSS ===')
  console.log(flattened)
  return flattened
}

const scss = `
\$color-primary: #7b2ff7;
\$spacing: 16px;
\$border-radius: 8px;

.card {
  padding: \$spacing;
  border-radius: \$border-radius;
  background: white;
}

.button {
  background: \$color-primary;
  padding: 8px \$spacing;
  border-radius: \$border-radius;
}
`

compileSimpleSCSS(scss)

SASS/SCSS: препроцессоры CSS

SCSS — это «CSS с суперсилами»: переменные до того, как они появились в CSS, вложение, миксины, циклы, функции. SCSS компилируется в обычный CSS. Несмотря на то, что CSS custom properties закрыли некоторые задачи, SCSS остаётся стандартом в крупных проектах.

Переменные

// SCSS переменные — компайл-таймные, не runtime
$color-primary: #7b2ff7;
$color-text: #1a202c;
$font-size-base: 16px;
$spacing-unit: 8px;

// Использование
.button {
  background: $color-primary;
  font-size: $font-size-base;
  padding: $spacing-unit * 2 $spacing-unit * 3;  // 16px 24px
}

CSS custom properties: изменяются в runtime. SCSS-переменные: подставляются при компиляции. Часто используют оба.

Вложение (Nesting)

.card {
  padding: 16px;
  border-radius: 8px;

  // Дочерние элементы
  &__title {       // & = .card → .card__title
    font-size: 20px;
    font-weight: 700;
  }

  &__body {
    color: #666;
  }

  // Псевдоклассы и псевдоэлементы
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 24px rgba(0,0,0,0.1);
  }

  &::before {
    content: '';
    display: block;
  }

  // Медиа-запросы внутри компонента
  @media (max-width: 768px) {
    padding: 12px;
  }
}

Миксины (@mixin / @include)

// Определение
@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

@mixin button-variant($bg, $color: white) {
  background: $bg;
  color: $color;
  border: none;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;

  &:hover {
    background: darken($bg, 10%);
  }
}

// Использование
.hero {
  @include flex-center;
  min-height: 100vh;
}

.button--primary {
  @include button-variant(#7b2ff7);
}

.button--danger {
  @include button-variant(#ef4444, white);
}

@extend — наследование

%button-base {
  display: inline-flex;
  align-items: center;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
}

.button {
  @extend %button-base;
  background: #7b2ff7;
  color: white;
}

.button--outline {
  @extend %button-base;
  background: transparent;
  border: 2px solid #7b2ff7;
  color: #7b2ff7;
}

%placeholder — не попадает в CSS сам по себе, только через @extend.

@use и @forward — модульная система

// _variables.scss
$color-primary: #7b2ff7;

// _mixins.scss
@use 'variables' as vars;

@mixin button {
  background: vars.$color-primary;
}

// main.scss
@use 'variables' as v;
@use 'mixins' as m;

.button {
  background: v.$color-primary;
  @include m.button;
}

Циклы и условия

// @each — перебор значений
$colors: ('primary': #7b2ff7, 'danger': #ef4444, 'success': #10b981);

@each $name, $color in $colors {
  .button--#{$name} {
    background: $color;
    color: white;
  }
}

// @for — числовые циклы
@for $i from 1 through 12 {
  .col-#{$i} {
    width: percentage($i / 12);
  }
}

// Генерирует: .col-1 { width: 8.333% } ... .col-12 { width: 100% }

Функции

@function rem($px, $base: 16) {
  @return #{$px / $base}rem;
}

@function spacing($multiplier) {
  @return $multiplier * 8px;
}

.container {
  padding: rem(24);         // 1.5rem
  margin-bottom: spacing(3); // 24px
}

Примеры

Симуляция SCSS-компилятора: подстановка переменных и выравнивание вложенности

// Мини SCSS → CSS компилятор
function compileSimpleSCSS(scss) {
  const variables = {}
  const output = []

  // Шаг 1: Собираем переменные ($name: value;)
  const varRegex = /\$([\w-]+):\s*([^;]+);/g
  let match
  while ((match = varRegex.exec(scss)) !== null) {
    variables[match[1]] = match[2].trim()
  }

  // Шаг 2: Подставляем переменные
  let processed = scss.replace(/\$([\w-]+)/g, (_, name) => {
    return variables[name] || `\$${name}`
  })

  // Шаг 3: Убираем строки с объявлением переменных
  processed = processed.replace(/\$[\w-]+:\s*[^;]+;\n?/g, '')

  // Шаг 4: Раскрываем однуровневое вложение
  function flattenNesting(css) {
    const result = []
    const lines = css.split('\n')
    let parentSelector = ''
    let inNested = false
    let depth = 0
    let nestedLines = []

    for (const rawLine of lines) {
      const line = rawLine.trim()
      if (!line) continue

      depth += (line.match(/\{/g) || []).length
      depth -= (line.match(/\}/g) || []).length

      if (depth === 0 && line.endsWith('{')) {
        parentSelector = line.slice(0, -1).trim()
        result.push(`${parentSelector} {`)
        inNested = false
      } else if (line.startsWith('&')) {
        // Псевдоэлемент или класс через &
        inNested = true
        const childSel = line.replace('&', parentSelector).replace(' {', '')
        result.push(`  /* nested → ${childSel} */`)
      } else if (depth >= 0 && line === '}') {
        result.push('}')
      } else if (depth > 0) {
        result.push(`  ${line}`)
      } else {
        result.push(line)
      }
    }
    return result.join('\n')
  }

  const flattened = flattenNesting(processed)
  console.log('=== Переменные ===')
  console.log(variables)
  console.log('\n=== Скомпилированный CSS ===')
  console.log(flattened)
  return flattened
}

const scss = `
\$color-primary: #7b2ff7;
\$spacing: 16px;
\$border-radius: 8px;

.card {
  padding: \$spacing;
  border-radius: \$border-radius;
  background: white;
}

.button {
  background: \$color-primary;
  padding: 8px \$spacing;
  border-radius: \$border-radius;
}
`

compileSimpleSCSS(scss)

Задание

Напиши CSS, который имитирует то, что сгенерировал бы SCSS-компилятор. Представь, что у тебя есть SCSS с переменными `$primary`, `$spacing`, `$radius` и вложенными правилами. Напиши итоговый CSS вручную: карточку со стилями, псевдоклассом `:hover` и адаптивным поведением на узких экранах.

Подсказка

SCSS `$primary: #7b2ff7` компилируется в подстановку значения везде. `$spacing: 16px`, `$spacing / 2` = `8px`. `&:hover` → `.card:hover`. `&__title` → `.card__title`. `@media` раскрывается как обычный медиа-запрос.

Загружаем среду выполнения...
Загружаем AI-помощника...