Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> JavaScript

วิธีสร้าง Off-Canvas Menu (Side Navigation) ด้วย CSS และ JavaScript

สร้าง Off-Canvas Menu (Side Navigation) ที่สไลด์ออกมาจากขอบจอซ้าย ทับเนื้อหาหรือผลักเนื้อหาไปข้างๆ

โค้ดตัวอย่างสมบูรณ์

<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Off-Canvas Side Menu</title>
  <style>
    * { box-sizing: border-box; }
    
    body {
      margin: 0;
      font-family: 'Segoe UI', Tahoma, sans-serif;
      overflow-x: hidden; /* ป้องกัน Scroll แนวนอน */
    }
    
    /* Side Navigation */
    .sidenav {
      height: 100vh;
      width: 0; /* เริ่มต้นซ่อน (กว้าง 0) */
      position: fixed;
      z-index: 1000;
      top: 0;
      left: 0;
      background-color: #2edada; /* ฟ้าอ่อน */
      overflow-x: hidden;
      padding-top: 80px;
      transition: width 0.4s ease; /* Animation ความกว้าง */
    }
    
    .sidenav a {
      display: block;
      padding: 12px 16px 12px 32px;
      text-decoration: none;
      font-size: 20px;
      color: #000;
      font-family: inherit;
      transition: color 0.3s, background-color 0.3s;
    }
    
    .sidenav a:hover {
      color: #f1f1f1;
      background-color: rgba(255,255,255,0.1);
    }
    
    /* ปุ่มปิด */
    .sidenav .closebtn {
      position: absolute;
      top: 16px;
      right: 24px;
      font-size: 32px;
      color: #000;
      text-decoration: none;
      padding: 0;
      width: auto;
      height: auto;
      line-height: 1;
    }
    
    .sidenav .closebtn:hover {
      color: #f1f1f1;
      background: none;
    }
    
    /* ปุ่มเปิดเมนู */
    .openbtn {
      padding: 16px 24px;
      background-color: #001b91;
      color: white;
      font-size: 18px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      transition: background-color 0.3s;
    }
    
    .openbtn:hover {
      background-color: #00146b;
    }
    
    /* เนื้อหาหลัก - ผลักไปข้างๆ */
    .main-content {
      transition: margin-left 0.4s ease;
      padding: 24px;
    }
    
    /* Overlay สำหรับ Mobile (คลิกเพื่อปิด) */
    .overlay {
      display: none;
      position: fixed;
      top: 0; left: 0; right: 0; bottom: 0;
      background: rgba(0,0,0,0.5);
      z-index: 999;
    }
    
    @media (max-width: 768px) {
      .overlay.active { display: block; }
    }
  </style>
</head>
<body>
  <!-- Side Navigation -->
  <nav class="sidenav" id="sidenav">
    <a href="#" class="closebtn" aria-label="Close Menu">×</a>
    <a href="#">เข้าสู่ระบบ</a>
    <a href="#">สมัครสมาชิก</a>
    <a href="#">หน้าแรก</a>
    <a href="#">เกี่ยวกับเรา</a>
  </nav>
  
  <!-- Overlay สำหรับ Mobile -->
  <div class="overlay" id="overlay"></div>
  
  <!-- เนื้อหาหลัก -->
  <main class="main-content">
    <button class="openbtn" id="openBtn">☰ เปิดเมนู</button>
    <h1>ตัวอย่าง Off-Canvas Menu</h1>
    <p>กดปุ่ม "เปิดเมนู" เพื่อดู Side Navigation สไลด์ออกมา</p>
    <p>เนื้อหาหลักจะถูกผลักไปทางขวาพร้อมกัน</p>
  </main>
  
  <script>
    const sidenav = document.getElementById('sidenav');
    const openBtn = document.getElementById('openBtn');
    const closeBtn = sidenav.querySelector('.closebtn');
    const overlay = document.getElementById('overlay');
    const mainContent = document.querySelector('.main-content');
    const NAV_WIDTH = '280px';
    
    function showNav() {
      sidenav.style.width = NAV_WIDTH;
      mainContent.style.marginLeft = NAV_WIDTH;
      overlay.classList.add('active');
      document.body.style.overflow = 'hidden'; // ป้องกัน Scroll Body
    }
    
    function hideNav() {
      sidenav.style.width = '0';
      mainContent.style.marginLeft = '0';
      overlay.classList.remove('active');
      document.body.style.overflow = '';
    }
    
    // Event Listeners
    openBtn.addEventListener('click', showNav);
    closeBtn.addEventListener('click', hideNav);
    overlay.addEventListener('click', hideNav);
    
    // ปิดเมนูด้วยปุ่ม Escape
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') hideNav();
    });
    
    // ปิดเมนูเมื่อคลิกลิงก์ (Optional)
    sidenav.querySelectorAll('a:not(.closebtn)').forEach(link => {
      link.addEventListener('click', hideNav);
    });
  </script>
</body>
</html>

หลักการทำงาน

  1. CSS: Side Nav เริ่มต้น width: 0, transition: width 0.4s
  2. JavaScript: Toggle ความกว้างระหว่าง 0 และ 280px
  3. Main Content: ใช้ margin-left ผลักเนื้อหาไปข้างขวา (Push Pattern)
  4. Overlay: สำหรับ Mobile - คลิกนอกเมนูเพื่อปิด, ป้องกัน Scroll Body

รูปแบบการทำงาน (Patterns)

PatternลักษณะCSS หลัก
Push (ตัวอย่างนี้)ผลักเนื้อหาหลักไปข้างๆsidenav width + main margin-left
Overlayทับเนื้อหา (z-index สูงกว่า)sidenav width, main ไม่ย้าย
Revealเนื้อหาหลักอยู่คงที่, เมนูอยู่ใต้body transform, sidenav fixed

ปรับปรุงสำหรับ Production

// 1. ใช้ CSS Class แทน Inline Styles
function showNav() {
  document.body.classList.add('sidenav-open');
}
function hideNav() {
  document.body.classList.remove('sidenav-open');
}

/* CSS */
body.sidenav-open .sidenav { width: 280px; }
body.sidenav-open .main-content { margin-left: 280px; }
body.sidenav-open .overlay { display: block; }
body.sidenav-open { overflow: hidden; }

// 2. ใช้ Transform แทน Width (Performance - GPU Accelerated)
.sidenav {
  transform: translateX(-100%);
  transition: transform 0.4s ease;
}
body.sidenav-open .sidenav {
  transform: translateX(0);
}
body.sidenav-open .main-content {
  transform: translateX(280px);
}

Accessibility (A11y)

  • aria-label บนปุ่มเปิด/ปิด
  • role="dialog" + aria-modal="true" บน Side Nav
  • Focus Trap - จำกัด Tab ให้อยู่ในเมนูขณะเปิด
  • Escape Key เพื่อปิด