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

Tổng quan cơ bản về cách tạo thành phần đường dẫn thích ứng và dễ truy cập để người dùng điều hướng 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). Xem bản minh hoạ.

Bản minh hoạ

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

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

Đườ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. Các lớp này cung cấp thêm chức năng bằng cách đặt các trang đồng cấp ngay vào phần điều hướng bằng <select>, giúp bạn có thể 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 điều hướng theo đường dẫn sau: home » rpg » indie » on sale, như minh hoạ dưới đây.

Thành phần breadcrumb (tập hợp liên kết phân cấp) 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.

Cấu trúc thông tin

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

Bộ sưu tập

Tập hợ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 (tập hợp liên kết phân cấp) của bài đăng này, các bộ sưu tập gồm có trò chơi FPS (khung hình/giây), trò chơi nhập vai (RPG), trò chơi chiến đấu, trình thu thập thông tin trong hầm ngục, 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 trò chơi đó đạ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 nội dung là một mục, người dùng sẽ ở trên trang bộ sưu tập đó. Ví dụ: các trò chơi đó nằm trên trang RPG (Trò chơi nhập vai), hiển thị danh sách trò chơi nhập vai, bao gồm các danh mục con 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ó kiến 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 về trang đích thu thập và truyền tải qua hệ thống phân cấp cũng có thể đưa dữ liệu này 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 của mình và tác động của chúng đối với 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 biết rằng trang này muốn sử dụng 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à do đó, đường dẫn sẽ sử dụng các màu mặc định do trình duyệt cung cấp.

<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 do 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ử, nên thuộc tính này thực sự đã được thông báo là thành phần điều hướng nên tôi đã chọn thêm thuộc tính này.

Biểu tượng

Khi một biểu tượng 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 phần tử 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 giống nhau không bị lặp lại, khiến các tài liệu lớn hơn và có khả năng khiến đườ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 ẩ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 của SVG, đưa thông tin 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ã để 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 cho thấy phần tử sử dụng SVG được kết xuất.

Xác định một lần, sử dụng bao nhiêu lần tuỳ thích mà không gây ảnh hưởng lớn đến hiệu suất trang cũng như 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 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.

Đây là nơi breadcrumb (tập hợp liên kết phân cấp) truyền thống và breadcrumb (tập hợp liên kết phân cấp) 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 bố trí đường liên kết và biểu tượng, còn .crumbicon chịu trách nhiệm xếp chồng 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ì các 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 vào 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ẽ giúp í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 giúp ích cho những người khác nên bạn sẽ thấy tính năng này xuất hiện ở phía trước và ở giữa trên iPad. Một thuộc tính cung cấp bối 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 bị ẩn đang được di chuột và chú thích theo ngữ cảnh của phần tử đó đang hiển thị.

Trang trí dòng phân cách

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

Dòng phân cách là không bắt buộc, bạn 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

Công cụ cho nhà phát triển cho thấy cách căn chỉnh đường dẫn điều hướng với một tính năng lớp phủ hộp linh hoạt của công 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 sẽ đảm bảo vụn bánh, đường chia và biểu tượng căn chỉnh.

.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 căn chỉnh theo chiều ngang với một số khoảng trống, nhưng đặc biệt nhắm mục tiêu các thành phần con của đường liên kết và chỉ định kiểu white-space: nowrap. Điều này rất quan trọng đối với đường dẫn nhiều từ vì chúng ta không muốn chúng phân phát 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 đến trang hiện tại nổi bật so với những đườ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 là cho trang hiện tại, mà chúng tôi còn định kiểu hình ảnh cho thành phần này để giúp người dùng thị giác 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ụ lưới cho nhà phát triển hiển thị lớp phủ một nút mà trong đó cả hàng và cột đều là ngăn xếp được đặt tên.

.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 nằm trên đầu ngăn xếp và có tính tương tác. Thêm kiểu opacity: .01 để phần tử vẫn sử dụng được và kết quả là một hộp chọn hoàn toà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> mà vẫn duy trì chức năng tích hợp sẵn.

.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

Breadcrumb phải có khả năng biểu thị một đường mòn rất dài. Tôi thích cho phép mọi thứ thoát 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.

.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 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 điểm nhanh trên vụn cuối cùng. Điều này có nghĩa là khi tải trang, khối đầu tiên tải được chụp nhanh và trong chế độ xem.
  • Xoá điểm chụp nhanh khỏi Safari vì điểm này gặp khó khăn khi 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 sự điều chỉnh tinh tế cho các khung nhìn nhỏ hơn là ẩn nhãn "Home" (Trang chủ), chỉ để lại biểu tượng:

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

Đặt cạnh nhau các đường dẫn có và không có nhãn ban đầu để so sánh.

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 hiệu ứng chuyển đổi trong 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ó kiểu nào khác cần thay đổi, hiệu ứng di chuột và lấy nét thật tuyệt vời và có ý nghĩa mà không cần 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ể bạn sử dụng loại bộ định tuyến nào 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à người dùng hiển thị trang thích hợp. Thứ hai, để bình thường hoá trải nghiệm người dùng, hãy đảm bảo không có thao tác điều hướng ngoài dự kiến nào xảy ra khi người dùng chỉ đang 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ó thể 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.

Cần có tính năng ngăn chặn sự kiện mong muốn do sử dụng phần tử <select>. Trên Windows Edge và có thể cũng là các trình duyệt khác, sự kiện changed được chọn sẽ kích hoạt khi người dùng duyệt qua các lựa chọn bằng bàn phím. Đây là lý do tôi gọi là mong muốn, vì người dùng chỉ giả chọn tuỳ chọn, như di chuột hoặc 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 mong muốn 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.

Một 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 đã nhấn là thao tác xác nhận điều hướng (Tab hoặc Enter) hay điều hướng theo không gian (ArrowUp hoặc ArrowDown). Với xác định này, thành phần có thể quyết định đợi hay tiếp tục khi sự kiện cho phần tử <select> kích hoạt.

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‽ 🙂

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