Базовый обзор того, как создать адаптивную и доступную полосу загрузки с помощью элемента <progress>
.
В этой публикации я хочу поделиться мыслями о том, как создать адаптивную и доступную полосу загрузки с элементом <progress>
. Попробуйте демо и посмотрите исходный код !
Если вы предпочитаете видео, вот версия этого поста на YouTube:
Обзор
Элемент <progress>
обеспечивает визуальную и звуковую обратную связь для пользователей о завершении процесса. Эта визуальная обратная связь полезна в таких сценариях, как: отслеживание хода выполнения формы, отображение информации о загрузке или выгрузке данных или даже указание того, что степень прогресса неизвестна, но работа всё ещё активна.
В этом GUI-задании мы использовали существующий HTML-элемент <progress>
, чтобы сэкономить усилия на обеспечении доступности. Цвета и макеты расширяют возможности настройки встроенного элемента, модернизируя компонент и обеспечивая его лучшую совместимость с дизайн-системами.

Разметка
Я решил заключить элемент <progress>
в тег <label>
, чтобы отказаться от явных атрибутов связи в пользу неявной . Я также пометил родительский элемент, на который влияет состояние загрузки, чтобы технологии чтения с экрана могли передавать эту информацию пользователю.
<progress></progress>
Если value
отсутствует, то прогресс элемента не определён . Атрибут max
по умолчанию равен 1, поэтому прогресс находится в диапазоне от 0 до 1. Например, если задать max
равным 100, диапазон будет равен 0–100. Я решил остаться в пределах от 0 до 1, заменив значения прогресса на 0,5 или 50%.
Прогресс в обертке
В неявной связи элемент прогресса оборачивается меткой следующим образом:
<label>Loading progress<progress></progress></label>
В своей демонстрации я решил включить метку только для экранных дикторов . Это достигается путём помещения текста метки в тег <span>
и применения к нему некоторых стилей, чтобы он фактически не отображался на экране:
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
Со следующим сопутствующим CSS от WebAIM :
.sr-only {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
Зона, затронутая ходом загрузки
Если у вас хорошее зрение, связать индикатор прогресса с соответствующими элементами и областями страницы может быть легко, но для пользователей с ослабленным зрением это может быть не так очевидно. Улучшите это, присвоив атрибут aria-busy
самому верхнему элементу, который изменится после завершения загрузки. Кроме того, укажите связь между индикатором прогресса и областью загрузки с помощью aria-describedby
.
<main id="loading-zone" aria-busy="true">
…
<progress aria-describedby="loading-zone"></progress>
</main>
В JavaScript переключите aria-busy
в true
в начале задачи и в значение false
после ее завершения.
Добавление атрибутов Aria
Хотя неявная роль элемента <progress>
— progressbar
, я сделал её явной для браузеров, в которых эта неявная роль отсутствует. Я также добавил атрибут indeterminate
, чтобы явно перевести элемент в состояние «неизвестно», что более понятно, чем просто отсутствие у элемента заданного value
.
<label>
Loading
<progress
indeterminate
role="progressbar"
aria-describedby="loading-zone"
tabindex="-1"
>unknown</progress>
</label>
Используйте tabindex="-1"
, чтобы сделать элемент прогресса доступным для фокусировки из JavaScript. Это важно для технологий чтения с экрана, поскольку фокусировка на элементе прогресса при изменении хода выполнения будет сообщать пользователю, насколько далеко он продвинулся.
Стили
Элемент progress немного сложен в стилизации. Встроенные HTML-элементы имеют особые скрытые части, которые может быть сложно выделить, и часто предлагают лишь ограниченный набор свойств для настройки.
Макет
Стили макета позволяют гибко настраивать размер элемента прогресса и положение метки. Добавлено специальное состояние завершения, которое может быть полезным, но не обязательным дополнительным визуальным индикатором.
<progress>
Макет
Ширина элемента progress остаётся неизменной, чтобы он мог уменьшаться и увеличиваться в зависимости от пространства, необходимого в дизайне. Встроенные стили удаляются путём установки appearance
и border
в значение none
. Это делается для того, чтобы элемент можно было нормализовать в разных браузерах, поскольку у каждого браузера свои стили для своего элемента.
progress {
--_track-size: min(10px, 1ex);
--_radius: 1e3px;
/* reset */
appearance: none;
border: none;
position: relative;
height: var(--_track-size);
border-radius: var(--_radius);
overflow: hidden;
}
Значение 1e3px
для _radius
использует экспоненциальную запись чисел для выражения больших чисел, поэтому border-radius
всегда округляется. Оно эквивалентно 1000px
. Мне нравится использовать это значение, потому что моя цель — использовать достаточно большое значение, чтобы его можно было задать и забыть (и это короче, чем записывать, чем 1000px
). При необходимости его можно легко сделать ещё больше: просто замените 3 на 4, и тогда 1e4px
будет эквивалентно 10000px
.
Используется overflow: hidden
, и это был спорный стиль. Он упростил некоторые вещи, например, избавил от необходимости передавать значения border-radius
в трек и элементы заполнения трека; но также лишил дочерние элементы прогресса возможности находиться за пределами элемента. Ещё одна итерация этого пользовательского элемента прогресса может быть реализована без overflow: hidden
, и это может открыть некоторые возможности для анимации или улучшения состояний завершения.
Прогресс завершен
CSS-селекторы выполняют здесь сложную работу, сравнивая максимальное значение со значением, и если они совпадают, прогресс завершён. После завершения генерируется псевдоэлемент, который добавляется в конец элемента прогресса, обеспечивая приятный дополнительный визуальный сигнал о завершении.
progress:not([max])[value="1"]::before,
progress[max="100"][value="100"]::before {
content: "✓";
position: absolute;
inset-block: 0;
inset-inline: auto 0;
display: flex;
align-items: center;
padding-inline-end: max(calc(var(--_track-size) / 4), 3px);
color: white;
font-size: calc(var(--_track-size) / 1.25);
}
Цвет
Браузер использует собственные цвета для элемента прогресса и адаптируется к светлым и тёмным оттенкам всего одним CSS-свойством. Это можно улучшить с помощью специальных селекторов, специфичных для браузера.
Светлый и темный стили браузера
Чтобы настроить на своем сайте темный и светлый адаптивный элемент <progress>
, достаточно color-scheme
.
progress {
color-scheme: light dark;
}
Прогресс одного свойства заполнен цветом
Чтобы подкрасить элемент <progress>
, используйте accent-color
.
progress {
accent-color: rebeccapurple;
}
Обратите внимание, что цвет фона трека меняется от светлого к тёмному в зависимости от accent-color
. Браузер обеспечивает нужную контрастность: довольно аккуратно.
Полностью индивидуальные светлые и темные цвета
Задайте два пользовательских свойства для элемента <progress>
: одно для цвета трека, а другое — для цвета прогресса. В медиазапросе prefers-color-scheme
укажите новые значения цвета для трека и прогресса.
progress {
--_track: hsl(228 100% 90%);
--_progress: hsl(228 100% 50%);
}
@media (prefers-color-scheme: dark) {
progress {
--_track: hsl(228 20% 30%);
--_progress: hsl(228 100% 75%);
}
}
Стили фокусировки
Ранее мы присвоили элементу отрицательный индекс табуляции, чтобы можно было программно фокусироваться на нём. Используйте :focus-visible
, чтобы настроить фокус и выбрать более умный стиль кольца фокусировки. Благодаря этому при щелчке мышью и фокусировке кольцо фокусировки не будет отображаться, но при нажатии на клавиатуре будет. Подробнее об этом рассказывается в видео на YouTube , которое стоит посмотреть.
progress:focus-visible {
outline-color: var(--_progress);
outline-offset: 5px;
}
Пользовательские стили для разных браузеров
Настройте стили, выбрав элементы элемента <progress>
, отображаемые в каждом браузере. Элемент progress — это один тег, но он состоит из нескольких дочерних элементов, которые отображаются с помощью псевдоселекторов CSS. Chrome DevTools будет отображать эти элементы, если вы включите этот параметр:
- Щелкните правой кнопкой мыши на странице и выберите «Исследовать элемент» , чтобы открыть DevTools.
- Нажмите на значок шестеренки «Настройки» в правом верхнем углу окна DevTools.
- Под заголовком Элементы найдите и установите флажок Показывать теневой DOM пользовательского агента .
Стили Safari и Chromium
Браузеры на базе WebKit, такие как Safari и Chromium, предоставляют возможности ::-webkit-progress-bar
и ::-webkit-progress-value
, которые позволяют использовать подмножество CSS. Пока же задайте background-color
с помощью созданных ранее пользовательских свойств, которые адаптируются к светлому и тёмному цветам.
/* Safari/Chromium */
progress[value]::-webkit-progress-bar {
background-color: var(--_track);
}
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
}
Стили Firefox
Firefox предоставляет псевдоселектор ::-moz-progress-bar
только для элемента <progress>
. Это также означает, что мы не можем напрямую подкрашивать дорожку.
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
Обратите внимание, что в Firefox используется цвет дорожки, заданный параметром accent-color
а в iOS Safari — светло-голубая дорожка. В тёмном режиме то же самое: в Firefox используется тёмная дорожка, но не заданный нами цвет, и она работает в браузерах на базе Webkit.
Анимация
При работе со встроенными в браузер псевдоселекторами зачастую используется ограниченный набор разрешенных свойств CSS.
Анимация заполнения трека
Добавление перехода к inline-size
элемента progress работает в Chromium, но не в Safari. Firefox также не использует свойство transition в своём ::-moz-progress-bar
.
/* Chromium Only 😢 */
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
transition: inline-size .25s ease-out;
}
Анимация :indeterminate
состояния
Здесь я проявляю немного больше креативности, чтобы создать анимацию. Создаётся псевдоэлемент для Chromium и применяется градиент, который анимируется вперёд и назад во всех трёх браузерах.
Пользовательские свойства
Пользовательские свойства отлично подходят для многих задач, но один из моих любимых — просто дать имя CSS-значению, которое, в общем-то, выглядит волшебно. Ниже представлен довольно сложный linear-gradient
, но с приятным названием. Его назначение и варианты использования легко понять.
progress {
--_indeterminate-track: linear-gradient(to right,
var(--_track) 45%,
var(--_progress) 0%,
var(--_progress) 55%,
var(--_track) 0%
);
--_indeterminate-track-size: 225% 100%;
--_indeterminate-track-animation: progress-loading 2s infinite ease;
}
Пользовательские свойства также помогут сохранить DRY-код, поскольку мы снова не можем сгруппировать вместе селекторы, специфичные для браузера.
Ключевые кадры
Цель — бесконечная анимация, которая движется вперёд и назад. Начальный и конечный ключевые кадры будут заданы в CSS. Для создания анимации, которая возвращается к исходной точке снова и снова, достаточно одного ключевого кадра — среднего на 50%
.
@keyframes progress-loading {
50% {
background-position: left;
}
}
Ориентация на каждый браузер
Не все браузеры позволяют создавать псевдоэлементы в самом элементе <progress>
или анимировать индикатор прогресса. Браузеры чаще поддерживают анимацию индикатора, чем псевдоэлементы, поэтому я перехожу от псевдоэлементов в качестве основы к анимированным индикаторам.
Псевдоэлемент хром
Chromium позволяет использовать псевдоэлемент: ::after
с позицией, чтобы перекрыть элемент. Используются неопределённые пользовательские свойства, а анимация перемещения вперёд и назад работает очень хорошо.
progress:indeterminate::after {
content: "";
inset: 0;
position: absolute;
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
полоса прогресса Safari
В Safari пользовательские свойства и анимация применяются к полосе прогресса псевдоэлемента:
progress:indeterminate::-webkit-progress-bar {
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
полоса прогресса Firefox
В Firefox пользовательские свойства и анимация также применяются к полосе прогресса псевдоэлемента:
progress:indeterminate::-moz-progress-bar {
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
JavaScript
JavaScript играет важную роль в элементе <progress>
. Он управляет значением, передаваемым элементу, и обеспечивает наличие в документе достаточной информации для программ чтения с экрана.
const state = {
val: null
}
Демонстрация предлагает кнопки для управления ходом выполнения; они обновляют state.val
, а затем вызывают функцию для обновления DOM .
document.querySelector('#complete').addEventListener('click', e => {
state.val = 1
setProgress()
})
setProgress()
Эта функция отвечает за оркестровку UI/UX. Начните с создания функции setProgress()
. Параметры не требуются, поскольку она имеет доступ к объекту state
, элементу progress и зоне <main>
.
const setProgress = () => {
}
Установка статуса загрузки на <main>
зоне
В зависимости от того, завершен ли процесс или нет, соответствующий элемент <main>
нуждается в обновлении атрибута aria-busy
:
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
}
Очистить атрибуты, если сумма загрузки неизвестна
Если значение неизвестно или не задано (в данном случае null
, удалите атрибуты value
и aria-valuenow
. Это приведёт к тому, что тег <progress>
станет неопределённым.
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
}
Исправление ошибок в десятичных дробях JavaScript
Поскольку я решил использовать максимальное значение прогресса по умолчанию, равное 1, демонстрационные функции увеличения и уменьшения используют десятичные числа. JavaScript и другие языки не всегда справляются с этим . Вот функция roundDecimals()
, которая обрезает лишнее из результата:
const roundDecimals = (val, places) =>
+(Math.round(val + "e+" + places) + "e-" + places)
Округлите значение так, чтобы его можно было представить и сделать разборчивым:
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"
}
Установить значение для программ чтения с экрана и состояния браузера
Значение используется в трех местах DOM:
- Атрибут
value
элемента<progress>
. - Атрибут
aria-valuenow
. - Внутреннее текстовое содержимое
<progress>
.
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"
progress.value = val
progress.setAttribute('aria-valuenow', valPercent)
progress.innerText = valPercent
}
Уделяя особое внимание прогрессу
После обновления значений зрячие пользователи увидят изменение хода выполнения, но пользователи экранных дикторов пока не узнают об этом. Установите фокус на элементе <progress>
, и браузер сообщит об обновлении!
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
const val = roundDecimals(state.val, 2)
const valPercent = val * 100 + "%"
progress.value = val
progress.setAttribute('aria-valuenow', valPercent)
progress.innerText = valPercent
progress.focus()
}
Заключение
Теперь, когда вы знаете, как я это сделал, как бы вы поступили? 🙂
Конечно, есть несколько изменений, которые я бы хотел внести, если бы мне дали ещё один шанс. Думаю, есть возможность привести текущий компонент в порядок, а также попробовать создать компонент без ограничений стиля псевдокласса элемента <progress>
. Стоит изучить этот вопрос!
Давайте разнообразим наши подходы и изучим все способы развития в Интернете.
Создайте демо, пришлите мне ссылку в Твиттер , и я добавлю ее в раздел ремиксов сообщества ниже!
Ремиксы сообщества
- Варун КС — исходный код и демо