Статьи

Точки останова и настройки в Sass

Некоторое время назад я, здесь, в SitePoint, написал Управление отзывчивыми контрольными точками с помощью Sass , представив несколько способов работы с адаптивными контрольными точками проектирования, используя переменные и миксины.

Последнее решение, которое я представил, опирается на карту точек останова, которая обычно хранится в файле конфигурации, и миксина, принимающего имя точки останова в качестве единственного аргумента, выводящего блок медиазапроса. Это должно выглядеть так:

// In `_config.scss` $breakpoints: ( 'small' : 767px, 'medium' : 992px, 'large' : 1200px, ) !default; // In `_mixins.scss` @mixin respond-to($breakpoint) { @if map-has-key($breakpoints, $breakpoint) { @media (min-width: map-get($breakpoints, $breakpoint)) { @content; } } @else { @warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. " + "Please make sure it is defined in `$breakpoints` map."; } } // In `_random-component.scss` .foo { color: red; @include respond-to('small') { color: blue; } } 

Это решение идеально подходит для многих проектов, от небольших до средних. Тем не менее, когда мы начинаем иметь много компонентов, это, как правило, немного отстает.

Действительно, мы можем различить два разных типа точек останова:

  • фактические точки останова, связанные с перекомпоновкой макета;
  • специфичные для компонента точки останова, помогающие улучшить внешний вид страницы.

Точки останова, связанные с макетом, — это то, что мы имеем в нашем предыдущем примере. Они диктуют, когда в макете происходит существенное изменение, например, когда столбец добавляется или удаляется из сетки. Они позволяют изменять макет, чтобы не выглядеть нарушенным, отсюда и названия точек останова .

Но чаще всего точек останова макета недостаточно. Поведение компонентов не должно быть продиктовано ими. Каждый конкретный компонент должен иметь свой собственный набор точек останова, инициирующих изменения микропланировки внутри этого конкретного компонента. Эти контрольные точки на самом деле не являются контрольными точками, поскольку макеты не ломаются без них, поэтому их следует называть точками настройки , как это было предложено Джереми Китом в Tweakpoints .

По моему опыту, не все точки останова созданы равными. Несомненно, есть моменты, когда макет должен кардинально измениться, чтобы контент не выглядел как дерьмо — эти медиазапросы могут по праву называться контрольными точками. Но есть и медиа-запросы, которые используются для тонкой обработки элементов страницы без каких-либо серьезных изменений в макете.
[…]
Это немного странно называть их контрольными точками, как если бы макет «сломался» без них. Эти медиа-запросы предназначены для настройки макета. Они не точки останова; они точки настройки.
— От Джереми Кейта в Tweakpoints

Конечно, они не являются взаимоисключающими. Au наоборот! Вы часто обнаружите, что хотите присоединиться к основным структурным изменениям в своих компонентах на точках останова макета, если это необходимо, и применить микрооптимизацию к точкам настройки для конкретного компонента.

В этой статье мы будем перегружать нашу существующую систему, чтобы можно было иметь дело как с точками останова для всего приложения, так и с точками останова компонентов.

Что нам нужно?

Моя идея состояла в том, чтобы сохранить точки останова для всего приложения в файле конфигурации ( _config.scss или _variable.scss ), но также иметь локальную карту точек настройки в каждом файле компонента. Например, давайте рассмотрим логотип , расположенный в components/_logo.scss . Чтобы учесть изменения в дизайне, логотип может иметь два варианта: встроенную версию и блочную версию.

 /// Logo-specific tweakpoints /// @type Map $tweakpoints: ( 'inline' : 650px, 'block' : 980px, ); .logo { .. } .logo__image { .. } .logo__baseline { .. } 

Внутри _logo.scss мы определили карту $tweakpoints которая содержит точки останова (ну, точками настройки, но вы поняли идею в данный момент), которые являются специфическими для этого компонента логотипа. Они имеют смысл только в этом контексте, поэтому должны быть доступны только в этом файле. При этом глобальные контрольные точки также должны быть доступны на тот случай, если они нам действительно понадобятся для серьезных изменений или чего-то еще.

Так что нам нужно? Нам нужно только, чтобы наш миксин знал об этой карте. Сначала следует проверить, находится ли запрошенная точка останова на локальной карте, а затем, если нет, попробовать на глобальной карте.

Перегрузка миксина

Удивительно, но наш миксин на самом деле очень прост и не намного длиннее предыдущего:

 /// Breakpoints/tweakpoints manager /// @param {String} $point - Breakpoint/tweakpoint name @mixin respond-to($point) { @if map-has-key($tweakpoints, $point) { @media (min-width: map-get($tweakpoints, $point)) { @content; } } @else if map-has-key($breakpoints, $point) { @media (min-width: map-get($breakpoints, $point)) { @content; } } @else { @warn "Could not find `#{$breakpoint}` in both local ($tweakpoints) and global ($breakpoints) contexts. Media block omitted."; } } 

Здесь есть три логических шага:

  1. Если запрошенная точка существует в $tweakpoints , откройте $tweakpoints и оставьте миксин.
  2. Если запрошенная точка не существует в $tweakpoints но существует в $breakpoints , откройте $tweakpoints и оставьте миксин.
  3. Если запрошенная точка не существует ни в $tweakpoints ни в $breakpoints , предупредите пользователя.

Обратите внимание, что если точка прерывания присутствует и в $tweakpoints и в $breakpoints , эта точка переопределит точку останова. Например, если у вас задана medium точка настройки и medium точка останова, эта точка будет использоваться при включении respond-to('medium') . Это предназначено, если мы хотим когда-либо локально переопределить точку останова для текущего компонента (хотя я бы не рекомендовал это делать).

Используй это

Использование этого свежего миксина ничем не отличается от использования нашего предыдущего. API не сильно изменился, и каждое изменение было бы полностью обратно совместимым, что, на мой взгляд, очень приятно.

 <a href="/" class="logo"> <img class="logo__image" src="..." alt="..." /> <p class="logo__baseline">...</p> </a> 
 /// Logo-specific tweakpoints /// @type Map $tweakpoints: ( 'inline' : 650px, 'block' : 980px, ); /** * Logo wrapper * 1. Turn the link into a block level element * 2. Size it based on the narrowest child when in *block* format */ .logo { display: block; /* 1 */ padding: .5em; color: currentcolor; text-decoration: none; @include respond-to('block') { width: min-content; /* 2 */ margin: 1em auto; text-align: center; } } /** * Logo image * 1. Give it a max-width when inline'd so it does break out */ .logo__image { vertical-align: middle; @include respond-to('inline') { max-width: 3em; /* 1 */ } @include respond-to('block') { display: block; height: auto; margin: 0 auto .5em; } } /** * Logo baseline * 1. Inline it when in *inline* format */ .logo__baseline { margin: 0; vertical-align: middle; @include respond-to('inline') { display: inline-block; /* 1 */ } } 

Идти дальше

В идеале вы хотите $tweakpoints инициализировать карту $tweakpoints в конце файла компонента, чтобы она не просочилась до следующего фрагмента. Достаточно простого:

 $tweakpoints: (); 

Это гарантирует, что для следующего компонента не настроена точка настройки. Проблема в том, что эту строку очень легко забыть, что на самом деле не так уж важно, если следующий компонент сам определит карту $tweakpoints . Но если это не так, и предыдущий компонент переопределяет глобальную точку останова с помощью точки настройки, следующий компонент будет иметь переопределенную точку останова, а не глобальную.

Например, рассмотрим это:

 // In `_config.scss` $breakpoints: ( 'small': 767px, 'medium': 992px, ); 
 // In `_component-1.scss` $tweakpoints: ( 'custom': 500px, 'medium': 1170px; ); // A lot of Sass rules here, but no `$tweakpoints: ();` at the end of file 
 // In `_component-2.scss` // No `$tweakpoints` map defined for this component 
 // In `main.scss` @import "utils/config"; @import "components/component-1"; @import "components/component-2"; 

Теперь, если у нас есть @include respond-to('medium') в _component-2.scss , значение точки останова будет 1170px , а не 992px . Это происходит потому, что предыдущий компонент ( _component-1.scss ) переопределяет глобальный medium локальным значением и не сбрасывает карту точек настройки в конце файла.

Итак, это отстой и очень подвержен ошибкам. Конечно, мы могли бы сделать лучше!

Действительно, мы можем. Что если бы у нас был component миксин, который позаботился о назначении и сбросе $tweakpoints ? Это в основном оболочка для нашего компонента.

 /// Component wrapper /// @param {Map} $component-tweakpoints [()] - Component tweakpoints @mixin component($component-tweakpoints: ()) { $tweakpoints: $component-tweakpoints !global; @content; $tweakpoints: () !global; } 

Это все, что нам нужно. Теперь давайте перепишем наш предыдущий пример:

 // In `_config.scss` $breakpoints: ( 'small': 767px, 'medium': 992px, ); 
 // In `_component-1.scss` @include component(( 'custom': 500px, 'medium': 1170px, )) { // A lot of Sass rules here, but no `$tweakpoints: ();` at the end of file } 
 // In `_component-2.scss` @include component { // No `$tweakpoints` map defined for this component } 
 // In `main.scss` @import "utils/config"; @import "components/component-1"; @import "components/component-2"; 

Это оно! Больше проблем нет, потому что component mixin позаботился о $tweakpoints карты $tweakpoints в конце компонента, чтобы убедиться в отсутствии неудачной утечки.

Последние мысли

Если честно, ребята, я еще не тестировал это решение в реальном проекте, но думаю, что это ценная дополнительная функция для управления точками останова. Благодаря этому небольшому дополнению становится очень легко обрабатывать как точки приложения, так и точки настройки для конкретных компонентов.

Я, вероятно, должен предупредить вас о потенциальном недостатке этого метода: оборачивая каждый компонент в вызов mixin ( @include component(..) { .. } ), вы не можете объявить специфичные для компонента mixin, так как вы не можете определить mixin внутри Mixin Call. Тем не менее, я не большой поклонник наличия специфичных для компонентов миксинов и функций, так что это может быть реальным недостатком, если вы будете думать, как я.

Я хотел бы закончить эту статью упоминанием того, что Sass-MQ от Kaelig допускает нечто эквивалентное.

 // _random-component.scss $tweakpoints: ( 'custom': 500px, 'medium': 1170px, ); .foo { @include mq('medium') { color: red; } @include mq('medium', $breakpoints: $tweakpoints) { color: blue; } } 

Как вы можете видеть, mq mixin принимает карту точек останова в качестве дополнительного аргумента, по умолчанию используется $mq-breakpoints (карта, используемая Sass-MQ для хранения глобальных точек останова). Если вы хотите сделать API немного более удобным, вы всегда можете написать свой собственный небольшой миксин:

 @mixin component-mq($from: false, $until: false) { @include mq($from: $from, $until: $until, $breakpoints: $tweakpoints) { @content; } } 

Тогда вы используете это так:

 .foo { // Global medium: 992px @include mq('medium') { color: red; } // Local medium: 1170px @include component-mq('medium') { color: blue; } } 

Это оно! Я надеюсь, вам понравится, и если у вас есть какие-либо предложения, обязательно поделитесь в комментариях! 🙂