程式碼研究室:建構 Sidenav 元件

本程式碼研究室將說明如何在網路上建構回應式滑出側邊導覽版面配置元件。建立元件的過程中,我們將先從 HTML、CSS 和 JavaScript 開始建構。

請參閱我的網誌文章「建構側邊導覽元件」,瞭解為了建構這個元件而選擇的 CSS 網路平台功能。

設定

  1. 按一下「Remix to Edit」,讓專案可供編輯。
  2. 開啟 app/index.html

HTML

首先,請瞭解 HTML 設定的基本要件,這樣就可以建立網頁內容和一些可用方塊。

將下列 HTML 拖曳至 <body> 標記中。

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

<aside> 會將導覽選單做為 <main> 的免付費元素保留,其中保存主要頁面內容。

接下來,我們將這些語意元素填入網頁內容的其他內容。

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

連結非常適合在 <nav> 元素內,<nav> 元素則適合位於 <aside> 側欄中。然而,還有更多改進空間。

在主要內容元素中新增標頭和文章,以語意方式保存版面配置內容。

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

標頭會顯示選單開啟連結。一側有關閉按鈕。 我們很快就會根據可視區域大小顯示及隱藏元素。

我們在 <article> 元素中貼上了一個預留位置句子。請將 `` 換成自己的內容,或貼上下方的 lorem:

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

網頁超過可視區域高度時,會造成可捲動的原因,

您目前已新增一個側邊元素,包括導覽、連結和關閉側邊導覽列。此外,您還可以為主要元素新增標頭、側邊導覽列開啟方式和一篇文章。 這是乾淨、語意且過去久的情況 但我們可以為所有人提供更簡潔清晰的介面側邊導覽列中的開啟連結會更清晰

在標頭開啟連結元素中新增 titlearia-label 屬性:

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

開啟的 SVG 圖示也可能更加清楚。將下列屬性新增至開啟連結元素中的 SVG:

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

側邊導覽列中的關閉連結可能會較為清楚。在側邊導覽關閉連結元素中新增 titlearia-label 屬性:

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

CSS

該安排元素版面配置的時間。主要內容和側邊導覽列是 <body> 標記的直接子項,因此很適合開始使用。

將下列 CSS 新增至 css/sidenav.css,讓 <body> 元素安排子項的版面配置。

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

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

基本上,這個版面配置如下:建立已命名的列 stack,其中包含所有內容,然後在該列中建立 2 欄,其中的第 2 欄又命名為 stack。第 1 欄的大小應符合最低內容需求,第 2 欄則可接受其餘部分。然後,如果受限制的可視區域 (540px 以下) 以上,請將側邊導覽列和主要內容元素放在同一列和欄中,讓兩者在 1x1 格線中彼此重疊。

再以這個回應式堆疊功能為基礎,我們現在可以利用網址列的狀態,切換側邊導覽列的顯示設定和轉換樣式。

返回 app/index.html 中的 <aside> 元素:

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

這樣一來,CSS 就能比對元素和網址雜湊。對於 :target 使用情況而言,這點非常重要。現在,元素的 ID 可以與我們將以 <a> 標記設定的網址雜湊相符。

此外,為了更輕鬆地指定 JavaScript,請新增控制側邊導覽列的重要元素 ID。首先,請在側邊導覽開啟連結中加入 ID:

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

接著,在側邊導覽關閉連結中加入 ID:

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

以上就是 <body> 回應式堆疊版面配置的元件,並將我們繫結至網址列。 接著執行下一項工作吧!

<aside> 也具備簡潔的版面配置。其中包含 2 個子項,一個 <nav> 是滑出的紙狀元件,而一個結尾的 <a> 連結元素 (設定網址必須設為 #)。這個連結在論文的右側看不到導覽面板,因此使用者可以「按一下關閉」視覺元件將其關閉。

將下列 CSS 新增至 css/sidenav.css

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

我認為這個比例和名稱對這裡來說非常出色 因此格狀空間能夠展現光彩,讓設計人員擁有許多掌控權。

接下來,需要有條件地重疊主要內容,並在任何文件捲動時保留我的位置。這對 position: sticky 和部分 overscroll-behavior 來說是很好的事。

為側邊導覽列新增下列樣式:

#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 */
  }
}

這些樣式可確保側邊導覽列為可視區域的高度、垂直捲動且包含捲動功能。重要的是,它會隱藏元素。根據預設,如果可視區域為 540px 或更小,請隱藏該側邊導覽列。除非!

:target 虛擬選取器新增至 #sidenav-open 元素:

#sidenav-open {

  @media (max-width: 540px) {

    &:target {
      visibility: visible;
    }
  }
}

如果元素的 ID 和網址列相同,請將 visibility 設為 visible。您可在捲動頁面後開啟側邊選單 或在側邊導覽列開啟時捲動頁面。您覺得如何?

將以下 CSS 加到 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;
  }
}

這些樣式會指定開啟和關閉按鈕,指定其輕觸和觸控樣式,並且在可視區域為 540px 以上時隱藏。

讓我們以尊重無障礙的方式新增 CSS 變形。 將下列 CSS 新增至 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;
  }
}
根據「prefers-reduced-motion」媒體查詢,顯示實際與未套用時間長度的互動示範。

某些 JavaScript 中的粉紅色

Escape 鍵應關閉選單。將這個 JS 新增至 js/index.js

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

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

這會監聽側邊導覽元素上的重要事件。如果是 Escape,系統會將網址雜湊設為空白,使側邊導覽轉換跳出。

接下來是使用者體驗 JS 部分是焦點管理。我希望能夠輕鬆開啟和關閉視窗,所以我等到側邊導覽列完成某種轉換後,再根據網址雜湊進行交叉比對,判斷啟動或關閉作業。然後使用 JavaScript 將焦點 設為與按下按鈕相對應的按鈕

js/index.js 中加入下列 JavaScript:

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();
});

立即體驗

  • 如要預覽網站,請按下「查看應用程式」,然後按下「全螢幕」圖示 全螢幕

結語

這個單元已完整滿足我對這項元件的需求。您可以放心建構程式碼,並使用 JavaScript 狀態 (而非網址) 推動應用程式,通常也能建立您的網站!而且持續新增或涵蓋更多用途

開啟 css/brandnav.css,查看已套用至這個元件的非版面配置相關樣式。我覺得重點並不重要,因為我希望從版面配置區分樣式,也比較容易使用複製及貼上功能。那裡還有更多學習功能!

如何製作回應式側邊導覽列元件? 你是否有超過 1 個 (例如正反兩面)?我想在 YouTube 影片中顯示您的解決方案,別忘了傳送 Tweet 給我,或在 YouTube 中留言提供您的程式碼,這樣每個人都能幫上忙!