Xây dựng thành phần breadcrumb (tập hợp liên kết phân cấp)

Thông tin tổng quan cơ bản về cách tạo thành phần breadcrumb (tập hợp liên kết phân cấp) thích ứng và dễ tiếp cận để người dùng di chuyển trên trang web của bạn.

Trong bài đăng này, tôi muốn chia sẻ suy nghĩ về cách xây dựng các thành phần breadcrumb (tập hợp liên kết phân cấp). Dùng thử bản minh hoạ.

Bản minh hoạ

Nếu bạn thích video, đây là phiên bản YouTube của bài đăng này:

Tổng quan

Thành phần breadcrumb (tập hợp liên kết phân cấp) cho thấy vị trí của người dùng trong hệ thống phân cấp trang web. Tên đó là Hansel và Gretel, hai người đã thả đường dẫn phía sau vào một số khu rừng tối và có thể tìm về nhà bằng cách truy vết các mảnh vụn ngược trở lại.

Đường dẫn trong bài đăng này không phải là đường dẫn chuẩn mà giống như đường dẫn. Chúng cung cấp thêm chức năng bằng cách đưa các trang đồng cấp ngay vào bảng điều hướng bằng <select>, cho phép truy cập ở nhiều lớ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ử. Đường nhỏ này được tạo bằng cách di chuyển theo đường dẫn sau: home » rpg » indie » on sale, như minh hoạ bên dưới.

Thành phần breadcrumb này sẽ cho phép người dùng di chuyển qua hệ phân cấp thông tin này; nhảy các nhánh và chọn các trang có tốc độ và độ chính xác.

Kiến trúc thông tin

Tôi thấy sẽ rất hữu ích khi suy nghĩ về các bộ sưu tập và mục.

Bộ sưu tập

Bộ sưu tập là một loạt các tuỳ chọn để lựa chọn. Trên trang chủ của nguyên mẫu breadcrumb của bài đăng này, các bộ sưu tập gồm các bộ sưu tập gồm FPS, RPG, brawler, dungeon thu thập thông tin, thể thao và giải đố.

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 đại diện cho một bộ sưu tập khác. Ví dụ: RPG là một mặt hàng và một bộ sưu tập hợp lệ. Khi mục này là một mục, người dùng sẽ ở trên trang bộ sưu tập đó. Ví dụ: Chúng nằm trên trang RPG, nơi hiển thị danh sách trò chơi nhập vai, bao gồm cả các danh mục phụ khác AAA, Indie và Tự phát hành.

Trong thuật ngữ 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 đa chiều khác, nhưng tôi hy vọng khái niệm trang đích thu thập và truyền tải thứ bậc cũng có thể đưa nó vào breadcrumb 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 thích hợp. Trong phần tiếp theo này, tôi sẽ đề cập đến các lựa chọn đánh dấu và 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. Đường dẫn ví dụ 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 breadcrumb sẽ sử dụng màu mặc định do trình duyệt cung cấp.

<nav class="breadcrumbs" role="navigation"></nav>

Bạn nên dùng phần tử <nav> để điều hướng trên trang web có vai trò điều hướng ngầm ẩn của ARIA. Trong quá trình kiểm thử, tôi nhận thấy 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ử, nhưng thuộc tính này thực sự được thông báo là thành phần điều hướng và 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ử <use> của SVG có nghĩa là bạn có thể xác định path một lần và sử dụng biểu tượng này cho mọi thực thể của biểu tượng. Điều này giúp thông tin đường dẫn không bị lặp lại, gây ra các tài liệu lớn hơn và nguy cơ dẫn đến tình trạng không nhất quán về đường dẫn.

Để sử dụng kỹ thuật này, hãy thêm một phần tử SVG ẩn vào trang và gói các biểu tượng trong 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 về biểu tượng vào bộ nhớ và tiếp tục phần còn lại của trang tham chiếu đến 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>

Công cụ cho nhà phát triển hiển thị phần tử sử dụng SVG được kết xuất.

Hãy xác định một lần, sử dụng bao nhiêu lần tuỳ thích, với tác động tối thiểu đến hiệu suất trang và tạo kiểu linh hoạt. Lưu ý rằng aria-hidden="true" được thêm vào phần tử SVG. Các biểu tượng không hữu ích đối với những người duyệt web chỉ nghe được nội dung. Nếu ẩn các biểu tượng này với những người dùng đó, họ sẽ không gây ra tiếng ồn không cần thiết.

Đây là nơi breadcrumb (tập hợp liên kết phân cấp) truyền thống và các đường dẫn trong thành phần này khác nhau. Thông thường, đây chỉ là một đường liên kết <a>, nhưng tôi đã thêm trải nghiệm người dùng truyền tải bằng một lựa chọn nguỵ trang. Lớp .crumb chịu trách nhiệm sắp xếp đường liên kết và biểu tượng, còn .crumbicon chịu trách nhiệm xếp biểu tượng và phần tử chọn với nhau. Tôi gọi đây là đường liên kết phân tách vì chức năng của nó rất giống với nút phân tách, nhưng dùng để đ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>

Đường liên kết và một số tuỳ chọn không có gì đặc biệt nhưng bổ sung thêm chức năng cho một breadcrumb (tập hợp liên kết phân cấp) đơn giản. Việc thêm title vào phần tử <select> sẽ hữu ích đối với người dùng trình đọc màn hình, đồng thời 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 trợ giúp tương tự cho tất cả những người dùng khác. Bạn sẽ thấy nút này nằm ở phía trước và chính giữa trên iPad. Một thuộc tính cung cấp ngữ cảnh của nút cho nhiều người dùng.

Ảnh chụp màn hình cho thấy phần tử chọn ẩn đang được di chuột và chú giải công cụ theo ngữ cảnh đang hiển thị.

Đồ trang trí cho dòng phân cách

<span class="crumb-separator" aria-hidden="true">→</span>

Bạn không bắt buộc phải thêm dòng phân cách, nhưng bạn chỉ cần thêm một dòng phân cách cũng sẽ hiệu quả (xem ví dụ thứ ba trong video ở trên). Sau đó, tôi sẽ đưa ra từng aria-hidden="true" vì chúng chỉ mang tính trang trí và không phải là nội dung mà trình đọc màn hình cần thông báo.

Tiếp theo, thuộc tính gap giúp đơn giản hoá khoảng cách giữa các thành phần này.

Kiểu

Vì màu sắc sử dụng màu hệ thống, nên đây chủ yếu là khoảng trống và ngăn xếp cho kiểu!

Hướng và luồng bố cục

Công cụ cho nhà phát triển hiển thị căn chỉnh điều hướng đường dẫn với tính năng lớp phủ linh hoạt.

Phần tử điều hướng chính nav.breadcrumbs đặt một thuộc tính tuỳ chỉnh theo phạm vi để thành phần con sử dụng, đồng thời thiết lập một bố cục được căn chỉnh theo chiều ngang theo chiều dọc. Điều này đảm bảo rằng các mảnh vụn, đường phân chia và biểu tượng khớp với nhau.

.breadcrumbs {
  --nav-gap: 2ch;

  display: flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: calc(var(--nav-gap) / 2);
}

Một breadcrumb (tập hợp liên kết phân cấp) hiển thị theo chiều dọc với lớp phủ flexbox.

Mỗi .crumb cũng thiết lập một bố cục căn chỉnh theo chiều ngang theo chiều ngang với một số khoảng trống, nhưng đặc biệt nhắm mục tiêu đến phần tử con liên kết của nó và chỉ định kiểu white-space: nowrap. Điều này rất quan trọng đối với các breadcrumb nhiều từ vì chúng ta không muốn chúng đi theo nhiều dòng. Trong phần sau của bài đăng này, chúng ta sẽ thêm các kiểu để xử lý tình trạng tràn 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;
    }
  }
}

Thêm aria-current="page" để giúp đường liên kết đến trang hiện tại nổi bật so với các đường liên kết còn lại. 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 dành cho trang hiện tại, chúng tôi đã tạo kiểu cho phần tử một cách trực quan để giúp người dùng sáng mắt 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 biểu tượng SVG có phần tử <select> "gần như vô hình".

Công cụ cho nhà phát triển dạng lưới hiển thị lớp phủ một nút mà cả hàng và cột đều được đặt tên là ngăn xếp.

.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> nằm ở cuối cùng trong DOM, vì vậy, phần tử này sẽ nằm ở đầu ngăn xếp và có tính 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 phù hợp hoàn hảo 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

Đường dẫn phải có khả năng đại diện cho một đường mòn rất dài. Tôi rất hâm mộ việc cho phép mọi thứ di chuyển ra khỏ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 đủ điều kiện để di chuyển ra khỏi màn hình khi thích hợp.

.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;
  }}
}

Kiểu tràn thiết lập trải nghiệm người dùng sau:

  • Cuộn theo chiều ngang có vùng chứa cuộn quá mức.
  • Khoảng đệm cuộn theo chiều ngang.
  • Một dấu tích nhỏ trên mảnh vụn cuối cùng. Điều này có nghĩa là khi tải trang, mảnh đầu tiên sẽ được tải nhanh và trong khung hiển thị.
  • Xoá điểm chụp nhanh khỏi Safari, vốn gặp khó khăn với việc kết hợp hiệu ứng chụp nhanh và cuộn ngang.

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 "Home", tức là chỉ để lại biểu tượng:

@media (width <= 480px) {
  .breadcrumbs .home-label {
    display: none;
  }
}

Đặt cạnh nhau các breadcrumb (tập hợp liên kết phân cấp) có và không có nhãn trang chủ để so sánh.

Hỗ trợ tiếp cận

Chuyển động

Thành phần này không có nhiều chuyển động, nhưng bằng cách gói lượt chuyển đổi trong quy trình 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ó kiểu nào khác cần thay đổi, các hiệu ứng di chuột và lấy nét sẽ tuyệt vời và có ý nghĩa khi không có transition, nhưng nếu chuyển động ổn, 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 là gì, khi người dùng thay đổi breadcrumb (tập hợp liên kết phân cấp), bạn cần cập nhật URL và cho người dùng thấy 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ó các thao tác điều hướng không mong muốn xảy ra khi người dùng chỉ duyệt xem các tuỳ chọn <select>.

JavaScript cần xử lý 2 biện pháp quan trọng về trải nghiệm người dùng: lựa chọn đã thay đổi và chuẩn bị ngăn chặn kích hoạt sự kiện thay đổi <select>.

Bạn cần ngăn chặn sự kiện sớm 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. Đây là lý do tôi gọi lệnh này là mong chờ, vì người dùng chỉ chọn giả lập tuỳ chọn, như di chuột hoặc tâm điểm, nhưng chưa xác nhận lựa chọn đó bằng enter hoặc click. Sự kiện chuẩn bị khiến tính năng thay đổi danh mục thành phần này không thể truy cập được, vì việc mở hộp chọn và chỉ 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> phù hợp 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 là theo dõi các sự kiện nhấn phím tắt trên từng 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ờ hay chuyển đi khi sự kiện của phần tử <select> kích hoạt.

Kết luận

Giờ bạn đã biết tôi làm được như thế nào, bạn sẽ làm thế nào 🙂

Hãy đa dạng hoá phương pháp tiếp cận của chúng ta và tìm hiểu tất cả các cách xây dựng trên web. Hãy tạo một bản minh hoạ, đường liên kết tweet me và tôi sẽ thêm bản phối lại đó 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