加载条

此模式展示了如何使用 <progress> 元素构建颜色自适应且可访问的加载栏。

完整文章 · YouTube 上的视频 · GitHub 上的来源

<main id="loading-zone" aria-busy="true">
 
<p>Loading Level</p>
 
<div class="card">
   
<label>
     
<span class="sr-only">Loading progress</span>
     
<progress
       
indeterminate
       
role="progressbar"
       
aria-describedby="loading-zone"
       
tabindex="-1"
     
>unknown</progress>
   
</label>
 
</div>
</main>

        progress
{
 
--_track: hsl(228 100% 90%);
 
--_track-size: min(10px, 1ex);
 
--_progress: hsl(228 100% 50%);
 
--_radius: 1e3px;
 
--_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;
 
 
/*  reset  */
 
appearance: none;
 
border: none;
 
 
/*  custom style  */
 
position: relative;
 
height: var(--_track-size);
 
border-radius: var(--_radius);
 
overflow: hidden;

 
@media (prefers-color-scheme: dark) {
   
--_track: hsl(228 20% 30%);
   
--_progress: hsl(228 100% 75%);
 
}

 
&:focus-visible {
   
outline-color: var(--_progress);
 
}
 
 
/*  Safari/Chromium  */
 
&[value]::-webkit-progress-bar {
   
background-color: var(--_track);
 
}
 
 
&[value]::-webkit-progress-value {
   
background-color: var(--_progress);
   
transition: inline-size .25s ease-out;
 
}
 
 
/*  Firefox  */
 
&[value]::-moz-progress-bar {
   
background-color: var(--_progress);
 
}
 
 
/*  indeterminate  */
 
&: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);
 
}
 
 
/*  indeterminate Safari  */
 
&:indeterminate::-webkit-progress-bar {
   
background: var(--_indeterminate-track);
   
background-size: var(--_indeterminate-track-size);
   
background-position: right;
   
animation: var(--_indeterminate-track-animation);
 
}
 
 
/*  indeterminate Firefox  */
 
&:indeterminate::-moz-progress-bar {
   
background: var(--_indeterminate-track);
   
background-size: var(--_indeterminate-track-size);
   
background-position: right;
   
animation: var(--_indeterminate-track-animation);
 
}
 
 
/*  complete  */
 
&:not([max])[value="1"]::before,
 
&[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);
 
}
}

@keyframes progress-loading {
 
50% {
   
background-position: left;
 
}
}
       

       
const progress = document.querySelector('progress')
const zone     = document.querySelector('#loading-zone')

const state = {
  val
: .1
}

const roundDecimals = (val, places) =>
 
+(Math.round(val + "e+" + places)  + "e-" + places)

const setProgress = () => {
 
// set loading zone status
  zone
.setAttribute('aria-busy', state.val < 1)

 
// clear attributes if no value to show
 
//  will show indeterminate state
 
if (state.val === null) {
    progress
.removeAttribute('aria-valuenow')
    progress
.removeAttribute('value')
    progress
.focus()
   
return
 
}

 
// round bad JS decimal math
 
const val = roundDecimals(state.val, 2)
 
const valPercent = val * 100 + "%"
 
 
// set value for screenreaders and element values
  progress
.value = val
  progress
.setAttribute('aria-valuenow', valPercent)
  progress
.innerText = valPercent

 
// focus so screenreaders hear the announced value update
  progress
.focus()
}