Обновляйте состояние компонента, если при выборе нужно согласовать элементы управления, текст и поведение. Переопределяйте CSS, если меняете только оформление. В этом руководстве на примере локального предварительного просмотра плитки в Safari на Mac показано, как выбрать и проверить оба подхода.

Задача читателя конкретна: изменить небольшую веб-страницу, которую вы создаёте, так, чтобы элементы управления формой оставались синхронизированными, а декоративное оформление было легко редактировать. Здесь под состоянием компонента подразумевается объект JavaScript, которым управляет собственная функция обновления страницы. Предполагается, что вы не используете фреймворк или особую функцию редактирования продукта.

Какие изменения относятся к состоянию компонента?

Для начала спросите себя, что должно согласовываться после изменения. Если при выборе круглой плитки должны обновиться флажок, предварительный просмотр и текстовое описание, храните этот выбор в одном месте — в состоянии. Затем формируйте все три результата на его основе. Это рекомендация по реализации: так связь между выбором и отображаемыми результатами становится явной.

У пользовательского свойства CSS, например --tile-size, другая роль. Оно передаёт значение объявлениям, в которых на него ссылаются с помощью var(). Пользовательские свойства участвуют в каскаде и обычно наследуются; их поведение подчиняется правилам CSS, даже если их задаёт JavaScript. Этот контракт описан в спецификации пользовательских свойств CSS.

Прежде чем редактировать, воспользуйтесь этой схемой выбора:

Требуемое изменение Предпочтительное место Причина в этом примере
Читатель выбирает круглую или квадратную плитку Состояние компонента Флажок, описание и форма должны согласовываться
Читатель выбирает размер плитки Состояние компонента, выводимое в CSS Ползунок, описание и размеры используют одно значение
Для всех плиток нужен разный декоративный цвет Объявление таблицы стилей Не требуется обновлять выбранное значение или текст
Предварительному просмотру нужно больше свободного места вокруг Объявление таблицы стилей Это меняет расположение плитки
При проверке нужен временный размер Локальное переопределение CSS Это визуальный эксперимент, который нужно удалить после проверки

Выбор зависит от задачи. В этом предварительном просмотре цвет задаётся в CSS, потому что это неизменное оформление. Если позже вы добавите выбор цвета и описание выбранного цвета, считайте этот выбор состоянием и отображайте его визуальный эффект. Не создавайте два независимо редактируемых значения для одного и того же выбора.

У встроенных элементов управления тоже есть собственное состояние. Для флажка считывайте текущее свойство checked. Атрибут HTML checked задаёт исходное состояние флажка и не отслеживает последующие изменения, сделанные пользователем. Это различие описано в справочнике MDN по свойству флажка.

Создайте автономный предварительный просмотр на Mac

Сохраните приведённый ниже документ целиком под именем tile-preview.html с помощью обычного текстового редактора кода, затем откройте его в Safari. Оставьте расширение .html. В примере CSS и JavaScript находятся в одном файле, используются системные шрифты, а простая геометрическая плитка рисуется без внешних ресурсов.

Таблица стилей находится в заголовке документа. HTML определяет элемент style как способ встроить CSS и отличает автономные внутренние стили от таблиц стилей, ссылающихся на дополнительные ресурсы. Автономность примера позволяет явно увидеть, какие значения задают оформление. См. разделы стандарта HTML о стилях и скриптах.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Tile preview</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      margin: 2rem;
      color: #222;
      background: #fff;
    }
    label { display: block; margin-block: 1rem; }
    .preview { padding: 24px; background: #eee; }
    .tile {
      width: 80px;
      height: 80px;
      border-radius: 0;
      width: var(--tile-size, 80px);
      height: var(--tile-size, 80px);
      border-radius: var(--tile-radius, 0);
      background-color: #547899;
    }
  </style>
</head>
<body>
  <h1>Tile preview</h1>
  <label>
    <input id="round" type="checkbox"> Round tile
  </label>
  <label>
    Tile size
    <input id="size" type="range" min="40" max="160"
           step="1" value="80">
  </label>
  <button id="reset" type="button">Reset choices</button>
  <p id="summary"></p>
  <div class="preview">
    <div id="tile" class="tile" aria-hidden="true"></div>
  </div>
  <script>
    const state = { rounded: false, size: 80 };
    const round = document.getElementById('round');
    const size = document.getElementById('size');
    const summary = document.getElementById('summary');
    const tile = document.getElementById('tile');
    const reset = document.getElementById('reset');

    function render() {
      round.checked = state.rounded;
      size.value = String(state.size);
      tile.style.setProperty('--tile-size', `${state.size}px`);
      tile.style.setProperty('--tile-radius', state.rounded ? '50%' : '0');
      summary.textContent = `Tile A: ${state.rounded ? 'round' : 'square'}, ${state.size}px.`;
    }

    function update(patch) {
      const next = { ...state, ...patch };
      if (typeof next.rounded !== 'boolean' ||
          !Number.isInteger(next.size) ||
          next.size < 40 || next.size > 160) {
        throw new RangeError('Use a boolean and a size from 40 to 160.');
      }
      Object.assign(state, next);
      render();
    }

    round.addEventListener('change', () => {
      update({ rounded: round.checked });
    });
    size.addEventListener('input', () => {
      update({ size: Number(size.value) });
    });
    reset.addEventListener('click', () => {
      update({ rounded: false, size: 80 });
    });
    render();
  </script>
</body>
</html>

Используемый здесь интерфейс стилей описан в документации: element.style.setProperty() задаёт встроенное объявление, включая пользовательское свойство. Имена свойств указываются в CSS-формате, например --tile-size. См. справочник MDN по setProperty.

Попробуйте выбрать Round tile, переместить ползунок Tile size на другое значение и нажать Reset choices. Первые два действия должны обновить и форму, и описание; сброс должен восстановить квадрат размером 80 пикселей. В этой программе одно лишь присваивание поля в state не вызывает render(). Направляйте изменения через update(), чтобы проверка и отрисовка выполнялись вместе.

Логика обновления и отрисовки проверена в Node с имитациями элементов управления и объекта стилей: проверялись исходные и изменённые значения, недопустимые размеры и сброс. Все проверки прошли, но они не подтверждают корректность отображения в браузере. Проверьте файл в Safari по видимым признакам, перечисленным выше.

Когда следует переопределять объявление CSS?

Для постоянного декоративного изменения отредактируйте сохранённую таблицу стилей. Например, задайте для background-color элемента .tile другой цвет CSS или измените внутренние отступы .preview. В этой программе ни для одного из этих изменений не требуется новое поле состояния, потому что они не представляют выбранное значение.

Для временного эксперимента включите инспектор Safari: Safari → Settings… → Advanced → Show features for web developers. Откройте Develop → Show Web Inspector или нажмите Option-Command-I. Эти шаги для Mac описаны в руководстве WebKit по инспектору.

Открыв пример, выполните в консоли следующий фрагмент целиком. Он временно меняет цвет плитки, а затем создаёт функцию для удаления переопределения:

const previewTile = document.getElementById('tile');
if (!previewTile) throw new Error('Open tile-preview.html first.');
previewTile.style.setProperty('background-color', '#8069a6');
function restoreTileColor() {
  previewTile.style.removeProperty('background-color');
}
// Run restoreTileColor() when finished comparing colors.

После удаления этого встроенного объявления каскад снова определит цвет. Объявление цвета в таблице стилей при этом не удаляется. Метод удаления описан в справочнике MDN по removeProperty. Чтобы сохранить выбранный цвет в этом примере после перезагрузки, отредактируйте и сохраните исходный CSS.

Не переопределяйте временно значения, которыми управляет отрисовщик. Если вручную изменить --tile-size, в описании по-прежнему будет указано значение состояния, а при следующем вызове render() ваш эксперимент будет заменён. Если вы хотите, чтобы весь компонент согласовывался, измените размер с помощью элемента управления или функции обновления.

Почему переопределение может ничего не менять?

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

  1. Проверьте цель. Убедитесь, что выбран элемент, отображающий форму.
  2. Проверьте входное значение. В примере размер должен быть целым числом от 40 до 160. Недопустимое значение вызывает ошибку до изменения состояния.
  3. Проверьте, где используется значение. Для размеров плитки используется var(--tile-size). Действующее прямое объявление width может обойти это значение для ширины, тогда как высота продолжит его использовать.
  4. Проверьте приоритет. Сравните применимые объявления, важность, слои и специфичность.
  5. Проверьте следующее взаимодействие. Если изменение исчезает после перемещения элемента управления, посмотрите, что задаёт render().

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

Также различайте встроенные и вычисленные значения. element.style показывает встроенные стили, поэтому пустое встроенное значение не означает, что у элемента нет стилей. getComputedStyle() считывает вычисленные значения после применения активных таблиц стилей. См. справочник MDN по style и справочник по getComputedStyle.

Выполните в консоли страницы с примером следующий диагностический фрагмент целиком:

const inspectedTile = document.getElementById('tile');
if (!inspectedTile) throw new Error('Open tile-preview.html first.');
console.table({
  inlineSizeToken: inspectedTile.style.getPropertyValue('--tile-size'),
  inlineWidth: inspectedTile.style.getPropertyValue('width'),
  resolvedWidth: getComputedStyle(inspectedTile).width,
  resolvedHeight: getComputedStyle(inspectedTile).height
});

Сразу после открытия неизменённого примера ожидаются такие значения: встроенный токен размера 80px, пустая встроенная ширина и вычисленные размеры 80px. Так видно, как встроенное пользовательское свойство может задавать значение объявления таблицы стилей, не создавая встроенное объявление width.

Какие ограничения браузера и ресурсов нужно учитывать?

По данным MDN, пользовательские свойства CSS широко поддерживаются браузерами с апреля 2017 года. Это не означает, что они поддерживаются всеми старыми браузерами. В примере перед объявлениями var() заданы обычные объявления размеров, поэтому плитка сохраняет статичный вид, если пользовательские свойства недоступны. См. справочник MDN по пользовательским свойствам.

Резервное значение внутри var(--tile-size, 80px) не обеспечивает совместимость с браузером, который не умеет интерпретировать пользовательские свойства. Оно также не проверяет все передаваемые токены: определённое значение, например blue, не станет допустимой шириной лишь потому, что предусмотрено резервное значение. Эти ограничения объяснены в руководстве MDN по пользовательским свойствам.

Если целевой старый браузер не поддерживает пользовательские свойства, можно изменить отрисовщик, чтобы он задавал обычные встроенные объявления width, height и border-radius с помощью setProperty(). Оставьте прежними проверку состояния и синхронизацию элементов управления. Это меняет способ отрисовки, поэтому удалите конкурирующие записи отрисовщика и проверьте целевой браузер, вместо того чтобы считать поддержку одинаковой.

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

Источники