基础概述了如何使用 <progress>
元素构建颜色自适应且可访问的加载栏。
在这篇博文中,我想分享一些想法,让您了解如何使用 <progress>
元素构建自适应且可访问的加载栏。试用演示版并查看源代码!
如果你更喜欢视频,可以参考本博文的 YouTube 版本:
概览
<progress>
元素可向用户提供有关完成情况的视觉和听觉反馈。这种视觉反馈对于如下场景很有用:表单显示进度、显示下载或上传信息,甚至显示进度量未知但工作仍处于活跃状态。
此 GUI 挑战采用现有的 HTML <progress>
元素,以减少无障碍功能方面的工作。颜色和布局突破了内置元素的自定义极限,使组件更具现代感,并使其更适合设计系统。
Markup
我选择了将 <progress>
元素封装在 <label>
中,这样我可以跳过显式关系属性,改为使用隐式关系。我还标记了受加载状态影响的父元素,以便屏幕阅读器技术将这些信息传回用户。
<progress></progress>
如果没有 value
,则元素的进度不确定。max
属性默认为 1,因此进度介于 0 和 1 之间。例如,如果将 max
设置为 100,则会将范围设置为 0-100。我选择不超出 0 和 1 的限制,将进度值转换为 0.5 或 50%。
标签封装进度
在隐式关系中,Progress 元素由如下标签封装:
<label>Loading progress<progress></progress></label>
在演示中,我选择仅针对屏幕阅读器添加标签。这是通过将标签文本封装在 <span>
中并对其应用一些样式,以使其实际上位于屏幕之外:
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
以及 WebAIM 中的以下 CSS:
.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>
”布局
进度元素的宽度保持不变,因此可以根据设计中所需的空间缩小和增长。通过将 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;
}
_radius
的 1e3px
值使用科学数字表示法表示一个大数字,因此 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);
}
颜色
浏览器会为 progress 元素使用自有颜色,并且只需一个 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 开发者工具将向您显示这些元素:
- 右键点击您的网页,然后选择检查元素以调出开发者工具。
- 点击开发者工具窗口右上角的“Settings”齿轮。
- 在元素标题下,找到并选中显示用户代理 shadow 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 仅在 <progress>
元素上公开 ::-moz-progress-bar
伪选择器。这也意味着我们无法直接对轨道进行色调调节。
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
请注意,Firefox 的轨道颜色是从 accent-color
设置的,而 iOS Safari 的轨道颜色则为浅蓝色。在深色模式下也是如此:Firefox 具有深色轨道,但不是我们设置的自定义颜色,并且可在基于 Webkit 的浏览器中运行。
动画
使用浏览器内置伪选择器时,它通常允许使用一组有限的 CSS 属性。
为填满的轨道添加动画效果
向进度元素的 inline-size
添加过渡效果适用于 Chromium,但不适用于 Safari。Firefox 也不会在其 ::-moz-progress-bar
上使用 transition 属性。
/* 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 伪元素
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()
此函数用于编排界面/用户体验。首先,请创建一个 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 中的三个位置:
<progress>
元素的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
}
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>
元素的伪类样式限制的空间。值得一探!
让我们来了解一下我们采用的方法多样化,并了解在 Web 上构建网站的所有方法。
只需创建一个演示,点击 tweet me 链接,我就会将其添加到下方的“社区混剪”部分中!