Создание компонента панели загрузки

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

В этом посте я хочу поделиться мыслями о том, как создать адаптивную к цвету и доступную панель загрузки с помощью элемента <progress> . Попробуйте демо-версию и просмотрите исходный код !

Светлое и темное, неопределенное, увеличивающееся и завершенное, демонстрируемое в Chrome.

Если вы предпочитаете видео, вот версия этого поста на YouTube:

Обзор

Элемент <progress> предоставляет пользователям визуальную и звуковую информацию о завершении. Эта визуальная обратная связь полезна для таких сценариев, как: прогресс в форме, отображение информации о загрузке или отправке или даже показ того, что объем прогресса неизвестен, но работа все еще активна.

Эта задача GUI работала с существующим элементом HTML <progress> , чтобы сэкономить некоторые усилия на доступности. Цвета и макеты расширяют границы настройки встроенного элемента, позволяя модернизировать компонент и лучше вписать его в системы дизайна.

Светлые и темные вкладки в каждом браузере, обеспечивающие обзор адаптивной иконки сверху вниз: Safari, Firefox, Chrome.
Демонстрация показана в Firefox, Safari, iOS Safari, Chrome и Android Chrome в светлой и темной схемах.

Разметка

Я решил обернуть элемент <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 после завершения.

Добавления атрибутов Арии

Хотя неявной ролью элемента <progress> является progressbar , я сделал ее явной для браузеров, в которых эта неявная роль отсутствует. Я также добавил атрибут indeterminate , чтобы явно перевести элемент в неизвестное состояние, что более понятно, чем наблюдение за тем, что элемент не имеет установленного value .

<label>
  Loading 
  <progress 
    indeterminate 
    role="progressbar" 
    aria-describedby="loading-zone"
    tabindex="-1"
  >unknown</progress>
</label>

Используйте tabindex="-1" , чтобы сделать элемент прогресса доступным для фокуса из JavaScript. Это важно для технологии чтения с экрана, так как фокус на прогрессе по мере его изменения сообщит пользователю, насколько далеко продвинулся обновленный прогресс.

Стили

Элемент прогресса немного сложен, когда дело доходит до стилизации. Встроенные HTML-элементы имеют специальные скрытые части, которые бывает сложно выбрать, и зачастую они предлагают лишь ограниченный набор свойств, которые нужно установить.

Макет

Стили макета предназначены для обеспечения некоторой гибкости в размере и положении метки элемента хода выполнения. Добавляется особое состояние завершения, которое может быть полезным, но не обязательным, дополнительным визуальным сигналом.

<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);
}

Снимок экрана: полоса загрузки при 100 % и галочке в конце.

Цвет

Браузер использует собственные цвета для элемента прогресса и адаптируется к светлым и темным цветам с помощью всего одного свойства 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> , которые предоставляет каждый браузер. Использование элемента прогресса представляет собой один тег, но он состоит из нескольких дочерних элементов, которые отображаются через псевдоселекторы CSS. Chrome DevTools покажет вам эти элементы, если вы включите этот параметр:

  1. Щелкните правой кнопкой мыши на своей странице и выберите «Проверить элемент» , чтобы открыть DevTools.
  2. Нажмите на шестеренку «Настройки» в правом верхнем углу окна DevTools.
  3. Под заголовком «Элементы» найдите и установите флажок «Показать теневую DOM пользовательского агента» .

Снимок экрана: где в 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 и местонахождение частей элемента прогресса.

Снимок экрана угла отладки, где в Safari, iOS Safari, Firefox, Chrome и Chrome на Android отображается работающая панель загрузки.

Обратите внимание, что в Firefox цвет дорожки установлен из accent-color , а в iOS Safari — светло-голубая дорожка. То же самое происходит и в темном режиме: в Firefox есть темная дорожка, но нет установленного нами пользовательского цвета, и он работает в браузерах на основе Webkit.

Анимация

При работе со встроенными псевдоселекторами браузера часто используется ограниченный набор разрешенных свойств CSS.

Анимация заполнения трека

Добавление перехода к inline-size элемента прогресса работает для Chromium, но не для Safari. Firefox также не использует свойство перехода в ::-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;
}

Пользовательские свойства также помогут коду оставаться СУХИМ, поскольку мы снова не можем группировать эти селекторы, специфичные для браузера, вместе.

Ключевые кадры

Цель — бесконечная анимация, которая движется вперед и назад. Начальный и конечный ключевые кадры будут установлены в 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 , элементу прогресса и зоне <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:

  1. Атрибут value элемента <progress> .
  2. Атрибут aria-valuenow .
  3. Внутреннее текстовое содержимое <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()
}

Снимок экрана: приложение Voice Over для Mac OS читает пользователю информацию о ходе загрузки.

Заключение

Теперь, когда вы знаете, как я это сделал, как бы вы‽ 🙂

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

Давайте разнообразим наши подходы и изучим все способы разработки в Интернете.

Создайте демо, пришлите мне ссылку в Твиттере , и я добавлю ее в раздел ремиксов сообщества ниже!

Ремиксы сообщества