Thông tin tổng quan cơ bản về cách tạo thanh tải thích ứng và dễ tiếp cận màu bằng phần tử <progress>
.
Trong bài đăng này, tôi muốn chia sẻ cách
tạo ra một màu sắc thích ứng và
thanh tải có thể truy cập được bằng phần tử <progress>
. Thử
bản minh hoạ và xem
nguồn!
Nếu bạn thích xem video hơn, sau đây là phiên bản của bài đăng này trên YouTube:
Tổng quan
Chiến lược phát hành đĩa đơn
<progress>
cung cấp phản hồi bằng hình ảnh và âm thanh cho người dùng khi hoàn tất. Chiến dịch này
phản hồi trực quan có giá trị cho các tình huống như: tiến trình thông qua biểu mẫu,
hiển thị thông tin tải xuống hoặc tải lên hay thậm chí cho thấy rằng
không xác định được tiến trình, nhưng công việc vẫn đang hoạt động.
Thử thách GUI này đã được hỗ trợ
phần tử HTML <progress>
hiện có để tiết kiệm công sức khi tiếp cận. Chiến lược phát hành đĩa đơn
màu sắc và bố cục đẩy giới hạn tuỳ chỉnh cho phần tử tích hợp sẵn lên
hiện đại hoá thành phần và làm cho thành phần đó phù hợp hơn trong hệ thống thiết kế.
![Các tab sáng và tối trong mỗi trình duyệt mang lại
tổng quan về biểu tượng thích ứng từ trên xuống dưới:
Safari, Firefox, Chrome.](https://web.dev/static/articles/building/a-loading-bar-component/image/light-dark-tabs-each-br-cd04389a437d1.png?authuser=19&hl=vi)
Markup (note: đây là tên ứng dụng)
Tôi đã chọn gói phần tử <progress>
trong một
<label>
so với
Tôi có thể bỏ qua các thuộc tính về mối quan hệ rõ ràng và ưu tiên ngầm định
.
Tôi cũng đã gắn nhãn một phần tử mẹ bị ảnh hưởng bởi trạng thái tải, vì vậy, màn hình
công nghệ đọc có thể chuyển tiếp thông tin đó trở lại người dùng.
<progress></progress>
Nếu không có value
, thì tiến trình của phần tử sẽ là
không xác định.
Thuộc tính max
mặc định là 1, vì vậy tiến trình sẽ nằm trong khoảng từ 0 đến 1. Đang cài đặt max
đến 100, chẳng hạn, sẽ đặt phạm vi thành 0-100. Tôi chọn ở trong phạm vi 0
và 1, chuyển các giá trị tiến trình thành 0,5 hoặc 50%.
Tiến trình gói nhãn
Trong mối quan hệ ngầm ẩn, phần tử tiến trình được bao bọc bởi một nhãn như sau:
<label>Loading progress<progress></progress></label>
Trong phần minh hoạ, tôi đã chọn thêm nhãn cho trình đọc màn hình
.
Bạn có thể thực hiện việc này bằng cách gói văn bản nhãn trong <span>
và áp dụng một số kiểu
để nó nằm ngoài màn hình một cách hiệu quả:
<label>
<span class="sr-only">Loading progress</span>
<progress></progress>
</label>
Với CSS đi kèm sau đây từ WebAIM:
.sr-only {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
Khu vực bị ảnh hưởng bởi tiến trình tải
Nếu có thị lực khoẻ mạnh, bạn có thể dễ dàng liên kết một chỉ báo tiến trình
với các thành phần và khu vực trang có liên quan, nhưng đối với người dùng khiếm thị thì
rất rõ ràng. Hãy cải thiện điều này bằng cách chỉ định
aria-busy
cho phần tử trên cùng. Phần tử này sẽ thay đổi khi quá trình tải hoàn tất.
Hơn nữa, chỉ ra mối quan hệ giữa tiến trình và vùng tải
thông qua tính năng
aria-describedby
.
<main id="loading-zone" aria-busy="true">
…
<progress aria-describedby="loading-zone"></progress>
</main>
Từ JavaScript, hãy chuyển đổi aria-busy
thành true
ở đầu tác vụ và để
false
sau khi hoàn tất.
Bổ sung thuộc tính Aria
Trong khi vai trò ngầm ẩn của phần tử <progress>
là
progressbar
, tôi đã trả lời rõ ràng
dành cho các trình duyệt thiếu vai trò ngầm ẩn đó. Tôi cũng đã thêm thuộc tính này
indeterminate
để đặt phần tử ở trạng thái không xác định một cách rõ ràng, đó là
rõ ràng hơn là việc quan sát phần tử không có value
nào được thiết lập.
<label>
Loading
<progress
indeterminate
role="progressbar"
aria-describedby="loading-zone"
tabindex="-1"
>unknown</progress>
</label>
Sử dụng
tabindex="-1"
để phần tử tiến trình có thể lấy JavaScript làm tâm điểm. Điều này quan trọng đối với
công nghệ trình đọc màn hình, vì tập trung vào tiến trình khi tiến trình thay đổi,
sẽ thông báo cho người dùng khoảng thời gian đã đạt đến tiến trình cập nhật.
Kiểu
Phần tử tiến trình hơi phức tạp khi định kiểu. HTML được tích hợp sẵn có các phần ẩn đặc biệt có thể khó chọn và thường chỉ cung cấp một tập hợp thuộc tính giới hạn được đặt.
Bố cục
Kiểu bố cục nhằm cho phép một số tính linh hoạt trong tiến trình kích thước của phần tử và vị trí nhãn. Trạng thái hoàn thành đặc biệt được thêm vào có thể là một tín hiệu trực quan bổ sung hữu ích nhưng không bắt buộc.
Bố cục <progress>
Chiều rộng của phần tử tiến trình vẫn giữ nguyên để phần này có thể co lại và phát triển
với không gian cần thiết trong thiết kế. Các kiểu tích hợp sẵn bị loại bỏ
đặt appearance
và border
thành none
. Việc này được thực hiện để phần tử có thể
được chuẩn hoá trên các trình duyệt, do mỗi trình duyệt có kiểu riêng cho
.
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;
}
Giá trị của 1e3px
cho _radius
sử dụng số khoa học
ký hiệu để biểu thị một
số lớn để border-radius
luôn được làm tròn. Tương đương với
1000px
. Tôi thích sử dụng định dạng này vì mục tiêu của tôi là sử dụng một giá trị đủ lớn để
Tôi có thể thiết lập rồi quên (và thời gian viết ngắn hơn 1000px
). Điều này cũng
dễ dàng làm cho nó lớn hơn nữa nếu cần: chỉ cần thay đổi 3 thành 4, sau đó 1e4px
là
tương đương với 10000px
.
overflow: hidden
được sử dụng và là một kiểu gây tranh cãi. Đã kiếm được vài
những việc dễ dàng, chẳng hạn như không cần truyền các giá trị border-radius
xuống
theo dõi và theo dõi các phần tử lấp đầy; nhưng điều đó cũng có nghĩa là không phần tử con của tiến trình
có thể nằm bên ngoài phần tử. Một lần lặp nữa cho tiến trình tuỳ chỉnh này
có thể thực hiện được mà không cần overflow: hidden
và phần tử này có thể mở ra một số
cơ hội để có ảnh động hoặc trạng thái hoàn thành tốt hơn.
Tiến trình đã hoàn tất
Bộ chọn CSS thực hiện công việc khó khăn ở đây bằng cách so sánh giá trị tối đa với giá trị và nếu chúng khớp với nhau thì tiến trình sẽ hoàn tất. Khi hoàn tất, một phần tử giả sẽ được tạo và thêm vào cuối phần tử tiến trình, cung cấp thêm tín hiệu hình ảnh đẹp mắt cho phần hoàn tất.
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);
}
Màu
Trình duyệt mang đến màu sắc riêng cho phần tử tiến trình và thích ứng với sáng và tối chỉ với một thuộc tính CSS. Bạn có thể dựa vào một số các bộ chọn đặc biệt dành riêng cho trình duyệt.
Kiểu trình duyệt sáng và tối
Cách chọn sử dụng phần tử <progress>
thích ứng tối và sáng cho trang web của bạn:
color-scheme
là tất cả những gì cần thiết.
progress {
color-scheme: light dark;
}
Màu tô tiến trình thuộc tính duy nhất
Để phủ màu một phần tử <progress>
, hãy sử dụng accent-color
.
progress {
accent-color: rebeccapurple;
}
Lưu ý rằng màu nền của bản nhạc thay đổi từ sáng sang tối tuỳ thuộc vào
accent-color
. Trình duyệt đang đảm bảo độ tương phản phù hợp: khá gọn gàng.
Màu sáng và tối tùy chỉnh hoàn toàn
Đặt hai thuộc tính tuỳ chỉnh trên phần tử <progress>
, một thuộc tính cho màu của bản nhạc
và nhãn còn lại cho màu tiến trình của bản nhạc. Bên trong
prefers-color-scheme
truy vấn phương tiện, cung cấp các giá trị màu mới cho kênh và theo dõi tiến trình.
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%);
}
}
Kiểu lấy tiêu điểm
Trước đó, chúng ta đã cung cấp cho phần tử một chỉ mục thẻ âm để phần tử này có thể được lập trình
tập trung. Sử dụng
:focus-visible
đến
tuỳ chỉnh tiêu điểm để chọn sử dụng kiểu vòng lấy nét thông minh hơn. Với cách này, chuột
thao tác nhấp và lấy nét sẽ không hiển thị vòng lấy nét nhưng thao tác nhấp bằng bàn phím sẽ hiển thị. Chiến lược phát hành đĩa đơn
Video trên YouTube sẽ phân tích sâu hơn về vấn đề này và
đáng xem xét.
progress:focus-visible {
outline-color: var(--_progress);
outline-offset: 5px;
}
Kiểu tùy chỉnh trên các trình duyệt
Tuỳ chỉnh kiểu bằng cách chọn các phần của phần tử <progress>
mà mỗi phần tử
mà trình duyệt hiển thị. Sử dụng phần tử tiến trình là một thẻ đơn, nhưng nó được tạo thành từ một thẻ
một số phần tử con được hiển thị thông qua bộ chọn giả CSS. Dịch vụ Công cụ của Chrome cho nhà phát triển
sẽ hiển thị các thành phần này nếu bạn bật chế độ cài đặt này:
- Nhấp chuột phải vào trang của bạn rồi chọn Kiểm tra phần tử để hiển thị Công cụ cho nhà phát triển.
- Nhấp vào bánh răng Cài đặt ở góc trên cùng bên phải của cửa sổ Công cụ cho nhà phát triển.
- Trong tiêu đề Elements, hãy tìm và bật tuỳ chọn Hiển thị bóng tác nhân người dùng DOM.
Kiểu Safari và Chromium
Các trình duyệt dựa trên WebKit như Safari và Chromium sẽ hiển thị
::-webkit-progress-bar
và ::-webkit-progress-value
, cho phép một tập hợp con của
CSS sẽ được sử dụng. Bây giờ, hãy đặt background-color
bằng cách sử dụng các thuộc tính tuỳ chỉnh
được tạo trước đó, thích ứng với ánh sáng và tối.
/* Safari/Chromium */
progress[value]::-webkit-progress-bar {
background-color: var(--_track);
}
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
}
Kiểu Firefox
Firefox chỉ hiển thị bộ chọn giả ::-moz-progress-bar
trên
Phần tử <progress>
. Điều này cũng có nghĩa là chúng ta không thể phủ màu trực tiếp cho bản nhạc.
/* Firefox */
progress[value]::-moz-progress-bar {
background-color: var(--_progress);
}
Lưu ý rằng Firefox có tập hợp màu của tuyến đường từ accent-color
trong khi iOS Safari
có bản nhạc màu xanh dương nhạt. Điều này cũng tương tự ở chế độ tối: Firefox có một vệt tối nhưng
không phải màu tuỳ chỉnh chúng tôi đã thiết lập
và hoạt động trong các trình duyệt dựa trên Webkit.
Hoạt ảnh
Khi làm việc với bộ chọn giả được tích hợp sẵn trong trình duyệt, bộ chọn này thường gặp hạn chế tập hợp các thuộc tính CSS được phép.
Tạo ảnh động cho quá trình lấp đầy bản nhạc
Thêm hiệu ứng chuyển đổi vào
inline-size
/
phần tử tiến trình hoạt động đối với Chromium nhưng không hoạt động với Safari. Firefox cũng
không sử dụng thuộc tính chuyển đổi trên ::-moz-progress-bar
.
/* Chromium Only 😢 */
progress[value]::-webkit-progress-value {
background-color: var(--_progress);
transition: inline-size .25s ease-out;
}
Tạo ảnh động cho trạng thái :indeterminate
Ở đây, tôi có thể sáng tạo hơn một chút để có thể cung cấp ảnh động. Phần tử giả cho Chromium được tạo và áp dụng hiệu ứng chuyển màu với hiệu ứng động quay lại và cho cả ba trình duyệt.
Thuộc tính tuỳ chỉnh
Thuộc tính tuỳ chỉnh rất phù hợp với nhiều thứ, nhưng một trong những thuộc tính tôi yêu thích thì đơn giản là
đặt tên cho một giá trị CSS trông kỳ diệu khác. Theo dõi khá
phức tạp
linear-gradient
!
nhưng với một cái tên hay. Mục đích và trường hợp sử dụng của trình cung cấp này có thể được hiểu rõ.
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;
}
Các thuộc tính tuỳ chỉnh cũng sẽ giúp mã ở trạng thái DRY vì một lần nữa, chúng ta không thể nhóm các bộ chọn dành riêng cho trình duyệt này lại với nhau.
Khung hình chính
Mục tiêu là một ảnh động vô hạn chạy qua lại. Bắt đầu và kết thúc
khung hình chính sẽ được thiết lập trong CSS. Chỉ cần một khung hình chính là khung hình chính ở giữa
tại 50%
để tạo một ảnh động quay lại vị trí bắt đầu của ảnh động,
lặp lại!
@keyframes progress-loading {
50% {
background-position: left;
}
}
Nhắm mục tiêu theo từng trình duyệt
Không phải trình duyệt nào cũng cho phép tạo phần tử giả trên <progress>
hoặc cho phép tạo ảnh động cho thanh tiến trình. Hỗ trợ nhiều trình duyệt khác
tạo ảnh động cho bản nhạc so với phần tử giả, nên tôi nâng cấp từ các phần tử giả dưới dạng
thành đế và các thanh hoạt ảnh.
Phần tử giả Chromium
Chromium không cho phép phần tử giả: ::after
được dùng với một vị trí để che phủ
phần tử. Các thuộc tính tuỳ chỉnh không xác định được sử dụng, và phía sau và
hoạt ảnh hướng về phía trước hoạt động rất tốt.
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);
}
Thanh tiến trình Safari
Đối với Safari, các thuộc tính tuỳ chỉnh và ảnh động được áp dụng cho thanh tiến trình của phần tử giả:
progress:indeterminate::-webkit-progress-bar {
background: var(--_indeterminate-track);
background-size: var(--_indeterminate-track-size);
background-position: right;
animation: var(--_indeterminate-track-animation);
}
Thanh tiến trình của Firefox
Đối với Firefox, các thuộc tính tuỳ chỉnh và ảnh động cũng được áp dụng cho thanh tiến trình của phần tử giả:
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 đóng vai trò quan trọng trong phần tử <progress>
. Chiến dịch này kiểm soát
giá trị được gửi đến phần tử và đảm bảo có đủ thông tin trong
cho trình đọc màn hình.
const state = {
val: null
}
Bản minh hoạ có các nút để kiểm soát tiến trình; họ cập nhật state.val
sau đó gọi một hàm để cập nhật
DOM.
document.querySelector('#complete').addEventListener('click', e => {
state.val = 1
setProgress()
})
setProgress()
Hàm này là nơi diễn ra hoạt động điều phối giao diện/trải nghiệm người dùng. Hãy bắt đầu bằng cách tạo một
Hàm setProgress()
. Không cần tham số nào vì tham số đó có quyền truy cập vào
Đối tượng state
, phần tử tiến trình và vùng <main>
.
const setProgress = () => {
}
Đặt trạng thái tải trên vùng <main>
Tuỳ thuộc vào tiến trình đã hoàn tất hay chưa, <main>
liên quan
cần cập nhật lên
aria-busy
thuộc tính:
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
}
Xoá thuộc tính nếu không xác định được thời lượng tải
Nếu giá trị này là không xác định hoặc chưa được đặt, thì null
trong cách sử dụng này, hãy xoá value
và
Thuộc tính aria-valuenow
. Thao tác này sẽ biến <progress>
thành không xác định.
const setProgress = () => {
zone.setAttribute('aria-busy', state.val < 1)
if (state.val === null) {
progress.removeAttribute('aria-valuenow')
progress.removeAttribute('value')
progress.focus()
return
}
}
Khắc phục vấn đề về toán học thập phân trong JavaScript
Vì tôi chọn sử dụng tiến trình mặc định tối đa là 1, nên bản minh hoạ
hàm tăng và hàm giảm sử dụng toán học thập phân. JavaScript và các kênh khác
không phải lúc nào cũng giỏi
.
Đây là hàm roundDecimals()
sẽ loại bỏ phần thừa khỏi toán học
kết quả:
const roundDecimals = (val, places) =>
+(Math.round(val + "e+" + places) + "e-" + places)
Làm tròn giá trị để giá trị đó có thể được trình bày và dễ đọc:
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 + "%"
}
Đặt giá trị cho trình đọc màn hình và trạng thái trình duyệt
Giá trị được sử dụng tại ba vị trí trong DOM:
- Thuộc tính
value
của phần tử<progress>
. - Thuộc tính
aria-valuenow
. - Nội dung văn bản bên trong
<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
}
Tập trung vào tiến độ
Sau khi cập nhật các giá trị, người dùng nhìn thấy sẽ thấy tiến trình thay đổi, nhưng màn hình
người dùng độc giả chưa được đưa ra thông báo về thay đổi. Tập trung vào
Phần tử <progress>
và trình duyệt sẽ thông báo việc cập nhật!
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()
}
Kết luận
Giờ bạn đã biết cách tôi thực hiện điều đó, bạn sẽ làm cách nào‽ 🙂
Chắc chắn sẽ có một vài thay đổi tôi muốn thực hiện nếu có cơ hội khác. Tôi nghĩ vẫn còn chỗ để dọn dẹp thành phần hiện tại, cũng như có thể thử tạo một thành phần mà không bị hạn chế về kiểu giả lớp của phần tử <progress>
. Rất đáng để khám phá!
Hãy đa dạng hoá phương pháp tiếp cận và tìm hiểu tất cả các cách xây dựng ứng dụng trên web.
Tạo một bản minh hoạ, tweet cho tôi các đường liên kết và tôi sẽ thêm vào vào phần bản phối lại của cộng đồng dưới đây!
Bản phối lại của cộng đồng
- Varun KS – nguồn và bản minh hoạ