Как создать стилизованный индикатор выполнения

Элемент <progress> семантически представляет ход выполнения задачи, например скачивания или загрузки информации или завершения части многоэтапного процесса. Использование нативного элемента улучшает доступность, поскольку программы чтения с экрана имеют неявный role="progressbar" и могут озвучивать значения.

В этом руководстве показано, как реализовать горизонтальный индикатор выполнения с настроенными стилями.

Индикатор выполнения или кольцо прогресса?

  • Если места мало или предпочтительно квадратное соотношение сторон, используйте круговой индикатор выполнения (кольцо прогресса): Кольцо прогресса.
  • Для большинства сценариев с неопределенным прогрессом предпочтительнее использовать спиннеры (Индикатор загрузки). Используйте неопределенные индикаторы выполнения, когда неопределенное состояние является временным и вскоре будет заменено определенным индикатором выполнения (например, ожидание метаданных о загрузке, прежде чем можно будет показать процент выполнения).

Markup

  1. Используйте нативный элемент <progress>. Он обеспечивает встроенные специальные возможности и согласованное поведение платформы.
  2. Используйте свойство max, если вам нужна шкала, отличная от стандартной (0–1).
  3. Чтобы задать определенное значение прогресса, используйте атрибут value. Опустите value для неопределенного состояния (например, до начала более конкретной обратной связи).
  4. Чтобы связать элемент индикатора выполнения с ярлыком, используйте <label for="...">, aria-labelledby или aria-label.

Какой тип настройки вам нужен?

Для ограниченного стиля достаточно следующих свойств:

  • accent-color, чтобы изменить цвет индикатора выполнения.
  • width/inline-size – изменить ширину индикатора выполнения.
  • height/block-size применяется непоследовательно – сначала протестируйте
  • writing-mode: vertical-lr для вертикальных полос.
  • Эффекты, которые применяются после отрисовки: opacity, filter, clip-path, маски, преобразования, box-shadow и т. д.

Если вам достаточно ограниченного набора стилей, пропустите следующий раздел.

ВНИМАНИЕ! Некоторые свойства (например, border-radius, background, border) отключают стиль индикатора выполнения по умолчанию без уведомления. Если вам нужен стиль интерфейса, выходящий за рамки указанных выше свойств, необходимо реализовать полностью собственный стиль, как описано в следующем разделе. НЕ ПРИМЕНЯЙТЕ более обширное форматирование "на всякий случай".

Полностью настраиваемый стиль

1. Базовая настройка

  • ОБЯЗАТЕЛЬНО. Используйте appearance: none, чтобы включить собственный стиль.
  • Сбросить границы UA с помощью border: 0 solid
  • Чтобы обрезать активную полосу по радиусу границы элемента, используйте overflow: clip ИЛИ примените к полосе то же значение border-radius. НЕ используйте border-radius: inherit на панели, так как это зависит от конкретной структуры элементов в Shadow DOM.
progress {
  /* Reset default browser styles */
  appearance: none;
  border: 0 solid;
  /* Clip bar to element’s border-radius  */
  overflow: clip;
}

2. Стилизация

  • Задайте стиль дорожки в самом элементе progress.
  • Используйте псевдоэлемент, предназначенный для нужного браузера, чтобы настроить индикатор выполнения: ::-webkit-progress-value для WebKit/Blink и ::-moz-progress-bar для Firefox.
  • ОБЯЗАТЕЛЬНО. Не группируйте псевдоэлементы -webkit- и -moz- в одном правиле. Правило будет считаться недействительным и будет игнорироваться. Селекторы :is() и :where() в этом случае не помогут, поскольку они не поддерживают псевдоэлементы.
  • Каждый псевдоэлемент, предназначенный для браузера, должен появляться ровно один раз. Настройки, зависящие от состояния (например, неопределенный размер сегмента и анимация), должны передаваться через дополнительные параметры, заданные в самом элементе progress.
  • Необязательно: настройте переход inline-size для заливки, чтобы изменения значений анимировались, а не происходили скачкообразно. Firefox определяет размер заливки в макете и игнорирует его, что не приводит к проблемам.
progress {
  --progress-bar-radius: 1e5px;
  border-radius: var(--progress-bar-radius);
  /* Apply color for custom progress track (background) */
  background: var(--progress-bar-track, var(--color-neutral-90));
  /* Subtle depth on the empty part of the track */
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
}

/* Let <progress> styling show through */
progress::-webkit-progress-bar {
  background: transparent;
}

/* Webkit, Chrome */
progress::-webkit-progress-value {
  background: var(--progress-bar-fill, var(--accent-color));
  border-radius: var(--progress-bar-radius);
  transition: inline-size 0.3s;
}

/* Firefox */
progress::-moz-progress-bar {
  background: var(--progress-bar-fill, var(--accent-color));
  border-radius: var(--progress-bar-radius);
  transition: inline-size 0.3s;
}

3. Стилизация неопределенного состояния

Добавьте специальную анимацию для неопределенного состояния. Превратите псевдоэлемент заполнения в короткий сегмент и перемещайте его по дорожке.

  • Стилизуйте саму заливку. НЕ воссоздавайте ее в качестве фона для progress.
  • Неопределенное заполнение существует только за счет анимации: ключевые кадры задают его длину с помощью реального inline-size (это превосходит встроенный inline-size UA Chromium) и перемещают его с помощью translate. НЕ используйте scale для длины, так как это искажает узор заливки.
  • Проценты translate рассчитываются относительно собственной ширины заливки, поэтому указывайте позиции в единицах дорожки: сделайте progress контейнером встроенного размера и используйте cqi в ключевых кадрах. Добавьте margin-right: auto в from, to, чтобы Chromium размещал заливку слева и при письме справа налево.
  • Для перемещения заливки используйте преобразования, а не поля.
    • Преобразования сохраняют анимацию в компоновщике (если только inline-size также не анимируется).
    • Поля в RTL-языках могут отображаться некорректно по двум причинам:
    • Firefox позиционирует неопределенное заполнение, используя только физическое левое поле, поэтому margin-inline-start не влияет на RTL, и заполнение никогда не перемещается.
    • В Chromium и WebKit, которые размещают заливку как обычный блок, как только поле выталкивает заливку за конец дорожки, поля в сумме превышают дорожку. Стандартный CSS решает эту проблему, игнорируя margin-left в RTL, поэтому сегмент переходит к краю, а не соскальзывает с него.
    • translate применяется после макета, поэтому ни одна из проблем не возникает.
  • Чтобы отразить движение в RTL (как это делает нативная панель Chrome), измените анимацию в :dir(rtl):
progress:dir(rtl) {
  --progress-bar-indeterminate-animation: progress-bar-indeterminate var(--progress-bar-indeterminate-animation-duration) infinite linear reverse;
}
  • Определите общедоступные свойства --progress-bar-indeterminate-* в progress (их название уже ограничивает их областью видимости состояния), чтобы потребители могли переопределять их, не зная о :indeterminate.
  • Чтобы изменять длину сегмента (например, передний край уходит вперед, а задний его догоняет), задайте для --progress-bar-indeterminate-animation две параллельные анимации: одну, ключевые кадры которой анимируют translate в cqi (положение), и другую, ключевые кадры которой анимируют inline-size (длину). Таким образом, у каждой анимации будут свои ключевые кадры и замедление. НЕ пытайтесь задать форму для обоих краев в одном списке ключевых кадров. Значение animation-timing-function для каждого ключевого кадра применяется ко всем свойствам в этом кадре.
  • --progress-bar-animation применяется в обоих состояниях и может использоваться для таких эффектов, как мерцание. revert-rule используется для переопределения только в том случае, если он задан, чтобы максимизировать компонуемость.
  • Не отключайте анимацию неопределенного состояния (в Chromium animation: none оставляет заполнение нулевой ширины). Если вы хотите уменьшить движение, замедлите его.
progress {
  /* Lets the fill position itself in track units (cqi) */
  container-type: inline-size;
  /* Segment length relative to the track */
  --progress-bar-indeterminate-size: 30%;
  --progress-bar-indeterminate-animation-duration: 1.5s;

  /* Respect prefers-reduced-motion */
  @media (prefers-reduced-motion: reduce) {
    --progress-bar-indeterminate-animation-duration: 10s;
  }
}

progress:indeterminate {
  --progress-bar-animation: var(
      --progress-bar-indeterminate-animation,
      progress-bar-indeterminate var(--progress-bar-indeterminate-animation-duration) infinite linear
    );
}

/* Add to the existing pseudo-element rules */
progress::-webkit-progress-value {
  animation: var(--progress-bar-animation, revert-rule);
}

progress::-moz-progress-bar {
  animation: var(--progress-bar-animation, revert-rule);
}

/* Physical: left to right regardless of direction (reverse under :dir(rtl) to mirror). */
@keyframes progress-bar-indeterminate {
  from, to {
    inline-size: var(--progress-bar-indeterminate-size);
    /* Pin to the physical left; Chromium would otherwise lay the fill out at the inline start */
    margin-right: auto;
  }
  from { translate: -100%; } /* Own width: fully outside the left edge */
  to { translate: 100cqi; }  /* Track width: fully outside the right edge */
}

Дополнительные соображения по поводу доступности

Помимо перечисленных выше рекомендаций, соблюдайте следующие правила:

  • Сообщайте о завершении, не переключая фокус. Не устанавливайте tabindex и не вызывайте .focus() для <progress>, так как этот элемент не является интерактивным и перемещение фокуса на него мешает пользователям, которые работают с клавиатурой. Чтобы сообщить о завершении фоновой задачи, обновите элемент статуса (role="status" или aria-live="polite").
  • Не добавляйте резервный текст в тег <progress>. Современные браузеры игнорируют текст в теге <progress>, и он не используется технологиями специальных возможностей.
  • Не используйте aria-busy + aria-describedby для неразмеченного общего элемента <div>. Программы чтения с экрана не всегда поддерживают aria-busy, а технологии специальных возможностей игнорируют aria-describedby в безымянном элементе <div> без роли. Если контент региона не готов во время загрузки, скройте его с помощью hidden или aria-hidden="true".
  • РЕКОМЕНДУЕТСЯ обеспечить достаточный контраст между индикатором выполнения и дорожкой при добавлении собственных стилей.
  • Рассмотрите альтернативные способы представления неопределенного состояния. Анимация индикатора выполнения, отображаемая параллельно с другим контентом, может отвлекать некоторых пользователей.

Резервные стратегии

Резервные варианты и поддержка свойства accent-color в браузерах

Свойство accent-color – это прогрессивное улучшение. Браузеры, которые не поддерживают это свойство, будут игнорировать его и использовать цвета интерфейса по умолчанию.

Если accent-color не поддерживается, а вам нужно настроить индикатор выполнения, используйте метод собственного стиля.

Резервные объявления и поддержка браузеров для revert-rule

Если revert-rule не поддерживается, animation: var(--progress-bar-animation, revert-rule) возвращается к animation-name: revert-rule с длительностью 0s, когда --progress-bar-animation не задано. Это безвредная пустая операция, если @keyframes revert-rule не существует.