Руководство по определению области видимости 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»