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 cạnh trượt ra thích ứng trên web. Chúng ta sẽ xây dựng thành phần này trong quá trình thực hiện, bắt đầu với HTML, sau đó là CSS, rồi đến JavaScript.

Hãy xem bài đăng trên blog của tôi Tạo 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 để tạo thành phần này.

Thiết lập

  1. Nhấp vào Remix to Edit (Trộn lại để 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 thông tin cơ bản về cách thiết lập HTML để có nội dung và một số hộp để làm việc.

Thả đoạn mã 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>, chứa nội dung trang chính.

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

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à 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>

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

Trong phần tử nội dung chính, hãy thêm một tiêu đề và một bài viết để chứa 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 lề có nút đóng. Chúng ta 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 nội dung Lorem ipsum đượ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à chiều dài của nội dung này 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, với một thanh điều hướng, các đường liên kết và cách đóng thanh điều hướng bên. Bạn cũng đã thêm một tiêu đề, một cách để mở thanh điều hướng bên và một bài viết vào phần tử chính. Mã này đã rõ ràng, có ngữ nghĩa và khá phù hợp với mọi thời đại, nhưng chúng ta có thể làm cho mã này rõ ràng và dễ hiểu hơn 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 thuộc tính titlearia-label vào phần tử đường liên kết mở ở tiêu đề:

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

Biểu tượng SVG đang 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 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 của thanh điều hướng bên:

<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à thanh điều hướng bên là các phần tử con trực tiếp của thẻ <body>, vì vậy, đó là một điểm khởi đầu phù hợp.

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 có nghĩa là: Tạo một hàng được đặt tên stack chứa mọi nội dung và 2 cột trong hàng đó, cột thứ 2 cũng được đặt tên là stack. Cột 1 phải được định cỡ theo nhu cầu nội dung tối thiểu và cột 2 có thể chiếm phần còn lại. Sau đó, nếu trong một khung nhìn bị hạn chế có kích thước 540px trở xuống, hãy đặt các phần tử điều hướng bên và nội dung chính vào cùng một hàng và cột, khiến các phần tử này nằm chồng lê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 thanh điều hướng bên.

Cập nhật phần tử <aside> trở lại 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ẽ đặt bằng thẻ <a>.

Ngoài ra, để dễ dàng nhắm mục tiêu JavaScript hơn, hãy thêm mã nhận dạng cho các phần tử chính kiểm soát thanh điều hướng bên. Trước tiên, hãy thêm mã nhận dạng vào đường liên kết mở của thanh đ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 thanh đ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>

Việc này gói gọn bố cục xếp chồng thích ứng <body> macro, đồng thời liên kết chúng ta vào 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 phần tử con, một <nav> là thành phần trông giống như tờ giấy trượt ra và một phần tử đường liên kết <a> đóng để đặt URL thành #. Đường liên kết không hiển thị ở bên phải của thanh điều hướng trượt ra trên giấy; vì vậy, người dùng có thể "nhấp vào" thành phần hình ảnh để loại bỏ.

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 rất 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 theo điều kiện và duy trì vị trí của mình thông qua mọi thao tác 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 thanh đ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 */
  }
}

Các kiểu đó đảm bảo điều hướng bên là chiều cao khung nhìn, cuộn theo chiều dọc và chứa thao tác cuộn. Quan trọng là lớp này sẽ ẩn phần tử. Theo mặc định, khi khung nhìn có kích thước 540px trở xuống, hãy ẩn thanh điều hướng bên đó. 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. Hãy tiếp tục mở trình đơn bên sau khi cuộn trang hoặc thử cuộn trang trong khi trình đơn bên đ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 các nút mở và đóng, chỉ định kiểu nhấn và chạm, đồng thời ẩn các nút này khi khung nhìn có kích thước từ 540px trở lên.

Để tạo điểm nhấn, hãy thêm các phép biến đổi CSS với khả năng hỗ trợ tiếp cận phù hợp. 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 có thời lượng được áp dụ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ẽ theo dõi một sự kiện chính trên phần tử sidenav. Nếu là Escape, thì giá trị này sẽ đặt hàm băm URL thành trống, khiến thanh điều hướng bên chuyển đổi ra ngoà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 diễn ra dễ dàng, vì vậy, tôi đợi cho đến khi thanh điều hướng bên hoàn tất một số loại chuyển đổi, sau đó kiểm tra chéo với hàm băm URL để xác định xem thanh điều hướng bên có ở trong hay không. Sau đó, tôi sử dụng JavaScript để đặt tiêu điểm vào nút bổ sung cho nút mà người dùng 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. Sau đó, nhấn vào biểu tượng Màn hình toàn cảnh toàn màn hình.

Kết luận

Đó là phần tóm tắt về 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 thành của riêng bạn! Vẫn còn nhiều trường hợp sử dụng khác mà bạn có thể thêm vào để đáp ứng.

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 quan trọng đối với bộ tính năng mà mình đang tập trung vào. Tôi hy vọng rằng việc tách biệt kiểu khỏi bố cục sẽ khuyến khích sao chép và dán. Có thể bạn sẽ có thêm nhiều điều mới!

Làm cách nào để tạo trượt ra các thành phần điều hướng bên thích ứng? Bạn có bao giờ có nhiều hơn 1, chẳng hạn như một bên có và bên kia 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ớ tweet cho tôi hoặc nhận xét trong YouTube kèm theo mã của bạn, điều đó sẽ giúp ích cho mọi người!