基礎總覽:如何建構回應式滑出式側邊導覽
在這篇文章中,我想分享如何為網頁製作 Sidenav 元件原型,這個元件具有回應性、有狀態、支援鍵盤導覽、可搭配或不搭配 JavaScript 運作,且適用於各種瀏覽器。試用示範模式。
如果比較喜歡看影片,可以觀看這篇貼文的 YouTube 版本:
總覽
建構回應式導覽系統並不容易,部分使用者會使用鍵盤,部分使用者會使用功能強大的桌機,部分使用者則會透過小型行動裝置造訪。所有訪客都應該能開啟及關閉選單。
網站策略
在探索這個元件時,我很高興能結合幾項重要的網頁平台功能:
我的解決方案有一個側欄,且只會在 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 個資料欄,其中 1 個資料列和 1 個資料欄分別命名為 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>
,以及名為 [escape]
的背景 <a>
,用於關閉選單。
#sidenav-open {
display: grid;
grid-template-columns: [nav] 2fr [escape] 1fr;
}
調整 2fr
和 1fr
,找出選單疊加層和負空間關閉按鈕的合適比例。
CSS 3D 轉換和轉場效果
現在,我們的版面配置會以行動裝置可視區域大小堆疊。在我新增一些新樣式之前, 預設會疊加在文章上。以下是我在下一節中要達成的 UX:
- 動畫開啟和關閉
- 只有在使用者同意的情況下,才使用動態效果
- 為
visibility
加入動畫效果,避免鍵盤焦點進入螢幕外元素
開始導入動態動畫時,我會優先考量無障礙功能。
無障礙動態效果
並非所有人都想要滑出動作體驗。在我們的解決方案中,這項偏好設定是透過調整媒體查詢內的 --duration
CSS 變數套用。這個媒體查詢值代表使用者作業系統的動態效果偏好設定 (如有)。
#sidenav-open {
--duration: .6s;
}
@media (prefers-reduced-motion: reduce) {
#sidenav-open {
--duration: 1ms;
}
}
現在,當側邊導覽列開啟和關閉時,如果使用者偏好減少動作,我會立即將元素移至檢視畫面,維持狀態但不移動。
轉場、變形、翻譯
側邊導覽列 (預設)
如要將行動裝置上側邊導覽列的預設狀態設為螢幕外狀態,請使用 transform: translateX(-110vw)
放置元素。
請注意,我在典型的螢幕外程式碼中新增了另一個 10vw
,確保側邊導覽列的 box-shadow
隱藏時不會出現在主要檢視區塊中。-100vw
@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
的外部位置滑動至 var(--duration)
的「內部」位置 0
。
@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>
這樣一來,系統就會在關閉時移除網址記錄項目,就像選單從未開啟一樣。
Focus UX
下一個程式碼片段可協助我們在開啟或關閉按鈕後,將焦點放在這些按鈕上。我想輕鬆切換。
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, 在 Twitter 上傳送你的版本給我,我會將其新增至下方的「社群混音」部分。
社群重混作品
- @_developit with custom elements: demo & code
- @mayeedwin1 使用 HTML/CSS/JS:示範與程式碼
- @a_nurella 的 Glitch Remix:示範與程式碼
- @EvroMalarkey,使用 HTML/CSS/JS:示範與程式碼