Руководство по определению области видимости CSS-переменных в Angular

Самое замечательное в CSS-переменных – тот факт, что они могут применяться к псевдоклассам, таким как наведение и фокусировка. Еще одна особенность CSS-переменных, в которую мы пока не углублялись, заключается в том, что они могут применяться как на уровне документа – глобально, – так и локально, для отдельных элементах. В этом уроке мы рассмотрим некоторые плюсы и минусы глобального и локального определения области видимости переменных, а также узнаем, как применять стили к определенным элементам во время выполнения кода.

Особенности переменных уровня документа и элемента

Подобно скомпилированным расширениям CSS, таким как Sass и Less, CSS теперь использует свои собственные «чистые» переменные. Они определяются путем добавления двойного тире (–) перед именем переменной. После этого переменные CSS передаются в в функцию var() для получения доступа к ее значению. CSS-переменные объявляют в верхней части файла CSS, как показано в первом примере ниже, или на уровне правил, как это сделано во втором примере:

1) Объявление переменной на уровне документа

--main-bg-color: brown;
.background {
  background-color: var(--main-bg-color);
}

2) Объявление переменной на уровне правил

.wrapper:focus {
  background-color: --focus-color;
}

При использовании метода setProperty() CSSStyleDeclaration на уровне документа в TypeScript или JavaScript переменная добавляется во встроенный атрибут стиля HTML тега:

document.documentElement.style.setProperty('--focus-color', this.textcolor);
Особенности переменных уровня документа и элемента

Этот метод во многом похож на объявление пользовательского свойства в псевдоклассе :root в таблице стилей:

:root {
  --main-bg-color: brown;
}

В этом методе нет ничего плохого, но вам следует помнить, что такой стиль затронет все элементы, которые ссылаются на переменную --main-bg-color. Хорошей практикой в веб-разработке считается объявление переменных в максимально ограниченной области, насколько это возможно. Этот совет подпадает под старое и общепринятое правило, которое гласит, что глобальные переменные – это плохой стиль программирования. Независимо от того, согласны ли вы с этим утверждением или нет, существует немало веских причин для ограничения области действия пользовательских свойств.

Предположим, нам нужно определить атрибут -- focus-color для определенного div элемента в TypeScript коде:

@ViewChild("svgImage", { static: true }) 
private svgImageRef: ElementRef<HTMLDivElement>;
//далее в коде
svgImageRef.nativeElement.style.setProperty('--focus-color', 'gray');

Как можно увидеть на скриншоте, в результате этого действия CSS-переменная была добавлена в инлайновый стиль элемента:

Особенности переменных уровня документа и элемента - 2

Локальное объявление переменной

Возьмем, к примеру, фрагмент CSS, где объявляются две переменные цвета – одна меняет цвет фона при наведении курсора, вторая определяет оттенок рамки фокуса:

.news-image {
  $hoverColor: var(--hover-color);
  $focusColor: var(--focus-color);
  :hover {
    background-color: $hoverColor;
  }
  &:focus {
    outline: 2px solid $focusColor;
  }
}

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

Напомним, что в примере привязки стилей CSS к событиям в приложениях Angular мы использовали глобальные CSS-переменные для создания трех цветных квадратов предварительного просмотра. Теперь давайте применим ту же технику к компоненту ленты новостей, но с использованием объявлений локальных переменных, как было показано выше.

Для создания ссылки на div, содержащий новость и изображение новостей в TypeScript коде, нам нужно добавить ссылку на шаблон #svgimage в тег div:

<div
  class="news-image"
  #svgImage
  tabindex="0"
  style="background-position: center; background-size: cover"
  [style.backgroundColor]="backgroundColor"
>
  <svg>
  ...
  </svg>
</div>

В верхней части класса FeedComponent вы увидите тот же способ объявления переменной @input, что и в предыдущем примере, а также ссылку на div-элемент с новостями и изображениями, любезно предоставленную декоратором ввода @ViewChild:

export class FeedComponent implements AfterContentInit, OnChanges {
  @Input('background-color') 
  backgroundColor: string = 'blue';
  @Input('hover-background-color') 
  hoverBackgroundColor = 'cyan';
  @Input('focus-border-color') 
  focusBorderColor = '#CCCCCC';

  @ViewChild('svgImage', { static: true })
  private svgImageRef: ElementRef<HTMLDivElement>
  //...

}
export class FeedComponent implements AfterContentInit, OnChanges {
  @Input('background-color') 
  backgroundColor: string = 'blue';
  @Input('hover-background-color') 
  hoverBackgroundColor = 'cyan';
  @Input('focus-border-color') 
  focusBorderColor = '#CCCCCC';

  @ViewChild('svgImage', { static: true })
  private svgImageRef: ElementRef<HTMLDivElement>
  //...
  
}

Вероятно, вы заметили, что компонент ленты новостей теперь использует хуки жизненного цикла AfterContentInit и On Changes. Давайте рассмотрим их подробнее.

Инициализация переменной

Хотя вы можете получить доступ к переменной @Input в событии ngOnInit, вы не сможете ссылаться на элементы DOM до хука ngAfterContentInit. Объявление стиля CSSStyleDeclaration для svgImageRefсохраняется в частной переменной-члене класса для последующего использования. После этого для элемента svgImageRef устанавливаются цвета наведения и фокусировки:

private svgStyle: CSSStyleDeclaration;

ngAfterContentInit(): void {
  this.svgStyle = this.svgImageRef.nativeElement.style;
  this.svgStyle.setProperty(
    '--hover-color', this.hoverBackgroundColor);
  this.svgStyle.setProperty(
    '--focus-color', this.focusBorderColor);
}

Обновление значений CSS-переменных

Поскольку хук жизненного цикла ngAfterContentInit устанавливает CSS-переменные при первой загрузке приложения, нам необходимо использовать другой хук для обновления входных переменных, которое происходит каждый раз, когда пользователь вводит данные в поля ввода и нажимает кнопку «Применить». Здесь нам как раз пригодится ngOnChanges: он вызывается, когда изменяется любое свойство директивы, связанное с данными, – другими словами, всякий раз, когда изменяется значение @Input переменной компонента.

В жизненном цикле приложения ngOnChanges в первый раз срабатывает на самом раннем этапе, фактически до ngOnInit(). Поэтому нет смысла обновлять CSS-цвета при первом обходе, так как модель DOM в это время еще не будет готова. Мы можем проверить, является ли обход документа первым с помощью значения свойства firstChange. Это свойство равно true, когда ngOnChanges вызывается впервые. Помимо этого, необходимо проверить саму переменную, так как каждая связанная переменная возвращается вместе с соответствующим экземпляром SimpleChanges. Вот что мы получаем для каждой из наших переменных @Input при первом выполнении ngOnChanges:

Обновление значений CSS-переменных

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

ngOnChanges(changes: SimpleChanges): void {
  if (changes.hoverBackgroundColor 
    &&& !changes.hoverBackgroundColor.firstChange) {
    this.svgStyle.setProperty(
      '--hover-color', changes.hoverBackgroundColor.currentValue);
  }
  if (changes.focusBorderColor
    &&& !changes.focusBorderColor.firstChange) {
    this.svgStyle.setProperty(
      '--Focus-color', changes.focusBorderColor.currentValue);
  }
}

Демонстрацию работы этого кода можно посмотреть на stackblitz.com:

Обновление значений CSS-переменных - 2

Заключение

В этом уроке мы рассмотрели метод определения области видимости CSS-переменных, а также обсудили то, как применять пользовательские свойства к определенным элементам с помощью TypeScript и JavaScript. В конечном итоге, решение о том, объявлять ли ваши CSS-переменные глобально или внутри блоков правил, остается за вами. Я лично стараюсь определять все переменные настолько локально, насколько это возможно, потому что увеличить область определения всегда можно позже.

Пожалуйста, оставляйте ваши мнения по текущей теме статьи. За комментарии, дизлайки, подписки, отклики, лайки низкий вам поклон!

Наталья Кайдаавтор-переводчик статьи «A Guide to CSS Variable Scoping in Angular»