构建加载条组件

介绍如何使用 <progress> 元素构建无障碍的颜色自适应加载栏的基本概览。

在这篇博文中,我想分享一下如何构建颜色自适应 使用 <progress> 元素访问加载条。试用 演示观看 来源

<ph type="x-smartling-placeholder">
</ph>
在 Chrome 上演示的浅色和深色、不确定、增加和完成功能。

如果你更喜欢视频,可以参阅此博文的 YouTube 版本:

概览

通过 <progress> 元素可向用户提供有关完成情况的视觉和听觉反馈。这个 视觉反馈在如下场景中非常有用:表单填写进度、 显示下载或上传信息,甚至是显示 进度未知,但工作仍在进行。

GUI 挑战适用于 现有的 HTML <progress> 元素,以节省一些无障碍功能。通过 颜色和布局突破了内置元素自定义的极限, 对组件进行现代化改造,使其更好地适应设计系统。

<ph type="x-smartling-placeholder">
</ph> 每个浏览器中,提供浅色和深色标签页 
    自适应图标自上到下概览: 
    Safari、Firefox、Chrome。 <ph type="x-smartling-placeholder">
</ph> Firefox、Safari、iOS Safari、 Chrome 和 Android Chrome(采用浅色和深色方案)。

Markup

我选择将 <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>

使用 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" 使 progress 元素可从 JavaScript 成为焦点。对于 屏幕阅读器技术,因为随着进度的变化而聚焦于进度, 会向用户宣布更新进度已进展到哪一步。

样式

在样式设置方面,进度元素有点棘手。内置 HTML 元素具有特殊的隐藏部分,这些部分很难选择,而且通常 仅提供有限的一组可供设置的属性。

布局

布局样式旨在提高游戏进度的灵活性 元素的大小和标签位置。添加了一种特殊的完成状态,该完成状态 提供实用但非必要的额外视觉提示。

<progress>”布局

进度元素的宽度保持不变,因此可以缩小和增大 以及设计中所需的空间内置样式 将 appearanceborder 设置为 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;
}

_radius1e3px 值使用科学数 表示法来表达 大数,因此 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> 元素中选择每个元素分别对应每个元素的 所有事件。使用 progress 元素作为单个标记,但它由 只有少量通过 CSS 伪选择器公开的子元素。Chrome 开发者工具 将会显示这些元素,前提是您启用了该设置:

  1. 右键点击您的页面,然后选择 Inspect Element 以调出开发者工具。
  2. 点击开发者工具窗口右上角的“设置”齿轮。
  3. Elements标题下,找到并启用显示用户代理阴影 DOM 复选框。

屏幕截图:可在开发者工具中公开用户代理 shadow DOM。

Safari 和 Chromium 样式

Safari 和 Chromium 等基于 WebKit 的浏览器 ::-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、 
  Android 版 Firefox、Chrome 和 Chrome 显示的加载栏都可以正常显示。

请注意,在 iOS Safari 中,Firefox 的轨迹颜色设置为 accent-color 有一个浅蓝色的轨迹深色模式也是一样: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 状态添加动画效果

我可以更发挥一些创意,以便提供动画。伪元素 并应用了渐变效果 这三种浏览器都适用

自定义属性

自定义属性在很多方面都非常有用,但我最喜欢的一个选项是 为其他看起来很神奇的 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,请移除 valuearia-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. <progress> 元素的 value 属性。
  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()
}

Mac OS“Voice Over”应用的屏幕截图 
  向用户显示加载条的进度

总结

现在您已经知道我是怎么做到的了,您该怎么做 ‽ 🙂?

如果再有机会,我当然想进行一些更改。我认为有空间来清理当前组件,还有空间尝试构建一个没有 <progress> 元素伪类样式限制的组件。值得一探!

让我们一起采用多样化的方法,学习所有在 Web 上构建应用的方法。

创建演示,在 Twitter 微博上添加链接,然后我会添加 到下面的社区混剪部分!

社区混剪作品