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

本程式碼研究室會說明如何建構網頁上的回應式滑出式側邊導覽版面配置元件。我們會逐步建構元件,先從 HTML 開始,然後是 CSS,最後是 JavaScript。

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

設定

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

HTML

首先,請取得 HTML 設定的基本概念,這樣才能有內容和一些方塊可供使用。

將下列 HTML 放入 <body> 標記中。

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

<aside> 會將導覽選單做為 <main> 的補充元素,而 <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/sidenav.css 中加入下列 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/sidenav.css 中新增下列 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 或更小時,請隱藏該側邊導覽。除非!

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

#sidenav-open {

  @media (max-width: 540px) {

    &:target {
      visibility: visible;
    }
  }
}

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

app/sidenav.css 底部新增下列 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/sidenav.css 中新增下列 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,系統會將網址雜湊設為空白,使側邊導覽列轉移出去。

下一個 UX 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 影片中介紹你的解決方案,請務必在 Twitter 上提及我,或在 YouTube 留言提供程式碼,讓大家都能參考!