Кольцо прогресса

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

В этом руководстве кольцо прогресса реализуется следующим образом:

  • Использование нативного элемента <progress> в качестве семантической основы, чтобы компонент был доступен для программ чтения с экрана и пользователей клавиатуры.
  • Стилизация компонента с помощью conic-gradient() и mask-image, что позволяет создать полностью адаптивное кольцо с поддержкой тем без необходимости манипулировать путями SVG.
  • Использование пользовательских свойств CSS и @property для плавного перехода заполнения индикатора с ускорением графического процессора.

Этот подход предпочтительнее решений, основанных только на SVG, поскольку в нем используется семантический элемент <progress>, а не ARIA, и он легче интегрируется с существующими макетами, системами проектирования и типографикой.

Для неопределенных состояний загрузки используйте индикатор загрузки.

Реализация

1. Markup

Используйте оболочку, чтобы удерживать как визуальное кольцо, так и дополнительный центральный контент. Элемент <progress> остается семантическим источником истины. Используйте служебный класс, чтобы визуально скрыть нативный индикатор выполнения, сохранив его доступность.

<div class="ring-wrapper">
  <progress value="75" max="100" aria-label="Task progress" class="progress-ring"></progress>
  <!-- Optional: Content to display in the center -->
  <div class="ring-content">
    75%
  </div>
</div>

2. Стили

Как скрыть нативный интерфейс

Чтобы оформить элемент <progress> как кольцо прогресса, сначала скройте стиль индикатора выполнения, используемый браузером по умолчанию.

/* Hide native bars */
progress.loading-spinner:indeterminate::-webkit-progress-bar {
  display: none;
  background: none;
}
progress.loading-spinner:indeterminate::-webkit-progress-value {
  display: none;
  background: none;
}
progress.loading-spinner:indeterminate::-moz-progress-bar {
  display: none;
  background: none;
}
progress.loading-spinner:indeterminate::slider-fill {
  display: none;
  background: none;
}

Контейнер и кольцо

Контейнер обеспечивает контекст позиционирования. Элемент <progress> обрабатывает визуальный градиент.

.progress-ring-wrapper {
  position: relative;
  display: grid;
  place-items: center;
}

progress.progress-ring {
  --size: 150px;
  --thickness: 16px;
  --track-color: #f1f5f9;
  --fill-color: #3b82f6;
  --value: attr(value type(<number>));
  width: var(--size);
  height: var(--size);

  transition: --value 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  border-radius: 50%;

  background: conic-gradient(
    var(--fill-color) calc(var(--value) * 1%),
    var(--track-color) 0
  );

  /* MANDATORY: Clip the background to the border-area. */
  background-clip: border-area;
  border: var(--thickness) solid transparent;
  background-origin: border-box;
}

.progress-ring-content {
  /* Positioned in the center of the wrapper */
  position: absolute;
}

Вы также можете использовать radial-gradient, чтобы сделать закругленные торцевые крышки.

Включите плавные переходы с помощью @property

Чтобы кольцо прогресса плавно анимировалось при изменении значения, зарегистрируйте --value как числовое дополнительное свойство.

Пользователи с повышенной чувствительностью к движению могут испытывать дезориентацию при переходе между значениями. Учитывайте запрос медиа prefers-reduced-motion, задав по умолчанию нулевую длительность (немедленный переход) и установив более длительное время для пользователей, не указавших предпочтения.

@property --value {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

progress.progress-ring {
  transition: --value 0s ease-in-out;
  @media (prefers-reduced-motion: no-preference) {
    transition-duration: 0.4s;
  }
}

3. Обновления прогресса

Обновляйте атрибут value элемента <progress> и текстовое содержимое .progress-ring-content (если отображается процент) при каждом изменении значения.

4. Необязательное состояние успеха

Вы можете использовать селектор атрибутов CSS, чтобы автоматически обновлять внешний вид кольца (например, менять цвет на зеленый), когда задача будет выполнена.

/* Change the fill color to green when the progress reaches 100% */
progress.progress-ring[value="100"] {
  --fill-color: #10b981;
}

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

Не добавляйте резервное значение в элемент <progress>. Он не используется технологиями специальных возможностей и игнорируется всеми современными браузерами.

Резервная анимация

Если @property не поддерживается, кольцо будет мгновенно переходить к новому значению, а не плавно. В большинстве случаев это нормально и не влияет на работу сайта.

Если переход абсолютно необходим, вы можете проверить поддержку @property и использовать цикл requestAnimationFrame() для интерполяции --value в старых браузерах.

Для браузеров, которые пока не поддерживают background-clip: border-area, используйте mask-image, чтобы сделать центр элемента <progress> пустым.

@supports not (background-clip: border-area) {
  mask-image: radial-gradient(
    transparent calc(50% - var(--thickness)),
    black calc(50% - var(--thickness) + 0.5px)
  );
  border: 0;
}

Для браузеров, которые не поддерживают функцию CSS attr() для любого свойства, используйте MutationObserver, чтобы автоматически синхронизировать атрибут value со специальным свойством --value.

if (!CSS.supports("width: attr(value type(<number>))")) {
  const observer = new MutationObserver(() => {
    progress.style.setProperty("--value", progress.getAttribute("value"));
  });
  observer.observe(progress, {
    attributes: true,
    attributeFilter: ["value"],
  });
}