Идеальный градиент для HTML5 Progress Bar: магия Container Queries

📂 Категория: CSS
🏷️ Теги:

Почему стоит использовать <progress>, а не <div>?

Многие разработчики по привычке создают прогресс-бары через два вложенных <div>. Однако у нативного тега <progress> есть три критических преимущества:

  • Доступность (Accessibility) из коробки: Скринридеры сразу понимают, что это индикатор процесса. Вам не нужно вручную добавлять role="progressbar", aria-valuenow, aria-valuemin и aria-valuemax. Браузер делает это за вас.
  • Лаконичность кода: Вместо управления шириной внутреннего блока через style={{ width: '30%' }}, вы просто меняете атрибут value. Это чище и логичнее с точки зрения структуры DOM.
  • Встроенное состояние “Loading”: Если убрать атрибут value, тег автоматически переходит в состояние indeterminate (неопределенное), что удобно для индикации ожидания без написания лишней логики.

Разметка с <progress>:

<progress class="progress-1" max="100" value="40"></progress>

Проблема «сжатого» градиента

При стилизации стандартного элемента <progress> с помощью градиента разработчики часто сталкиваются с неприятным визуальным эффектом: градиент «сжимается» или «растягивается» вместе с заполнением полоски.

В обычном подходе градиент применяется к самому индикатору (::-webkit-progress-value).

Правильно

Неправильно

Показать CSS код
.progress-bad {
  --_height: 1rem;
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: var(--_height);
  border: none;
  border-radius: var(--_height);
  overflow: hidden;
}

.progress-bad::-webkit-progress-bar {
  background-color: #eee;
  border-radius: var(--_height);
}

.progress-bad::-webkit-progress-value {
  border-radius: var(--_height);
  background-image: linear-gradient(to right, #ef4444 0%, #facc15 50%, #22c55e 100%);
}

.progress-bad::-moz-progress-bar {
  border-radius: var(--_height);
  background-image: linear-gradient(to right, #ef4444 0%, #facc15 50%, #22c55e 100%);
}

Результат: Когда прогресс равен 10%, вы видите только первые 10% градиента (ярко-красный). Когда прогресс 100% — всю растяжку. Цвета выглядят статично привязанными к длине заполненной части, а не к общей шкале.

Теперь добавим всего пару строк кода, и получим правильный вариант:

.progress-1 {
  container-type: inline-size;
}
.progress-1::-webkit-progress-value {
  background-size: 100cqi;
}
.progress-1::-moz-progress-bar {
  background-size: 100cqi;
}

Финальный CSS:

Показать CSS код
.progress-1 {
  --_height: 1rem;
  appearance: none;
  -webkit-appearance: none;
  container-type: inline-size;
  width: 100%;
  height: var(--_height);
  border: none;
  border-radius: var(--_height);
  overflow: hidden;
}

.progress-1::-webkit-progress-bar {
  background-color: #eee;
  border-radius: var(--_height);
}

.progress-1::-webkit-progress-value {
  border-radius: var(--_height);
  background-image: linear-gradient(to right, #ef4444 0%, #facc15 50%, #22c55e 100%);
  background-size: 100cqi;
}

.progress-1::-moz-progress-bar {
  border-radius: var(--_height);
  background-image: linear-gradient(to right, #ef4444 0%, #facc15 50%, #22c55e 100%);
  background-size: 100cqi;
}

В чем главные преимущества?

Фиксация градиента через cqi - используя background-size: 100cqi, мы привязываем размер градиента не к текущей ширине заполненной полоски, а к полной ширине всего элемента <progress>.

Как это работает: Даже если полоска заполнена всего на 20%, она отображает именно ту часть градиента, которая соответствует этому участку на общей шкале. Цвет плавно «проявляется», а не растягивается.

Итог

Цвета больше не зависят от того, насколько заполнена шкала — они зависят от позиции. Это делает индикатор интуитивно понятным: красный всегда означает «начало», желтый — «середину», а зеленый — «финиш», независимо от текущей длины полоски.

Похожие статьи