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ẻ suy nghĩ về cách tạo thành phần đường dẫn. Dùng thử bản minh hoạ.

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 breadcrumb (tập hợp liên kết phân cấp) hiển thị người dùng ở đâu trong hệ thống phân cấp trang web. Tên này đến từ Hansel và Gretel, người đã bỏ những vụn đường mòn phía sau họ trong một vài khu rừng tối và họ tìm được đường về nhà bằng cách truy vết ngược trở lại.

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. Chúng cung cấp chức năng bổ sung bằng cách đặt các thuộc tính đồng cấp vào ngay phần điều hướng bằng <select>, cho phép truy cập nhiều lớp nhất có thể.

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.

Cấu 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. Từ trang chủ của nguyên mẫu đường dẫn của bài đăng này, các bộ sưu tập là FPS, RPG, brawler, 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 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ặt hàng và bộ sưu tập. 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 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 thuật ngữ khoa học máy tính, thành phần đường dẫn này đại diện cho một mảng nhiều 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 này, tôi sẽ đề cập đến các lựa chọn đánh dấu và cách chúng tác động đế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.

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

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 thông tin đường dẫn không bị lặp lại, khiến các tài liệu lớn hơn và có thể dẫn đến việc đườ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 phần tử <symbol> có 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 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 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 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 người duyệt xem nội dung chỉ nghe được nội dung và đang ẩn khỏi những người dùng đó sẽ ngăn họ thêm nhiễu 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, 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>

Đườ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 nhiều chức năng cho 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 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 ở vị trí trung tâm 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 với phần tử chọn không hiển thị đang được di chuột qua và chú giải công cụ 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 sẽ cung cấp cho mỗi aria-hidden="true" vì chúng là nội dung 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.

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

DevTools hiển thị cách căn chỉnh điều hướng breadcrumb bằng tính năng lớp phủ flexbox.

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 để các phần tử con sử dụng, nếu không sẽ thiết lập một bố cục ngang được căn chỉnh theo chiều dọc. Điều này đảm bảo rằng các dấu vết, đườ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ộ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 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 đường dẫn nhiều từ vì chúng tôi diễn ra trên nhiều dòng. Ở phần sau của bài đăng này, chúng ta sẽ thêm các kiểu để xử lý 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 để giúp liên kết trang hiện tại nổi bật so với nghỉ ngơi. Người dùng trình đọc màn hình không chỉ có chỉ báo rõ ràng cho biết đườ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".

Công cụ cho nhà phát triển của lưới hiển thị phủ lên một nút mà hàng và cột đều có 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> 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 sử dụng được, 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> trong khi 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

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 việc cho phép để mọi thứ bị lệch ra khỏi màn hình theo chiều ngang, khi thích hợp và tôi cảm thấy điều này breadcrumb (tập hợp liên kết phân cấp) đủ đ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 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à trên trang, hãy tải tải vụn được chụp nhanh và trong tầm nhìn.
  • 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 đa phương tiệ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;
  }
}

So sánh các chuỗi liên kết có và không có nhãn trang chủ.

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 phần tử hiệu ứng chuyển đổi trong quá 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 thật tuyệt vời và có ý nghĩa nếu không có transition, nhưng nếu chuyển động được chấp nhận, chúng ta sẽ thêm sang tương tác.

JavaScript

Trước tiên, bất kể bạn 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, URL cần được cập nhật và người dùng hiển thị 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>.

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

Bạn cần ngăn chặn sự kiện háo hức do sử dụng phần tử <select>. Trên Windows Edge và có thể cũng như các trình duyệt khác, hãy chọn changed sự kiện 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à háo hức, vì người dùng chỉ giả chọn một tuỳ chọn, chẳng hạ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 hay click. Người háo hức sự kiệ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, bởi vì mở hộp chọn và chỉ cần duyệt qua mục sẽ kích hoạt sự kiện và hãy 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 đượ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á các phương pháp và tìm hiểu tất cả các cách xây dựng trên web. Hãy tạo bản minh hoạ, gửi đường liên kết cho tôi trên Twitter và tôi sẽ thêm bản minh hoạ đó vào phầ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