Thông tin tổng quan cơ bản về cách tạo thành phần đường dẫn dạng chuỗi liên kết thích ứng và hỗ trợ tiếp cận để người dùng điều hướng trên trang web của bạn.
Trong bài đăng này, tôi muốn chia sẻ cách tạo thành phần breadcrumb (tập hợp liên kết phân cấp). Dùng thử bản minh hoạ.
Nếu bạn thích xem video, hãy xem phiên bản video của bài đăng này trên YouTube:
Tổng quan
Thành phần breadcrumbs (dấu vết) cho biết vị trí của người dùng trong hệ phân cấp trang web. Tên này bắt nguồn từ câu chuyện Hansel và Gretel, trong đó hai nhân vật này đã rắc bánh mì vụn phía sau họ trong một số khu rừng tối và có thể tìm đường về nhà bằng cách lần theo dấu vết bánh mì.
Các đường dẫn liên kết phân cấp trong bài đăng này không phải là đường dẫn liên kết phân cấp tiêu chuẩn mà là đường dẫn liên kết phân cấp kiểu. Các thành phần này cung cấp chức năng bổ sung bằng cách đưa các trang đồng cấp vào thành phần điều hướng bằng <select>
, cho phép truy cập nhiều cấp.
Trải nghiệm người dùng ở chế độ nền
Trong video minh hoạ thành phần ở trên, các danh mục phần giữ chỗ là thể loại trò chơi điện tử. Dấu vết này được tạo bằng cách điều hướng theo đường dẫn sau: home »
rpg » indie » on sale
, như minh hoạ bên dưới.
Thành phần đường dẫn này cho phép người dùng di chuyển qua hệ thống phân cấp thông tin này; chuyển sang các nhánh và chọn các trang một cách nhanh chóng và chính xác.
Kiến trúc thông tin
Tôi thấy việc suy nghĩ theo bộ sưu tập và mục rất hữu ích.
Bộ sưu tập
Bộ sưu tập là một mảng gồm các tuỳ chọn để lựa chọn. Trên trang chủ của nguyên mẫu đường dẫn dạng chuỗi trong bài đăng này, các bộ sưu tập là FPS, RPG, brawler, dungeon crawler, thể thao và câu đố.
Mục
Trò chơi điện tử là một mục, một bộ sưu tập cụ thể cũng có thể là một mục nếu bộ sưu tập đó đại diện cho một bộ sưu tập khác. Ví dụ: RPG là một mục và một bộ sưu tập hợp lệ. Khi đó là một mặt hàng, người dùng đang ở trang bộ sưu tập đó. Ví dụ: các danh mục phụ này nằm trên trang Trò chơi nhập vai, trong đó có danh sách các trò chơi nhập vai, bao gồm cả các danh mục phụ bổ sung AAA, Indie và Tự xuất bản.
Theo khoa học máy tính, thành phần breadcrumb (tập hợp liên kết phân cấp) này đại diện cho một mảng đa chiều:
const rawBreadcrumbData = {
"FPS": {...},
"RPG": {
"AAA": {...},
"indie": {
"new": {...},
"on sale": {...},
"under 5": {...},
},
"self published": {...},
},
"brawler": {...},
"dungeon crawler": {...},
"sports": {...},
"puzzle": {...},
}
Ứng dụng hoặc trang web của bạn sẽ có cấu trúc thông tin tuỳ chỉnh (IA) tạo ra một mảng nhiều chiều khác, nhưng tôi hy vọng khái niệm về trang đích của bộ sưu tập và duyệt qua hệ phân cấp cũng có thể đưa vào đường dẫn của bạn.
Bố cục
Markup (note: đây là tên ứng dụng)
Các thành phần tốt bắt đầu bằng HTML phù hợp. Trong phần tiếp theo, tôi sẽ trình bày các lựa chọn về mã đánh dấu và mức độ tác động của các lựa chọn đó đến thành phần tổng thể.
Bảng phối màu tối và sáng
<meta name="color-scheme" content="dark light">
Thẻ meta color-scheme
trong đoạn mã trên thông báo cho trình duyệt rằng trang này muốn có kiểu trình duyệt sáng và tối. Dấu vết bánh mì mẫu không bao gồm bất kỳ CSS nào cho các bảng phối màu này, vì vậy, dấu vết bánh mì sẽ sử dụng màu mặc định do trình duyệt cung cấp.
Phần tử điều hướng
<nav class="breadcrumbs" role="navigation"></nav>
Bạn nên sử dụng phần tử <nav>
để điều hướng trang web. Phần tử này có vai trò điều hướng ARIA ngầm ẩn.
Trong quá trình kiểm thử, tôi nhận thấy rằng việc có thuộc tính role
đã thay đổi cách trình đọc màn hình tương tác với phần tử, thuộc tính này thực sự được thông báo là điều hướng, vì vậy, tôi đã chọn thêm thuộc tính này.
Biểu tượng
Khi một biểu tượng được lặp lại trên một trang, phần tử SVG <use>
có nghĩa là bạn có thể xác định path
một lần và sử dụng nó cho tất cả các thực thể của biểu tượng. Điều này giúp tránh lặp lại cùng một thông tin đường dẫn, gây ra tài liệu lớn hơn và có thể dẫn đến tình trạng đường dẫn không nhất quán.
Để sử dụng kỹ thuật này, hãy thêm một phần tử SVG bị ẩn vào trang và gói các biểu tượng đó trong một phần tử <symbol>
bằng một mã nhận dạng duy nhất:
<svg style="display: none;">
<symbol id="icon-home">
<title>A home icon</title>
<path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
</symbol>
<symbol id="icon-dropdown-arrow">
<title>A down arrow</title>
<path d="M19 9l-7 7-7-7"/>
</symbol>
</svg>
Trình duyệt đọc HTML SVG, đưa thông tin biểu tượng vào bộ nhớ và tiếp tục với phần còn lại của trang tham chiếu mã nhận dạng để sử dụng thêm biểu tượng, như sau:
<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
<use href="#icon-home" />
</svg>
<svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
<use href="#icon-dropdown-arrow" />
</svg>
Xác định một lần, sử dụng nhiều lần như bạn muốn, với mức tác động tối thiểu đến hiệu suất trang và kiểu linh hoạt. Lưu ý aria-hidden="true"
được thêm vào phần tử SVG.
Các biểu tượng này không hữu ích cho những người duyệt web chỉ nghe thấy nội dung, việc ẩn chúng khỏi những người dùng đó sẽ ngăn họ gây thêm tiếng ồn không cần thiết.
Đường liên kết phân tách .crumb
Đây là điểm khác biệt giữa đường dẫn truyền thống và đường dẫn trong thành phần này.
Thông thường, đây sẽ chỉ là một đường liên kết <a>
, nhưng tôi đã thêm trải nghiệm người dùng xuyên suốt bằng một lựa chọn được ngụy trang. Lớp .crumb
chịu trách nhiệm bố trí đường liên kết và biểu tượng, trong khi .crumbicon
chịu trách nhiệm xếp chồng biểu tượng và phần tử chọn cùng nhau. Tôi gọi đó là đường liên kết phân tách vì các chức năng của đường liên kết này rất giống với nút phân tách, nhưng dành cho việc điều hướng trang.
<span class="crumb">
<a href="#sub-collection-b">Category B</a>
<span class="crumbicon">
<svg>...</svg>
<select class="disguised-select" title="Navigate to another category">
<option>Category A</option>
<option selected>Category B</option>
<option>Category C</option>
</select>
</span>
</span>
Một đường liên kết và một số tuỳ chọn không có gì đặc biệt nhưng lại thêm chức năng vào một đường dẫn đơn giản. Việc thêm title
vào phần tử <select>
sẽ hữu ích cho người dùng trình đọc màn hình, cung cấp cho họ thông tin về thao tác của nút. Tuy nhiên, tính năng này cũng cung cấp sự trợ giúp tương tự cho mọi người, bạn sẽ thấy tính năng này ở chính giữa trên iPad. Một thuộc tính cung cấp ngữ cảnh nút cho nhiều người dùng.
Trang trí dòng phân cách
<span class="crumb-separator" aria-hidden="true">→</span>
Bạn không bắt buộc phải sử dụng dòng phân cách, chỉ cần thêm một dòng phân cách cũng đã rất hiệu quả (xem ví dụ thứ ba trong video ở trên). Sau đó, tôi cung cấp từng aria-hidden="true"
vì chúng chỉ để trang trí chứ không phải là
nội dung mà trình đọc màn hình cần thông báo.
Thuộc tính gap
được đề cập tiếp theo giúp cho khoảng cách của các thuộc tính này trở nên đơn giản.
Kiểu
Vì màu này sử dụng màu hệ thống, nên chủ yếu là các khoảng trống và ngăn xếp cho kiểu!
Hướng và luồng bố cục
Phần tử điều hướng chính nav.breadcrumbs
đặt một thuộc tính tuỳ chỉnh trong phạm vi để con sử dụng, đồng thời thiết lập bố cục được căn chỉnh theo chiều ngang. Điều này đảm bảo rằng các đường dẫn, đường phân chia và biểu tượng được căn chỉnh.
.breadcrumbs {
--nav-gap: 2ch;
display: flex;
align-items: center;
gap: var(--nav-gap);
padding: calc(var(--nav-gap) / 2);
}
Mỗi .crumb
cũng thiết lập một bố cục ngang được căn chỉnh theo chiều dọc với một số khoảng trống, nhưng nhắm mục tiêu đặc biệt đến các đường liên kết con và chỉ định kiểu white-space: nowrap
. Điều này rất quan trọng đối với các chuỗi breadcrumb gồm nhiều từ vì chúng ta không muốn các chuỗi này xuất hiện trên nhiều dòng. Ở phần sau của bài đăng này, chúng ta sẽ thêm kiểu để xử lý tình trạng tràn lề theo chiều ngang mà thuộc tính white-space
này gây ra.
.crumb {
display: inline-flex;
align-items: center;
gap: calc(var(--nav-gap) / 4);
& > a {
white-space: nowrap;
&[aria-current="page"] {
font-weight: bold;
}
}
}
aria-current="page"
được thêm vào để giúp đường liên kết của trang hiện tại nổi bật so với các đường liên kết khác. Người dùng trình đọc màn hình không chỉ có chỉ báo rõ ràng rằng đường liên kết là dành cho trang hiện tại, mà chúng tôi còn tạo kiểu trực quan cho phần tử này để giúp người dùng bình thường có được trải nghiệm người dùng tương tự.
Thành phần .crumbicon
sử dụng lưới để xếp chồng một biểu tượng SVG với phần tử <select>
"gần như không nhìn thấy".
.crumbicon {
--crumbicon-size: 3ch;
display: grid;
grid: [stack] var(--crumbicon-size) / [stack] var(--crumbicon-size);
place-items: center;
& > * {
grid-area: stack;
}
}
Phần tử <select>
là phần tử cuối cùng trong DOM, vì vậy, phần tử này nằm ở đầu ngăn xếp và có thể tương tác. Thêm kiểu opacity: .01
để phần tử vẫn có thể sử dụng được, kết quả là một hộp chọn vừa vặn với hình dạng của biểu tượng.
Đây là một cách hay để tuỳ chỉnh giao diện của phần tử <select>
trong khi vẫn duy trì chức năng tích hợp.
.disguised-select {
inline-size: 100%;
block-size: 100%;
opacity: .01;
font-size: min(100%, 16px); /* Defaults to 16px; fixes iOS zoom */
}
Trình đơn mục bổ sung
Dấu vết bánh mì phải có thể đại diện cho một đường dẫn rất dài. Tôi thích cho phép các thành phần nằm ngoài màn hình theo chiều ngang khi thích hợp và tôi cảm thấy thành phần đường dẫn này đủ tiêu chuẩn.
.breadcrumbs {
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x proximity;
scroll-padding-inline: calc(var(--nav-gap) / 2);
& > .crumb:last-of-type {
scroll-snap-align: end;
}
@supports (-webkit-hyphens:none) { & {
scroll-snap-type: none;
}}
}
Các kiểu mục bổ sung thiết lập trải nghiệm người dùng sau:
- Cuộn ngang với vùng chứa cuộn xuống cuối cùng.
- Khoảng đệm cuộn theo chiều ngang.
- Một điểm chụp nhanh trên mảnh cuối cùng. Điều này có nghĩa là khi tải trang, mảnh đầu tiên sẽ tải nhanh và hiển thị.
- Xoá điểm chụp nhanh khỏi Safari, điểm này gặp khó khăn với các tổ hợp hiệu ứng cuộn ngang và chụp nhanh.
Truy vấn về nội dung nghe nhìn
Một điều chỉnh tinh tế cho các khung nhìn nhỏ hơn là ẩn nhãn "Trang chủ", chỉ để lại biểu tượng:
@media (width <= 480px) {
.breadcrumbs .home-label {
display: none;
}
}
Hỗ trợ tiếp cận
Có chuyển động
Không có nhiều chuyển động trong thành phần này, nhưng bằng cách gói chuyển đổi trong một bước kiểm tra prefers-reduced-motion
, chúng ta có thể ngăn chặn chuyển động không mong muốn.
@media (prefers-reduced-motion: no-preference) {
.crumbicon {
transition: box-shadow .2s ease;
}
}
Không cần thay đổi kiểu nào khác, hiệu ứng di chuột và lấy nét rất tuyệt vời và có ý nghĩa mà không cần transition
, nhưng nếu chuyển động ổn thì chúng ta sẽ thêm một hiệu ứng chuyển đổi tinh tế vào hoạt động tương tác.
JavaScript
Trước tiên, bất kể loại bộ định tuyến bạn sử dụng trong trang web hoặc ứng dụng của mình, khi người dùng thay đổi đường dẫn, bạn cần cập nhật URL và hiển thị cho người dùng trang thích hợp. Thứ hai, để chuẩn hoá trải nghiệm người dùng, hãy đảm bảo không có thao tác điều hướng nào không mong muốn xảy ra khi người dùng chỉ duyệt qua các tuỳ chọn <select>
.
Hai biện pháp quan trọng đối với trải nghiệm người dùng mà JavaScript cần xử lý: Chọn đã thay đổi và muốn ngăn chặn việc kích hoạt sự kiện <select>
thay đổi.
Bạn cần ngăn chặn sự kiện vội vàng do sử dụng phần tử <select>
. Trên Windows Edge và có thể cả các trình duyệt khác, sự kiện chọn changed
sẽ kích hoạt khi người dùng duyệt qua các tuỳ chọn bằng bàn phím. Đó là lý do tôi gọi phương thức này là eager (tham lam), vì người dùng chỉ chọn giả lập tuỳ chọn, chẳng hạn như di chuột hoặc lấy tiêu điểm, nhưng chưa xác nhận lựa chọn bằng enter
hoặc click
. Sự kiện háo hức khiến bạn không thể sử dụng tính năng thay đổi danh mục thành phần này, vì việc mở hộp chọn và chỉ cần duyệt qua một mục sẽ kích hoạt sự kiện và thay đổi trang trước khi người dùng sẵn sàng.
Sự kiện thay đổi <select>
tốt hơn
const crumbs = document.querySelectorAll('.breadcrumbs select')
const allowedKeys = new Set(['Tab', 'Enter', ' '])
const preventedKeys = new Set(['ArrowUp', 'ArrowDown'])
// watch crumbs for changes,
// ensures it's a full value change, not a user exploring options via keyboard
crumbs.forEach(nav => {
let ignoreChange = false
nav.addEventListener('change', e => {
if (ignoreChange) return
// it's actually changed!
})
nav.addEventListener('keydown', ({ key }) => {
if (preventedKeys.has(key))
ignoreChange = true
else if (allowedKeys.has(key))
ignoreChange = false
})
})
Chiến lược cho việc này là theo dõi các sự kiện nhấn bàn phím trên mỗi phần tử <select>
và xác định xem phím được nhấn là xác nhận điều hướng (Tab
hoặc Enter
) hay điều hướng không gian (ArrowUp
hoặc ArrowDown
). Với quyết định này, thành phần có thể quyết định chờ hoặc tiếp tục khi sự kiện cho phần tử <select>
kích hoạt.
Kết luận
Giờ thì bạn đã biết cách tôi làm, còn bạn thì sao‽ 🙂
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 phần bản phối lại của cộng đồng ở bên dưới!
Bản phối lại của cộng đồng
- Tux Solbakk dưới dạng thành phần web: mã và bản minh hoạ