<progress>
요소를 사용하여 색상 적응형 및 액세스 가능한 로드 바를 빌드하는 방법에 관한 기본 개요
이 게시물에서는 <progress>
요소를 사용하여 색상 적응형 및 액세스 가능한 로드 바를 빌드하는 방법을 공유하고자 합니다. 데모를 사용해 보고 소스를 확인하세요.
동영상을 선호한다면 이 게시물의 YouTube 버전을 참조하세요.
개요
<progress>
요소는 완료에 관한 시각적 및 음성 피드백을 사용자에게 제공합니다. 이 시각적 피드백은 양식 진행 상황, 정보 다운로드 또는 업로드 표시, 진행률을 알 수 없지만 작업이 여전히 활성 상태임을 표시하는 등의 시나리오에서 유용합니다.
이 GUI 챌린지는 기존 HTML <progress>
요소와 함께 작동하여 접근성과 관련된 노력을 절약했습니다. 색상과 레이아웃은 기본 제공 요소의 맞춤설정 한계를 높여 구성요소를 현대화하고 디자인 시스템에 더 적합하게 만듭니다.
마크업
여기서는 암시적 관계 대신 명시적 관계 속성을 건너뛸 수 있도록 <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"
를 사용하여 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;
}
_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);
}
색상
브라우저는 진행률 요소에 고유한 색상을 사용하며 하나의 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에서 다음 요소를 표시합니다.
- 페이지를 마우스 오른쪽 버튼으로 클릭하고 Inspect Element를 선택하여 DevTools를 불러옵니다.
- DevTools 창의 오른쪽 상단에 있는 설정 톱니바퀴 아이콘을 클릭합니다.
- 요소 제목에서 사용자 에이전트 shadow DOM 표시 체크박스를 찾아 사용 설정합니다.
Safari 및 Chromium 스타일
Safari 및 Chromium과 같은 WebKit 기반 브라우저는 CSS의 하위 집합을 사용할 수 있는 ::-webkit-progress-bar
및 ::-webkit-progress-value
를 노출합니다. 지금은 앞에서 만든 맞춤 속성을 사용하여 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
의 전환 속성을 사용하지 않습니다.
/* 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()
이 함수에서 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
}
}
자바스크립트 십진법 수학 문제 해결
진행률 기본값인 최대값인 1을 유지하기로 했으므로 데모 증분 및 감소 함수는 십진법 계산을 사용합니다. 자바스크립트 및 기타 언어가 항상 뛰어난 것은 아닙니다.
다음은 수학 결과에서 초과분을 자른 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>
요소의 의사 클래스 스타일 제한 없이 구성요소를 빌드할 여지가 있다고 생각합니다. 살펴볼 만한 가치가 있습니다.
접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다.
데모를 만들고 링크를 트윗해 주세요. 그러면 아래의 커뮤니티 리믹스 섹션에 추가하겠습니다.