建立側邊導覽列元件

回應式側邊選單的建立方式基礎總覽

在本篇文章中,我想與您分享如何為網頁製作 Sidenav 元件的原型,這個元件具備回應式、有狀態、支援鍵盤導覽、不論是否使用 JavaScript 皆可運作,且可跨瀏覽器運作。試用示範模式

如果你偏好觀看影片,請參閱這篇文章的 YouTube 版本:

總覽

建構回應式導覽系統相當困難。有些使用者會使用鍵盤,有些使用者會使用效能強大的電腦,有些使用者則會透過小型行動裝置造訪網站。所有訪客都應能開啟和關閉選單。

桌面到行動裝置的響應式版面配置示範
iOS 和 Android 的淺色和深色主題

網路策略

在這個元件探索中,我很高興能結合幾項重要的網路平台功能:

  1. CSS :target
  2. CSS grid
  3. CSS 轉換
  4. 針對檢視區和使用者偏好設定的 CSS 媒體查詢
  5. focus 適用的 JS 使用者體驗改善

我的解決方案只有一個側欄,且只有在「行動版」可視區域為 540px 以下時才會切換。我們會透過 540px 中斷點,切換行動裝置互動式版面配置和靜態桌面版面配置。

CSS :target 虛擬類別

一個 <a> 連結將網址雜湊設為 #sidenav-open,另一個則設為空白 ('')。最後,元素會使用 id 與雜湊相符:

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

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

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

點選每個連結會變更網頁網址的雜湊狀態,然後使用偽類顯示和隱藏側邊欄:

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
  }

  #sidenav-open:target {
    visibility: visible;
  }
}

CSS 格線

過去,我只使用絕對或固定位置的側邊導覽版版面配置和元件。不過,格線的 grid-area 語法可讓我們將多個元素指派給同一列或欄。

堆疊

主要版面配置元素 #sidenav-container 是一個格線,可建立 1 列和 2 欄,每個欄和列都會命名為 stack。當空間受限時,CSS 會將所有 <main> 元素的子項指派給相同的格線名稱,將所有元素放入相同的空間,建立堆疊。

#sidenav-container {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;
  min-height: 100vh;
}

@media (max-width: 540px) {
  #sidenav-container > * {
    grid-area: stack;
  }
}

<aside> 是包含側邊導覽的動畫元素。其中包含 2 個子項:名為 [nav] 的導覽容器 <nav>,以及用於關閉選單的背景 <a> (名為 [escape])。

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

調整 2fr1fr,找出您喜歡的選單疊加層和負空間關閉按鈕比例。

示範變更比例後的效果。

CSS 3D 變形效果和轉場效果

我們的版面配置現已堆疊在行動裝置可視區域的大小。在新增其他樣式之前,預設會在文章上方顯示。在接下來的章節中,我將會拍攝一些使用者體驗:

  • 開啟和關閉動畫
  • 只有在使用者同意的情況下,才以動畫呈現動作
  • visibility 建立動畫,避免鍵盤焦點進入畫面外元素

開始實作動畫時,我會先考量無障礙功能。

無障礙動作

並非所有人都喜歡滑出動作體驗。我們的解決方案會在媒體查詢中調整 --duration CSS 變數,藉此套用這項偏好設定。此媒體查詢值代表使用者的動作作業系統偏好 (如果有的話)。

#sidenav-open {
  --duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
示範互動情況 (套用與未套用時間長度的互動)。

現在,當側邊導覽列滑動開啟和關閉時,如果使用者偏好減少動畫效果,我會立即將元素移至檢視畫面,在沒有動畫效果的情況下維持狀態。

轉場、轉換、翻譯

側邊導覽列關閉 (預設)

為了將行動裝置側邊選單的預設狀態設為畫面外狀態,我使用 transform: translateX(-110vw) 來定位元素。

請注意,我在 -100vw 的一般螢幕外程式碼中新增了另一個 10vw,以確保側邊導覽列的 box-shadow 在隱藏時不會窺探主要檢視區。

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);
  }
}
側邊導覽列

#sidenav 元素與 :target 相符時,請將 translateX() 位置設為 homebase 0,並在網址雜湊變更時,觀察 CSS 如何將元素從 -110vw 的「out」位置,滑動至 var(--duration) 上的 0 的「in」位置。

@media (max-width: 540px) {
  #sidenav-open:target {
    visibility: visible;
    transform: translateX(0);
    transition:
      transform var(--duration) var(--easeOutExpo);
  }
}

轉場可見度

目前的目標是在選單顯示時,將其隱藏起來,以免系統將焦點放在畫面外的選單。方法是在 :target 變更時設定瀏覽權限轉場。

  • 進入畫面時,請勿轉換顯示設定,而是立即顯示,這樣我可以立即看到元素滑入畫面並接受焦點。
  • 在退出時,轉換顯示,但延遲轉換,因此會在轉換結束時翻轉為 hidden

改善無障礙使用者體驗

這項解決方案需要變更網址,才能管理狀態。自然而然地,您應該在這裡使用 <a> 元素,這樣就能免費獲得一些不錯的無障礙功能。讓我們在互動元素中加入標籤,清楚表達意圖。

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

<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
  <svg>...</svg>
</a>
語音操作和鍵盤互動使用者體驗的示範。

如今,我們的主要互動按鈕已清楚說明滑鼠和鍵盤的使用意圖。

:is(:hover, :focus)

這個實用的 CSS 功能式偽裝選取器,可讓我們快速將懸停樣式與焦點共用,以便納入懸停樣式。

.hamburger:is(:hover, :focus) svg > line {
  stroke: hsl(var(--brandHSL));
}

加入 JavaScript

按下 escape 鍵即可關閉

鍵盤上的 Escape 鍵應該會關閉選單,對嗎?我們來接線吧。

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

sidenav.addEventListener('keyup', event => {
  if (event.code === 'Escape') document.location.hash = '';
});
瀏覽器歷史記錄

為避免開啟和關閉互動導致瀏覽器記錄中堆疊多個項目,請在關閉按鈕中加入下列 JavaScript 內嵌程式碼:

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>

這會在關閉時移除網址歷史記錄項目,使其看似從未開啟過選單。

以使用者體驗為重

下一段程式碼片段能幫助我們將焦點放在開啟或關閉按鈕的開啟和關閉按鈕上。我想輕鬆換機。

sidenav.addEventListener('transitionend', e => {
  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
      ? document.querySelector('#sidenav-close').focus()
      : document.querySelector('#sidenav-button').focus();
})

側邊選單開啟後,將焦點移至關閉按鈕。側邊導覽列關閉時,請聚焦開啟的按鈕。方法是在 JavaScript 中對元素呼叫 focus()

結論

既然你知道我如何做到,你會怎麼做呢?這可建立有趣的元件架構!誰會製作第一個含有空格的版本?🙂

讓我們多方嘗試,瞭解在網路上建構應用程式的所有方式。建立 Glitch,並在推特上傳你的版本,我會將其加入下方的「社群混音」部分。

社群重混作品