Lớp học lập trình: Xây dựng thành phần Sidenav

Lớp học lập trình này hướng dẫn bạn cách tạo một thành phần bố cục điều hướng bên dạng trượt ra thích ứng trên web. Chúng ta sẽ tạo thành phần này trong quá trình thực hiện, bắt đầu bằng HTML, sau đó là CSS, rồi đến JavaScript.

Hãy xem bài đăng trên blog của tôi Building a Sidenav component (Xây dựng thành phần Sidenav) để tìm hiểu về các tính năng của nền tảng web CSS được chọn để xây dựng thành phần này.

Thiết lập

  1. Nhấp vào Trộn để chỉnh sửa để có thể chỉnh sửa dự án.
  2. Mở app/index.html.

HTML

Trước tiên, hãy tìm hiểu những điều cơ bản về chế độ thiết lập HTML để có nội dung và một số hộp để thao tác.

Thả HTML sau vào thẻ <body>.

<aside></aside>
<main></main>

<aside> chứa trình đơn điều hướng dưới dạng một phần tử bổ sung cho <main>, trong đó chứa nội dung chính của trang.

Tiếp theo, chúng ta sẽ điền những phần tử ngữ nghĩa đó bằng nội dung còn lại của trang.

Thêm một phần tử điều hướng, một số đường liên kết điều hướng và một đường liên kết đóng bên trong phần tử <aside>.

<aside>
  <nav>
    <h4>My</h4>
    <a href="#">Dashboard</a>
    <a href="#">Profile</a>
    <a href="#">Preferences</a>
    <a href="#">Archive</a>

    <h4>Settings</h4>
    <a href="#">Accessibility</a>
    <a href="#">Theme</a>
    <a href="#">Admin</a>
  </nav>

  <a href="#"></a>
</aside>

Các đường liên kết rất phù hợp bên trong các phần tử <nav> và các phần tử <nav> rất phù hợp trong thanh bên <aside>. Tuy nhiên, chúng tôi vẫn có thể cải thiện thêm.

Trong phần tử nội dung chính, hãy thêm một tiêu đề và một bài viết để giữ nội dung bố cục theo ngữ nghĩa.

<main>
  <header>
    <a href="#sidenav-open" class="hamburger">
      <svg viewBox="0 0 50 40">
        <line x1="0" x2="100%" y1="10%" y2="10%" />
        <line x1="0" x2="100%" y1="50%" y2="50%" />
        <line x1="0" x2="100%" y1="90%" y2="90%" />
      </svg>
    </a>
    <h1>Site Title</h1>
  </header>

  <article>
    {put some placeholder content here}
  </article>
</main>

Tiêu đề có đường liên kết mở trình đơn. Phần bên có nút đóng. Chúng tôi sẽ sớm hiện và ẩn các phần tử dựa trên kích thước khung hiển thị.

Trong phần tử <article>, chúng ta đã dán một câu giữ chỗ. Thay thế "" bằng nội dung của riêng bạn hoặc dán văn bản lorem được cung cấp bên dưới:

<h2>Totam Header</h2>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Cum consectetur, necessitatibus velit officia ut impedit veritatis temporibus soluta? Totam odit cupiditate facilis nisi sunt hic necessitatibus voluptatem nihil doloribus! Enim.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead Totam Odit</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

<h3>Subhead</h3>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>
<p>Lorem ipsum dolor sit, amet consectetur adipisicing elit. Fugit rerum, amet odio explicabo voluptas eos cum libero, ex esse quasi optio incidunt soluta eligendi labore error corrupti! Dolore, cupiditate porro.</p>

Nội dung này và độ dài của nội dung sẽ khiến trang có thể cuộn khi vượt quá chiều cao khung nhìn.

Đến đây, bạn đã thêm một phần tử aside, có nav, các đường liên kết và cách đóng sidenav. Bạn cũng đã thêm một tiêu đề, một cách để mở sidenav và một bài viết vào phần tử chính. Mã này đã rõ ràng, ngữ nghĩa và khá vượt thời gian, nhưng chúng ta có thể làm cho mã này rõ ràng hơn nữa cho mọi người. Đường liên kết mở trong thanh điều hướng bên có thể được đánh dấu rõ ràng hơn.

Thêm các thuộc tính titlearia-label vào phần tử đường liên kết mở đầu trang:

<a href="#sidenav-open" class="hamburger">
<a href="#sidenav-open" title="Open Menu" aria-label="Open Menu" class="hamburger">

Biểu tượng SVG mở cũng có thể được đánh dấu rõ ràng hơn. Thêm các thuộc tính sau vào SVG bên trong phần tử đường liên kết mở:

<svg viewBox="0 0 50 40">
<svg viewBox="0 0 50 40" role="presentation" focusable="false" aria-label="trigram for heaven symbol">

Đường liên kết đóng trong trình đơn bên có thể được đánh dấu rõ ràng hơn. Thêm các thuộc tính titlearia-label vào phần tử đường liên kết đóng sidenav:

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

CSS

Đã đến lúc bố trí các phần tử. Nội dung chính và sidenav là các phần tử con trực tiếp của thẻ <body>, vì vậy, đây là một nơi phù hợp để bắt đầu.

Thêm CSS sau vào css/sidenav.css để phần tử <body> bố trí các phần tử con.

body {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;

  @media (max-width: 540px) {
    & > :matches(aside, main) {
      grid-area: stack;
    }
  }
}

Về cơ bản, bố cục này cho biết: Tạo một hàng có tên là stack với mọi thứ trong đó và 2 cột trong hàng đó, cột thứ 2 cũng có tên là stack. Cột thứ nhất phải được điều chỉnh kích thước theo nhu cầu tối thiểu về nội dung và cột thứ hai có thể chiếm phần còn lại. Sau đó, nếu trong khung hiển thị bị hạn chế là 540px trở xuống, hãy đặt các phần tử nội dung chính và sidenav vào cùng một hàng và cột, dẫn đến việc chúng nằm trên nhau trong lưới 1x1.

Với chức năng xếp chồng thích ứng này làm cơ sở, giờ đây, chúng ta có thể tận dụng trạng thái của thanh URL để bật/tắt chế độ hiển thị và kiểu chuyển đổi của sidenav.

Cập nhật phần tử <aside> trong app/index.html:

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

Điều này cho phép CSS so khớp một phần tử và hàm băm URL với nhau. Điều này rất quan trọng đối với việc sử dụng :target. Giờ đây, mã nhận dạng của phần tử có thể khớp với hàm băm URL mà chúng ta sẽ thiết lập bằng thẻ <a>.

Ngoài ra, để nhắm mục tiêu JavaScript dễ dàng hơn, hãy thêm các mã nhận dạng cho các phần tử chính kiểm soát sidenav. Trước tiên, hãy thêm một mã nhận dạng vào đường liên kết mở sidenav:

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

Tiếp theo, hãy thêm một mã nhận dạng vào đường liên kết đóng sidenav:

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

Đó là bố cục xếp chồng thích ứng <body> của macro, đồng thời liên kết chúng ta với thanh URL. Hãy cùng tiếp tục!

<aside> cũng có bố cục gọn gàng. Thẻ này có 2 thành phần con, một <nav> là thành phần có giao diện giống như giấy và trượt ra, còn một phần tử liên kết đóng <a> đặt url thành #. Đường liên kết này không xuất hiện ở bên phải của trình đơn điều hướng dạng trượt của giấy; mục đích là để người dùng có thể "nhấp ra ngoài" thành phần trực quan để đóng thành phần đó.

Thêm CSS sau vào css/sidenav.css:

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

Tôi nghĩ tỷ lệ và tên là một điểm nhấn thực sự thú vị ở đây, nơi lưới có thể tỏa sáng và mang lại cho nhà thiết kế nhiều quyền kiểm soát.

Tiếp theo, tôi cần phủ nội dung chính một cách có điều kiện và duy trì vị trí của mình trong quá trình cuộn tài liệu. Đây là một công việc tuyệt vời cho position: sticky và một số overscroll-behavior.

Thêm các kiểu sau cho sidenav:

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

  @media (max-width: 540px) {
    position: sticky;
    top: 0;
    max-height: 100vh;
    overflow: hidden auto;
    overscroll-behavior: contain;

    visibility: hidden; /* not keyboard accessible when closed */
  }
}

Những kiểu này đảm bảo sidenav có chiều cao khung nhìn, cuộn theo chiều dọc và chứa nội dung cuộn. Điều rất quan trọng là nó sẽ ẩn phần tử. Theo mặc định, khi khung hiển thị là 540px trở xuống, hãy ẩn sidenav đó. Trừ phi!

Thêm bộ chọn giả :target vào phần tử #sidenav-open:

#sidenav-open {

  @media (max-width: 540px) {

    &:target {
      visibility: visible;
    }
  }
}

Khi mã nhận dạng của phần tử đó và thanh URL giống nhau, hãy đặt visibility thành visible. Tiếp tục mở trình đơn bên sau khi di chuyển trang hoặc thử di chuyển trang trong khi sidenav đang mở. Bạn nghĩ gì về thông tin này?

Thêm CSS sau vào cuối app/sidenav.css:

#sidenav-button,
#sidenav-close {
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  user-select: none;
  touch-action: manipulation;

  @media (min-width: 540px) {
    display: none;
  }
}

Các kiểu này nhắm đến nút mở và đóng của chúng ta, chỉ định kiểu chạm và kiểu cảm ứng của các nút này, đồng thời ẩn các nút này khi khung hiển thị có kích thước 540px trở lên.

Để thêm một chút điểm nhấn, hãy thêm các biến đổi CSS có tính hỗ trợ tiếp cận. Thêm CSS sau vào css/sidenav.css:

#sidenav-open {
  --easeOutExpo: cubic-bezier(0.16, 1, 0.3, 1);
  --duration: .6s;

  ...

  @media (max-width: 540px) {
    ...

    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);

    &:target {
      visibility: visible;
      transform: translateX(0);
      transition: transform var(--duration) var(--easeOutExpo);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    --duration: 1ms;
  }
}
Bản minh hoạ về hoạt động tương tác có và không áp dụng thời lượng dựa trên truy vấn nội dung nghe nhìn `prefers-reduced-motion`.

Thêm một chút JavaScript

Phím Escape sẽ đóng trình đơn. Thêm JS này vào js/index.js:

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

sidenav.addEventListener('keyup', e => {
  if (e.code === 'Escape') {
    document.location.hash = '';
  }
});

Thao tác này sẽ lắng nghe một sự kiện chính trên phần tử sidenav. Nếu là Escape, thì hàm này sẽ đặt giá trị băm URL thành trống, khiến sidenav chuyển đổi.

Phần tiếp theo của UX JS là quản lý tiêu điểm. Tôi muốn việc mở và đóng trở nên dễ dàng, vì vậy tôi đợi cho đến khi sidenav hoàn tất một loại chuyển đổi nào đó, sau đó kiểm tra chéo với hàm băm URL để xác định xem nó có ở trong hay không. Sau đó, tôi dùng JavaScript để đặt tiêu điểm vào nút bổ sung cho nút mà họ vừa nhấn.

Thêm JavaScript sau vào js/index.js:

const closenav = document.querySelector('#sidenav-close');
const opennav = document.querySelector('#sidenav-button');

sidenav.addEventListener('transitionend', e => {
  if (e.propertyName !== 'transform') {
    return;
  }

  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
    ? closenav.focus()
    : opennav.focus();
});

Dùng thử

  • Để xem trước trang web, hãy nhấn vào Xem ứng dụng, rồi nhấn vào Toàn màn hình toàn màn hình.

Kết luận

Đó là phần kết thúc cho những nhu cầu của tôi đối với thành phần này. Bạn có thể thoải mái xây dựng dựa trên đó, điều khiển bằng trạng thái JavaScript thay vì URL và nói chung là biến nó thành của riêng bạn! Luôn có nhiều điều cần thêm hoặc nhiều trường hợp sử dụng cần đề cập.

Mở css/brandnav.css để xem các kiểu không liên quan đến bố cục mà tôi đã áp dụng cho thành phần này. Tôi không cảm thấy điều này quan trọng đối với bộ tính năng mà tôi đang tập trung vào và tôi hy vọng rằng việc tách các kiểu khỏi bố cục sẽ khuyến khích việc sao chép và dán. Bạn có thể học hỏi thêm nhiều điều tại đó!

Làm cách nào để tạo các thành phần sidenav thích ứng có thể trượt ra? Bạn có bao giờ có nhiều hơn 1, chẳng hạn như một ở cả hai bên không? Tôi rất muốn giới thiệu giải pháp của bạn trong một video trên YouTube. Hãy nhớ gửi tweet cho tôi hoặc bình luận trên YouTube kèm theo mã của bạn. Điều này sẽ giúp ích cho mọi người!