构建样式化进度条

<progress> 元素是表示任务完成进度的语义方式,例如下载或上传信息,或完成多步骤流程的一部分。使用原生元素有助于提高无障碍性,因为屏幕阅读器具有隐式 role="progressbar",可以读出值。

本指南实现了一个具有样式自定义功能的完全样式化水平进度条。

进度条还是进度环?

  • 如果空间有限或更适合使用方形宽高比,请考虑使用圆形进度条(进度环):进度环
  • 对于大多数不确定进度场景,最好使用旋转图标(加载旋转图标)。当不确定状态是暂时的,并且很快会被确定进度条取代时,请使用不确定进度条(例如,在显示进度百分比之前等待上传相关的元数据)

Markup

  1. 使用原生 <progress> 元素:它提供内置无障碍功能和平台一致的行为。
  2. 如果需要使用不同于默认 0-1 比例的比例,请使用 max。
  3. 对于特定的进度值,请设置 value 属性。对于不确定状态(例如,在开始提供更具体的反馈之前),请省略 value。
  4. 使用 <label for="...">、aria-labelledby 或 aria-label 将进度元素与标签相关联。

需要哪种类型的自定义?

对于有限的样式设置,以下属性就足够了:

  • accent-color,以更改进度条的颜色
  • width / inline-size 可更改进度条的宽度
  • height/block-size 适用情况不一致 - 请先测试
  • writing-mode: vertical-lr 表示垂直条。
  • 绘制后应用的效果:opacity、filter、clip-path、遮罩、转换、box-shadow 等。

如果有限的样式设置已足够,请跳过下一部分。

警告:某些属性(例如 border-radius、background、border)会静默停用默认进度条样式。如果您需要上述属性之外的样式,则必须实现完全自定义样式,如下一部分所述。请勿“为了以防万一”而应用更广泛的样式。

完全自定义样式

1. 基本设置

  • 强制性:使用 appearance: none 启用自定义样式。
  • 使用 border: 0 solid 重置 UA 边框
  • 如需将活动栏剪裁到元素的 border-radius,请使用 overflow: clip 或对该栏应用相同的 border-radius。请勿在栏上使用 border-radius: inherit,这取决于 shadow DOM 中元素的特定结构。
progress {
  /* Reset default browser styles */
  appearance: none;
  border: 0 solid;
  /* Clip bar to element’s border-radius  */
  overflow: clip;
}

2. 样式

  • 在 progress 元素本身上设置轨道样式。
  • 使用适当的浏览器定位伪元素来定位进度条本身:::-webkit-progress-value 用于 WebKit/Blink,::-moz-progress-bar 用于 Firefox。
  • 强制性要求:请勿在同一规则中将 -webkit- 和 -moz- 伪元素分组。该规则将被视为无效并被忽略。:is() 和 :where() 在这里不起作用,因为它们不支持伪元素。
  • 每个浏览器定位的伪元素应只出现一次。特定于状态的自定义(例如不确定状态下的片段大小和动画)应通过在 progress 元素本身上设置的自定义属性进行路由。
  • 可选:在填充上添加 inline-size 过渡效果,使确定值变化时呈现动画效果,而不是直接跳变。Firefox 会在布局中调整填充大小并忽略它,这不会造成任何影响。
progress {
  --progress-bar-radius: 1e5px;
  border-radius: var(--progress-bar-radius);
  /* Apply color for custom progress track (background) */
  background: var(--progress-bar-track, var(--color-neutral-90));
  /* Subtle depth on the empty part of the track */
  box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
}

/* Let <progress> styling show through */
progress::-webkit-progress-bar {
  background: transparent;
}

/* Webkit, Chrome */
progress::-webkit-progress-value {
  background: var(--progress-bar-fill, var(--accent-color));
  border-radius: var(--progress-bar-radius);
  transition: inline-size 0.3s;
}

/* Firefox */
progress::-moz-progress-bar {
  background: var(--progress-bar-fill, var(--accent-color));
  border-radius: var(--progress-bar-radius);
  transition: inline-size 0.3s;
}

3. 不确定状态样式

为不确定状态添加自定义动画:将填充伪元素转换为短线段,并使其沿轨道滑动。

  • 为填充本身设置样式;请勿在 progress 上将其重新创建为背景。
  • 不确定填充仅通过其动画存在:关键帧使用实际 inline-size 设置其长度(这优于 Chromium 的 UA 内嵌 inline-size),并使用 translate 移动它。请勿使用 scale 作为长度,否则会使填充上绘制的任何图案变形。
  • translate 百分比会根据填充自身的宽度进行解析,因此请改用轨道单位表示位置:将 progress 设为内嵌大小容器,并在关键帧中使用 cqi。在 from, to 中添加 margin-right: auto,以便 Chromium 在 RTL 模式下也将填充内容放置在物理左侧。
  • 使用转换(而非外边距)来移动填充。
    • 转换会将动画保留在合成器中(除非 inline-size 也在添加动画效果)
    • 在 RTL 模式下,外边距会因以下两个不同的原因而失效:
    • Firefox 仅使用其物理左外边距来定位不确定填充,因此 margin-inline-start 在 RTL 中无效,并且填充永远不会移动。
    • 在 Chromium 和 WebKit 中,填充内容会作为普通块进行布局,一旦外边距将填充内容推到轨道末端之外,外边距的总和就会超过轨道。标准 CSS 通过在 RTL 中忽略 margin-left 来解决此问题,因此该片段会跳转到边缘,而不是滑出边缘。
    • translate 在布局之后应用,因此这两个问题都不适用。
  • 如需在 RTL 中镜像动作(就像 Chrome 的原生栏一样),请在 :dir(rtl) 下反转动画:
progress:dir(rtl) {
  --progress-bar-indeterminate-animation: progress-bar-indeterminate var(--progress-bar-indeterminate-animation-duration) infinite linear reverse;
}
  • 在 progress 上定义公共 --progress-bar-indeterminate-* 属性(其名称已将其范围限定为状态),以便使用者无需了解 :indeterminate 即可替换这些属性。
  • 如需改变片段长度(例如,前缘向前移动,后缘跟上),请将 --progress-bar-indeterminate-animation 设置为两个并发动画:一个动画的关键帧在 cqi(位置)中为 translate 添加动画效果,另一个动画的关键帧为 inline-size(长度)添加动画效果,这样每个动画都有自己的关键帧和加/减速。请勿尝试在单个关键帧列表中同时调整两个边缘;每个关键帧的 animation-timing-function 会应用于该关键帧中的每个属性。
  • --progress-bar-animation 适用于这两种状态,可用于实现确定性微光等效果。revert-rule 仅在设置后才用于替换,以最大限度地提高可组合性。
  • 切勿停用不确定动画(animation: none 会在 Chromium 中留下零宽度填充)。如需减少动画效果,请改为减慢动画速度。
progress {
  /* Lets the fill position itself in track units (cqi) */
  container-type: inline-size;
  /* Segment length relative to the track */
  --progress-bar-indeterminate-size: 30%;
  --progress-bar-indeterminate-animation-duration: 1.5s;

  /* Respect prefers-reduced-motion */
  @media (prefers-reduced-motion: reduce) {
    --progress-bar-indeterminate-animation-duration: 10s;
  }
}

progress:indeterminate {
  --progress-bar-animation: var(
      --progress-bar-indeterminate-animation,
      progress-bar-indeterminate var(--progress-bar-indeterminate-animation-duration) infinite linear
    );
}

/* Add to the existing pseudo-element rules */
progress::-webkit-progress-value {
  animation: var(--progress-bar-animation, revert-rule);
}

progress::-moz-progress-bar {
  animation: var(--progress-bar-animation, revert-rule);
}

/* Physical: left to right regardless of direction (reverse under :dir(rtl) to mirror). */
@keyframes progress-bar-indeterminate {
  from, to {
    inline-size: var(--progress-bar-indeterminate-size);
    /* Pin to the physical left; Chromium would otherwise lay the fill out at the inline start */
    margin-right: auto;
  }
  from { translate: -100%; } /* Own width: fully outside the left edge */
  to { translate: 100cqi; }  /* Track width: fully outside the right edge */
}

其他无障碍注意事项

除了上述准则之外,还应遵循以下准则:

  • 在不抢占焦点的情况下宣布完成:请勿在 <progress> 上设置 tabindex 或调用 .focus(),因为它是非交互式的,将焦点移至它会干扰键盘用户。如需在后台任务完成时发出通知,请更新礼貌状态元素(role="status" 或 aria-live="polite")。
  • 请勿在 <progress> 内添加后备文本:现代浏览器会忽略 <progress> 内的文本,辅助技术也不会使用这些文本。
  • 避免在未加标签的通用 <div> 上使用 aria-busy + aria-describedby。aria-busy 具有不一致的屏幕阅读器支持,并且辅助技术会忽略未命名 <div>(没有角色)上的 aria-describedby。如果某个区域的内容在加载时未准备就绪,请使用 hidden 或 aria-hidden="true" 将其隐藏。
  • 务必确保进度条和轨道之间有适当的对比度,尤其是在添加自定义样式时。
  • 考虑使用其他方式来表示不确定状态:与其他内容并行显示的不确定进度条动画可能会让某些用户分心。

后备策略

accent-color 的回退和浏览器支持

accent-color 属性是渐进增强。 不支持此属性的浏览器会忽略它,并使用其默认界面颜色。

如果不支持 accent-color,并且需要为进度条设置样式,请使用自定义样式设置方法为进度条应用首选样式。

revert-rule 的后备方案和浏览器支持

如果 revert-rule 不受支持,则在未设置 --progress-bar-animation 时,animation: var(--progress-bar-animation, revert-rule) 会回退到 animation-name: revert-rule,时长为 0s,在不存在 @keyframes revert-rule 时,这是一种无害的空操作。