Другое

Sass @custom-media не работает: решение проблемы

Узнайте, почему Sass не поддерживает @custom-media и как создать кастомные медиа-запросы с помощью миксинов, PostCSS и других методов.

Sass не может обработать новый at‑rule @custom-media: что делать?

Я пытаюсь добавить кастомные медиа‑запросы в свой проект с использованием синтаксиса:

scss
@custom-media --phone-small (width >= #{$breakpoint-tablet-small})

Однако Sass выдает ошибку ({ expected scss(css-lcurlyexpected) как в редакторе, так и при сборке проекта. Sass не распознает этот at‑rule. Как правильно использовать @custom-media в Sass, или есть ли альтернативные способы создания кастомных медиа‑запросов?

Sass не поддерживает @custom-media at‑rule, так как эта функция пока не реализована ни в одной версии Sass. Основная проблема заключается в том, что @custom-media является частью спецификации CSS Level 4, которая ещё не полностью поддерживается в Sass, особенно при использовании сложных выражений с переменными. Вместо этого следует использовать альтернативные подходы с миксинами или PostCSS для создания кастомных медиа‑запросов.

Содержание

Почему Sass не поддерживает @custom-media

Основная причина, по которой Sass не может обработать @custom-media, заключается в том, что эта функция пока не реализована в нативной поддержке Sass. Как указано в исследованиях, на момент декабря 2021 года:

“As of now (Dec 2021), it appears that, while in the spec, @custom-media has not been implemented anywhere yet. Neither caniuse nor MDN know about @custom-media.” [Stack Overflow]

Sass имеет ограничения в поддержке CSS Media Queries Level 4, особенно при использовании:

  • Range context (например, width >= 320px)
  • Сложных выражений с переменными
  • Пользовательских свойств в медиа‑запросах

Даже в последних версиях Dart Sass сохраняются проблемы с синтаксисом диапазонов:

“LibSass and older versions of Dart Sass and Ruby Sass don’t support media queries with features written in a range context.” [Sass Documentation]

Основные ошибки при использовании @custom-media

Ошибка ({ expected scss(css-lcurlyexpected) возникает из‑за нескольких причин:

1. Неподдерживаемый синтаксис диапазона

scss
// НЕ РАБОТАЕТ
@custom-media --phone-small (width >= #{$breakpoint-tablet-small})

Sass не распознает операторы >=, <=, <, > в контексте медиа‑запросов без правильной обработки.

2. Проблемы с переменными

scss
// НЕ РАБОТАЕТ
$breakpoint: 768px;
@custom-media --mobile (max-width: $breakpoint);

Sass не может подставить переменные напрямую в @custom-media правила.

3. Синтаксические ошибки в миксинах

При попытке создать миксины для @custom-media возникают ошибки компиляции:

scss
// ВЫЗЫВАЕТ ОШИБКУ
@mixin custom-media($breakpoints) {
  @each $breakpoint in $breakpoint-names {
    @custom-media --#{$breakpoint}-media #{$media-query};
  }
}

“the error is (max-width: 40em) isn’t a valid CSS value, despite that being exactly what i want the value to be. does sass not support the @custom-media syntax?” [Stack Overflow]

Альтернативные методы создания кастомных медиа‑запросов

1. Стандартные миксины для медиа‑запросов

scss
// Правильный подход с миксинами
@mixin mobile {
  @media (max-width: 768px) {
    @content;
  }
}

@mixin tablet {
  @media (min-width: 769px) and (max-width: 1024px) {
    @content;
  }
}

@mixin desktop {
  @media (min-width: 1025px) {
    @content;
  }
}

// Использование
.container {
  @include mobile {
    padding: 10px;
  }
}

2. Миксины с гибкими параметрами

scss
// Более гибкий подход
@mixin respond-to($breakpoint) {
  @if $breakpoint == mobile {
    @media (max-width: 768px) { @content; }
  }
  @if $breakpoint == tablet {
    @media (min-width: 769px) and (max-width: 1024px) { @content; }
  }
  @if $breakpoint == desktop {
    @media (min-width: 1025px) { @content; }
  }
}

// Использование
.header {
  @include respond-to(mobile) {
    font-size: 14px;
  }
}

3. Использование map для breakpoints

scss
// Организация через map
$breakpoints: (
  mobile: 768px,
  tablet: 1024px,
  desktop: 1200px
) !default;

@mixin media-up($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    $breakpoint-value: map.get($breakpoints, $breakpoint);
    @media (min-width: $breakpoint-value) {
      @content;
    }
  }
}

@mixin media-down($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    $breakpoint-value: map.get($breakpoints, $breakpoint);
    @media (max-width: ($breakpoint-value - 1px)) {
      @content;
    }
  }
}

// Использование
.hero {
  @include media-up(tablet) {
    grid-template-columns: 1fr 1fr;
  }
}

Решение с использованием PostCSS

Если вам действительно нужна функциональность @custom-media, лучшим решением является использование PostCSS с плагином postcss-custom-media.

Настройка PostCSS

Создайте файл postcss.config.js:

javascript
const customMediaUnprefixed = require('./src/hooks/useMedia/custom-media.js');

const customMediaCache = {
  customMedia: Object.assign(
    {},
    ...Object.keys(customMediaUnprefixed).map((key) => ({
      [`--${key}`]: customMediaUnprefixed[key]
    }))
  ),
};

const config = {
  plugins: [
    require('postcss-preset-env')({
      stage: 0,
      customProperties: false,
      browsers: ['> 0.5%, last 2 versions, Firefox ESR, not dead'],
      importFrom: [customMediaCache],
      features: {
        'color-mod-function': { unresolved: 'warn' },
      },
    }),
  ],
};

module.exports = config;

Файл кастомных медиа‑запросов

Создайте custom-media.js:

javascript
module.exports = {
  mobile: '(max-width: 719px)',
  tablet: '(min-width: 720px)',
  desktop: '(min-width: 1280px)',
  large: '(min-width: 1920px)',
};

Использование в SCSS

scss
// Теперь вы можете использовать кастомные медиа
.element {
  @media (--mobile) {
    color: red;
  }
  
  @media (--tablet) {
    color: blue;
  }
}

“config const customMediaUnprefixed = require(‘./src/hooks/useMedia/custom-media.js’); const customMediaCache = { customMedia: Object.assign({}, …Object.keys(customMediaUnprefixed).map((key) => ({[--${key}]: customMediaUnprefixed[key]}))), };” [Stack Overflow]

Правильные миксины для медиа‑запросов в Sass

Вот надёжные миксины, которые работают во всех версиях Sass:

1. Базовые миксины для диапазонов

scss
// Минимальная ширина
@mixin min-width($breakpoint) {
  @media (min-width: $breakpoint) {
    @content;
  }
}

// Максимальная ширина
@mixin max-width($breakpoint) {
  @media (max-width: $breakpoint) {
    @content;
  }
}

// Диапазон между
@mixin between($min, $max) {
  @media (min-width: $min) and (max-width: $max) {
    @content;
  }
}

// Вне диапазона
@mixin outside($min, $max) {
  @media (max-width: $min), (min-width: $max) {
    @content;
  }
}

2. Продвинутая система с map

scss
// Глобальные переменные
$breakpoints: (
  'xs': 0,
  'sm': 576px,
  'md': 768px,
  'lg': 992px,
  'xl': 1200px,
  'xxl': 1400px
) !default;

// Миксин для min-width
@mixin media-breakpoint-up($name) {
  $min: map-get($breakpoints, $name);
  @if $min {
    @media (min-width: $min) {
      @content;
    }
  }
}

// Миксин для max-width
@mixin media-breakpoint-down($name) {
  $max: map-get($breakpoints, $name);
  @if $max {
    $max: $max - 0.02px;
    @media (max-width: $max) {
      @content;
    }
  }
}

// Миксин для диапазона
@mixin media-breakpoint-between($lower, $upper) {
  $min: map-get($breakpoints, $lower);
  $max: map-get($breakpoints, $upper);
  @if $min and $max {
    $max: $max - 0.02px;
    @media (min-width: $min) and (max-width: $max) {
      @content;
    }
  }
}

3. Пример использования

scss
.header {
  padding: 1rem;
  
  @include media-breakpoint-up(md) {
    padding: 2rem;
  }
  
  @include media-breakpoint-down(sm) {
    font-size: 0.9rem;
  }
  
  @include media-breakpoint-between(sm, lg) {
    background-color: #f0f0f0;
  }
}

“Simply put, you’ve made your mixin too specific and not very reusable for other sites. I’m currently using a collection of 4 mixins to handle the most common media queries: min-width, max-width, between, and outside” [Stack Overflow]

Рекомендации по организации медиа‑запросов

1. Используйте семантические имена

scss
// Хорошо
@mixin mobile { @media (max-width: 768px) { @content; } }
@mixin tablet { @media (min-width: 769px) and (max-width: 1024px) { @content; } }
@mixin desktop { @media (min-width: 1025px) { @content; } }

// Плохо
@mixin small { @media (max-width: 768px) { @content; } }
@mixin medium { @media (min-width: 769px) and (max-width: 1024px) { @content; } }

2. Группируйте медиа‑запросы

scss
.button {
  background: blue;
  color: white;
  padding: 10px 20px;
  
  // Mobile-first подход
  @include mobile {
    padding: 8px 16px;
    font-size: 14px;
  }
  
  @include tablet {
    padding: 12px 24px;
    font-size: 16px;
  }
  
  @include desktop {
    padding: 15px 30px;
    font-size: 18px;
  }
}

3. Избегайте вложенности медиа‑запросов

scss
// Плохая практика
.container {
  .header {
    .logo {
      width: 100px;
      
      @include mobile {
        width: 50px;
      }
    }
  }
}

// Хорошая практика
.logo {
  width: 100px;
  
  @include mobile {
    width: 50px;
  }
}

4. Используйте переменные для повторяющихся значений

scss
$spacing-unit: 8px;
$font-size-base: 16px;

.element {
  padding: $spacing-unit * 2;
  font-size: $font-size-base;
  
  @include mobile {
    padding: $spacing-unit;
    font-size: $font-size-base * 0.875;
  }
}

“Since our @media query is global, it cannot know about this --mobile-breakpoint variable. This extends to the :root selector as well. SASS / SCSS can make this even more confusing by allowing @media blocks to be nested inside of other rulesets. Don’t be fooled!” [Alternative to CSS variable media queries]

Источники

  1. Sass: Breaking Change: Media Queries Level 4
  2. Sass: CSS At‑Rules
  3. Why @custom-media does not apply the respective styles in sass - Stack Overflow
  4. How can I use @custom-media in Sass mixins? - Stack Overflow
  5. Want CSS variables in media query declarations? Try this!
  6. CSSNext postcss-custom-media - unable to import medias from file with ‘importFrom’ option - Stack Overflow
  7. Handling custom media queries in Sass with Twitter Bootstrap - Stack Overflow
  8. Media queries in Sass - Stack Overflow

Заключение

  1. Sass не поддерживает @custom-media – эта функция находится в спецификации CSS Level 4, но ещё не реализована в Sass нативно.
  2. Используйте стандартные миксины вместо @custom-media – они обеспечивают лучшую совместимость и предсказуемое поведение.
  3. PostCSS с плагином postcss-custom-media – лучший вариант, если вам нужна именно функциональность @custom-media.
  4. Организуйте медиа‑запросы через map и переменные – это обеспечивает гибкость и поддерживаемость кода.
  5. Следуйте Mobile‑First подходу – начинайте с мобильных стилей и постепенно добавляйте более широкие экраны для лучшей производительности.

Для большинства проектов стандартные миксины на основе @media правил являются оптимальным решением, обеспечивающим максимальную совместимость и предсказуемое поведение компиляции Sass.

Авторы
Проверено модерацией
Модерация