需求:點擊漢堡圖標 / 從左緣右滑 → 左側欄滑入;再次點擊 / 左滑 → 滑出。核心技術:**CSS Transform 動畫 + 觸控事件攔截**。
<div id="app">
<header class="topbar">
<button id="menuBtn" aria-label="選單">☰</button>
<h1>標題</h1>
</header>
<div id="sidebar" class="sidebar">
<nav><ul><li>選項 1</li><li>選項 2</li></ul></nav>
</div>
<main id="content" class="content">頁面內容</main>
<div id="overlay" class="overlay"></div>
</div>
/* 側邊欄預設隱藏在左側外部 */
.sidebar {
position: fixed; top: 0; left: 0; bottom: 0; width: 260px;
background: #fff; box-shadow: 2px 0 8px rgba(0,0,0,.15);
transform: translateX(-100%); /* 關鍵:GPU 加速 */
transition: transform 0.3s ease;
z-index: 1000;
}
.sidebar.open { transform: translateX(0); }
/* 遮罩層 */
.overlay {
position: fixed; inset: 0; background: rgba(0,0,0,.4);
opacity: 0; visibility: hidden; transition: opacity 0.3s;
z-index: 999;
}
.overlay.show { opacity: 1; visibility: visible; }
/* 內容區可選:側欄開啟時右移或縮放 */
.content { transition: transform 0.3s ease; }
.content.shifted { transform: translateX(260px); }
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('overlay');
const menuBtn = document.getElementById('menuBtn');
const content = document.getElementById('content');
function openSidebar() {
sidebar.classList.add('open');
overlay.classList.add('show');
content.classList.add('shifted');
document.body.style.overflow = 'hidden'; // 防止背景滾動
}
function closeSidebar() {
sidebar.classList.remove('open');
overlay.classList.remove('show');
content.classList.remove('shifted');
document.body.style.overflow = '';
}
function toggleSidebar() {
sidebar.classList.contains('open') ? closeSidebar() : openSidebar();
}
// 1. 點擊按鈕切換
menuBtn.addEventListener('click', toggleSidebar);
// 2. 點擊遮罩關閉
overlay.addEventListener('click', closeSidebar);
// 3. 觸控滑動手勢 (從左緣右滑開啟,左滑關閉)
let startX = 0;
const THRESHOLD = 80; // 觸發閾值 px
document.addEventListener('touchstart', e => {
startX = e.touches[0].clientX;
}, { passive: true });
document.addEventListener('touchend', e => {
const endX = e.changedTouches[0].clientX;
const deltaX = endX - startX;
if (startX < 30 && deltaX > THRESHOLD) { // 從左緣右滑
openSidebar();
} else if (deltaX < -THRESHOLD) { // 左滑關閉
closeSidebar();
}
}, { passive: true });
// 4. ESC 鍵關閉
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeSidebar(); });
touchmove 中即時設定 sidebar.style.transform = \`translateX(\${Math.min(0, -260 + deltaX)}px)\`,釋放時判斷閾值決定彈回或彈開 (需配合 transition: none 臨時關閉動畫)。aria-expanded、role="dialog"、焦點陷阱。<Drawer /> 元件,透過 v-model / 受控屬性控制。| 現象 | 原因 | 解決 |
|---|---|---|
| 滑動卡頓 | 未用 transform / 觸發重排 | 只動 transform/opacity,加 will-change: transform |
| 遮罩點擊不關閉 | z-index 層級錯誤 | overlay z-index 低於 sidebar,高於 content |
| Android 返回鍵不關閉 | 未監聽 back 事件 | WebView 加上 onBackPressed 處理或 History API |
此方案純 CSS 動畫、零依賴、效能極佳,適用於所有現代行動瀏覽器與 WebView。