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 thành phần bố cục điều hướng bên trượt ra có tính thích ứng trên web. Chúng ta sẽ tạo thành phần trong quá trình tạo, bắt đầu bằng HTML, sau đó đến CSS, sau đó là JavaScript.

Hãy xem bài đăng Xây dựng thành phần Sidenav trên blog của tôi để tìm hiểu về các tính năng trên 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 Remix để chỉnh sửa (Remix) để chỉnh sửa dự án.
  2. Mở app/index.html.

HTML

Trước tiên, bạn cần nắm được các yếu tố cần thiết để thiết lập HTML sao cho 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> giữ trình đơn điều hướng dưới dạng một phần tử bổ sung cho <main>, nơi chứa nội dung chính của trang.

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

Thêm một thành phần điều hướng, một số đường liên kết điều hướng và đườ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>

Đường liên kết hoạt động hiệu quả bên trong phần tử <nav>, còn phần tử <nav> hoạt động hiệu quả trong thanh bên <aside>. Tuy nhiên, vẫn còn nhiều điều chúng tôi có thể làm để cải thiện.

Trong phần tử nội dung chính, hãy thêm tiêu đề và bài viết để lưu giữ nội dung bố cục về mặt 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 đang mở trình đơn. Mặt ngoài có nút đóng. Chúng tôi sẽ sớm hiển thị và ẩn các phần tử dựa trên kích thước khung nhìn.

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 lorem được cung cấp dưới đây:

<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, là những yếu tố sẽ khiến trang có thể cuộn khi vượt quá chiều cao khung nhìn của bạn.

Cho đến nay, bạn đã thêm một phần tử riêng biệt, có thành phần điều hướng, đường liên kết và cách đóng phần điều hướng bên. Bạn cũng đã thêm tiêu đề, cách mở thanh điều hướng bên và một bài viết vào phần tử chính. Tính năng này đã gọn gàng, có ngữ nghĩa và khá lỗi thời, nhưng chúng tôi có thể làm cho nó rõ ràng và rõ ràng hơn cho mọi người. Đường liên kết đang 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ở của tiêu đề:

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

Bạn cũng có thể đánh dấu biểu tượng SVG đang mở 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 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 đóng điều hướng bên:

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

CSS

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

Thêm CSS sau đây 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;
    }
  }
}

Bố cục này về cơ bản như sau: Tạo một hàng được đặt tên là stack với mọi nội dung trong hàng đó và 2 cột trong hàng đó, cột thứ hai cũng được đặt tên là stack. Cột đầu tiên phải được định kích thước theo nhu cầu nội dung tối thiểu và cột thứ 2 có thể chiếm phần còn lại. Sau đó, nếu trong một khung nhìn bị hạn chế từ 540px trở xuống, hãy đặt thành phần điều hướng bên và các thành phần nội dung chính vào cùng một hàng và cột, dẫn đến việc các thành phần này nằm chồng lên nhau trong một 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 thanh điều hướng bên.

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

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

Điều này cho phép CSS 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. Bây giờ, 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 mã nhận dạng cho các phần tử chính kiểm soát tính năng điều hướng bên. Trước tiên, hãy thêm mã vào đường liên kết mở ngăn điều hướng bên:

<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ã nhận dạng vào đường liên kết đóng ngăn điều hướng bên:

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

Thao tác này kết thúc 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. Lớp này có 2 thành phần con, một <nav> là thành phần trông giống như giấy trượt ra và một thành phần liên kết <a> đóng giúp đặt URL thành #. Đường liên kết không xuất hiện ở bên phải của thanh điều hướng trượt ra để mọi người có thể "nhấp vào tắt" thành phần hình ảnh để đóng.

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

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

Tôi nghĩ rằng tỷ lệ và tên là một điểm nhấn thực sự tốt ở đây, nơi lưới có thể toả 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ủ lên nội dung chính theo điều kiện và duy trì vị trí của mình thông qua bất kỳ thao tác cuộn tài liệu nào. Đâ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 ngăn điều hướng bên:

#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 đó đảm bảo phần điều hướng bên là chiều cao khung nhìn, cuộn dọc và chứa thanh cuộn. Điều quan trọng là nó ẩn phần tử. Theo mặc định, khi khung nhìn là 540px trở xuống, hãy ẩn điều hướng bên đó. Trừ khi!

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. Hãy tiếp tục và mở trình đơn bên sau khi cuộn trang hoặc thử cuộn trang trong khi thanh điều hướng bên đang mở. Bạn cảm nhận như thế nào?

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 mục tiêu đến các nút mở và đóng, chỉ định kiểu nhấn và chạm của chúng, đồng thời ẩn chúng khi khung nhìn là 540px trở lên.

Để tinh tế, hãy thêm phép biến đổi CSS với khả năng hỗ trợ tiếp cận tôn trọng. 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ạ 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 = '';
  }
});

Quá trình này sẽ theo dõi một sự kiện chính trong phần tử điều hướng bên. Nếu là Escape, hàm này sẽ thiết lập hàm băm URL thành trống, làm cho thành phần điều hướng bên sẽ chuyển đổi.

Phần tiếp theo của JS trong trải nghiệm người dùng là quản lý tiêu điểm. Tôi muốn đơn giản hoá thao tác mở và đóng, vì vậy, tôi sẽ đợi cho đến khi ngăn điều hướng bên kết thúc quá trình chuyển đổi kiểu nào đó, sau đó kiểm tra chéo với hàm băm URL để xác định xem nó vào hay ra. Sau đó, tôi sử dụng JavaScript để đặt tiêu điểm vào nút bổ trợ cho nút 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 View App (Xem ứng dụng), sau đó nhấn vào Fullscreen toàn màn hình (Toàn màn hình).

Kết luận

Đó là thông tin tóm tắt về những nhu cầu tôi có đối với thành phần này. Hãy thoải mái xây dựng dựa trên nó, điều khiển nó với trạng thái JavaScript thay vì URL và nói chung hãy đặt nó theo cách của bạn! Luôn có nhiều trường hợp cần bổ sung hoặc nhiều trường hợp sử dụng hơn 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 cảm thấy không quan trọng đối với bộ tính năng mà tôi đang tập trung vào. Tôi hy vọ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. Có thể có nhiều bài học hơn cho bạn ở đó!

Làm cách nào để trượt các thành phần điều hướng bên thích ứng? Có bao giờ bạn có nhiều hơn 1 thiết bị ở cả hai bên không? Tôi muốn giới thiệu giải pháp của bạn trong video trên YouTube, hãy nhớ tweet tôi hoặc bình luận trên YouTube bằng mã của bạn, điều đó sẽ giúp ích cho tất cả mọi người!