Xây dựng thành phần điều hướng bên

Tổng quan cơ bản về cách tạo thanh điều hướng bên ngoài dạng trang trình bày thích ứng

Trong bài đăng này, tôi muốn chia sẻ với bạn cách tôi tạo nguyên mẫu thành phần Sidenav cho web phản hồi nhanh, có trạng thái, hỗ trợ điều hướng bằng bàn phím, hoạt động với và không có JavaScript, và hoạt động trên nhiều trình duyệt. Xem 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ật khó khăn khi xây dựng một hệ thống điều hướng thích ứng. Một số người dùng sẽ sử dụng bàn phím một số người sẽ có máy tính để bàn mạnh mẽ và một số người sẽ truy cập từ thiết bị di động nhỏ. Tất cả những người truy cập đều có thể mở và đóng trình đơn.

Bản minh hoạ bố cục thích ứng từ máy tính đến thiết bị di động
Giao diện sáng và tối trên iOS và Android

Chiến thuật web

Trong phần khám phá thành phần này, tôi rất vui khi được kết hợp một số tính năng quan trọng của nền tảng web:

  1. Dịch vụ so sánh giá (CSS) :target
  2. Lưới CSS
  3. Biến đổi CSS
  4. Truy vấn phương tiện CSS cho khung nhìn và lựa chọn ưu tiên của người dùng
  5. JS cho focus các tính năng nâng cao về trải nghiệm người dùng

Giải pháp của tôi có một thanh bên và chỉ bật/tắt khi ở trên "thiết bị di động" khung nhìn từ 540px trở xuống. 540px sẽ là điểm ngắt để chuyển đổi giữa bố cục tương tác trên thiết bị di động và bố cục tĩnh cho máy tính để bàn.

Lớp giả :target của CSS

Một đường liên kết <a> đặt hàm băm URL thành #sidenav-open và đường liên kết còn lại thành giá trị trống (''). Cuối cùng, một phần tử có id để khớp với hàm băm:

<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<aside id="sidenav-open">
  …
</aside>

Việc nhấp vào mỗi liên kết này sẽ làm thay đổi trạng thái băm của URL trang của chúng tôi, sau đó, với một lớp giả, tôi sẽ hiển thị và ẩn điều hướng bên:

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
  }

  #sidenav-open:target {
    visibility: visible;
  }
}

Lưới CSS

Trước đây, tôi chỉ sử dụng vị trí tuyệt đối hoặc cố định bố cục và thành phần điều hướng bên. Tuy nhiên, Grid với cú pháp grid-area, cho phép chúng ta chỉ định nhiều phần tử cho cùng một hàng hoặc cột.

Ngăn xếp

Phần tử bố cục chính #sidenav-container là một lưới tạo 1 hàng và 2 cột, 1 trong số đó có tên là stack. Khi không gian bị hạn chế, CSS sẽ chỉ định tất cả thuộc tính của phần tử <main> phần tử con vào cùng một tên lưới, đặt tất cả các phần tử vào cùng một không gian, tạo ra một ngăn xếp.

#sidenav-container {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;
  min-height: 100vh;
}

@media (max-width: 540px) {
  #sidenav-container > * {
    grid-area: stack;
  }
}

<aside> là phần tử ảnh động chứa thanh điều hướng bên. Có 2 thành phần con: vùng chứa điều hướng <nav> có tên là [nav] và một phông nền <a> có tên [escape], dùng để đóng trình đơn.

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

Điều chỉnh 2fr1fr để tìm tỷ lệ mà bạn muốn cho lớp phủ trình đơn và nút đóng không gian âm của lớp phủ đó.

Bản minh hoạ những gì sẽ xảy ra khi bạn thay đổi tỷ lệ.

Biến đổi CSS 3D và hiệu ứng chuyển cảnh

Bố cục của chúng ta hiện được xếp chồng theo kích thước khung nhìn trên thiết bị di động. Cho đến khi tôi thêm một số kiểu mới, nó sẽ chồng lên bài viết của chúng tôi theo mặc định. Dưới đây là một số trải nghiệm người dùng mà tôi sẽ nhắm đến trong phần tiếp theo này:

  • Tạo ảnh động cho thao tác mở và đóng
  • Chỉ tạo ảnh động bằng chuyển động nếu người dùng đồng ý với điều đó
  • Tạo ảnh động cho visibility để tiêu điểm bằng bàn phím không nhập vào phần tử ngoài màn hình

Khi bắt đầu triển khai hoạt ảnh chuyển động, tôi muốn ưu tiên khả năng hỗ trợ tiếp cận.

Xe có hỗ trợ tiếp cận

Không phải ai cũng muốn trải nghiệm chuyển động trượt ra. Trong giải pháp của chúng tôi, tuỳ chọn này được áp dụng bằng cách điều chỉnh biến CSS --duration bên trong truy vấn nội dung đa phương tiện. Giá trị truy vấn phương tiện này đại diện cho lựa chọn ưu tiên của người dùng về hệ điều hành đối với chuyển động (nếu có).

#sidenav-open {
  --duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
Bản minh hoạ hoạt động tương tác có áp dụng và không áp dụng thời lượng.

Khi thanh điều hướng bên ở dạng trượt mở và đóng, nếu người dùng muốn giảm chuyển động, Tôi ngay lập tức di chuyển phần tử vào khung hiển thị, duy trì trạng thái không chuyển động.

Chuyển đổi, biến đổi, dịch

Điều hướng bên ngoài (mặc định)

Để đặt trạng thái mặc định của điều hướng bên trên thiết bị di động thành trạng thái ngoài màn hình, Tôi định vị phần tử bằng transform: translateX(-110vw).

Lưu ý, tôi đã thêm một 10vw khác vào mã ngoài màn hình thông thường của -100vw, để đảm bảo box-shadow của ngăn điều hướng bên không xuất hiện trong khung nhìn chính khi bị ẩn.

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);
  }
}
Điều hướng bên trong

Khi phần tử #sidenav khớp dưới dạng :target, hãy đặt vị trí translateX() thành homebase 0, và xem khi CSS trượt phần tử từ vị trí ra khỏi -110vw sang "in" của 0 trên var(--duration) khi hàm băm URL thay đổi.

@media (max-width: 540px) {
  #sidenav-open:target {
    visibility: visible;
    transform: translateX(0);
    transition:
      transform var(--duration) var(--easeOutExpo);
  }
}

Chế độ hiển thị chuyển đổi

Mục tiêu hiện tại là ẩn trình đơn khỏi trình đọc màn hình khi trình đơn này ra mắt, để hệ thống không đặt tiêu điểm vào trình đơn ngoài màn hình. Tôi thực hiện việc này bằng cách thiết lập chuyển đổi chế độ hiển thị khi :target thay đổi.

  • Khi đi vào, đừng chuyển đổi chế độ hiển thị; hiển thị ngay lập tức để tôi có thể thấy phần tử trượt vào và chấp nhận tiêu điểm.
  • Khi chuyển ra ngoài, chế độ hiển thị hiệu ứng chuyển đổi sẽ hiển thị nhưng trì hoãn để chuyển sang hidden ở cuối quá trình chuyển đổi.

Các tính năng nâng cao cho trải nghiệm người dùng hỗ trợ tiếp cận

Giải pháp này dựa trên việc thay đổi URL để quản lý trạng thái. Đương nhiên là bạn nên sử dụng phần tử <a> ở đây và phần tử này có khả năng hỗ trợ tiếp cận tốt các tính năng mới miễn phí. Hãy tô điểm cho các thành phần tương tác của chúng ta bằng các nhãn thể hiện rõ ý định.

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
  <svg>...</svg>
</a>
Bản minh hoạ trải nghiệm người dùng tương tác qua bàn phím và lồng tiếng.

Giờ đây, các nút tương tác chính đã nêu rõ ý định của chúng đối với cả chuột và bàn phím.

:is(:hover, :focus)

Công cụ chọn giả chức năng hữu ích này của CSS giúp chúng ta nhanh chóng hoà nhập với kiểu di chuột của chúng tôi bằng cách chia sẻ chúng với tiêu điểm.

.hamburger:is(:hover, :focus) svg > line {
  stroke: hsl(var(--brandHSL));
}

Thêm JavaScript

Nhấn escape để đóng

Phím Escape trên bàn phím sẽ đóng trình đơn phải không? Hãy kết nối vào đó.

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', event => {
  if (event.code === 'Escape') document.location.hash = '';
});
Nhật ký duyệt web

Để tránh trường hợp tương tác mở và đóng xếp chồng nhiều vào nhật ký duyệt web, thêm JavaScript sau cùng dòng vào nút đóng:

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>

Thao tác này sẽ xoá mục nhập nhật ký URL khi đóng, khiến nó trông như thể trình đơn chưa bao giờ mở.

Tập trung vào trải nghiệm người dùng

Đoạn mã tiếp theo giúp chúng ta tập trung vào các nút mở và đóng sau chúng mở hoặc đóng. Tôi muốn bật/tắt một cách dễ dàng.

sidenav.addEventListener('transitionend', e => {
  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
      ? document.querySelector('#sidenav-close').focus()
      : document.querySelector('#sidenav-button').focus();
})

Khi bảng điều hướng bên mở ra, hãy lấy tiêu điểm vào nút đóng. Khi điều hướng bên đóng, lấy tiêu điểm vào nút mở. Tôi thực hiện việc này bằng cách gọi focus() trên phần tử trong JavaScript.

Kết luận

Giờ bạn đã biết cách tôi thực hiện điều đó, bạn sẽ làm thế nào?! Điều này tạo ra một số cấu trúc thành phần thú vị! Ai sẽ thiết kế phiên bản đầu tiên có khung giờ nhận đặt quảng cáo? 🙂

Hãy đa dạng hoá và tìm hiểu tất cả các cách xây dựng trên web. Tạo nhiễu, tweet cho tôi phiên bản của bạn rồi tôi sẽ thêm nó 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