iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
JavaScript

解決學了JavaScript不知道能做什麼:用DOM操作打造實用網頁效果 系列 第 10 篇

【第十課】彈窗效果:輕量化 Modal 燈箱效果(遮罩層控制與阻擋滾動)

  • 分享至 

  • xImage
  •  

前言

不論是登入畫面、條款確認還是圖片放大看,Modal 燈箱元件在網站中隨處可見。看似簡單的彈窗,如果沒有處理好「背景鎖定」與「遮罩點擊關閉」,就會造成體驗感下降,今天我們用純原生 DOM 操作寫出專業等級的 Modal 燈箱。

觀念與函式深度解析

1. document.body.style.overflow = 'hidden' 阻擋滾動

  • 為什麼選用這個屬性?

    當 Modal 彈窗打開時,如果使用者滑動滾輪,後方的網頁內容依然會跟著上下滾動,這在使用者體驗(UX)上是非常差的。直接將 body 的 overflow 屬性設定為 "hidden",就能瞬間鎖定網頁的滾動條。

  • 這個屬性在幹嘛?

    overflow: hidden 會告訴瀏覽器:「只要超出視窗範圍的內容,通通隱藏且不允許滾動」。當關閉彈窗時,再將其重置為 ""(恢復預設),頁面就能重新滑動。

  • 有沒有其他替代方案?

    • 方案 A:對滾動事件執行 event.preventDefault()
      • 簡介:監聽 touchmove 或 wheel 事件並取消預設行為。
      • 比較:這種寫法非常繁瑣,且在行動裝置(iOS Safari)上容易出現兼容性怪癖;直接切換 body 的 CSS overflow 屬性是最乾淨高效的做法。

2. 遮罩層點擊關閉(event.target 與事件冒泡判斷)

  • 為什麼選用這個判斷?

    好的 Modal 設計通常允許使用者點擊「彈窗外的半透明黑色遮罩」來快速關閉彈窗。我們需要判斷使用者點擊的到底是不是「遮罩本體」,而不是點到內部的「對話框文字區域」。

  • 這個判斷在幹嘛?

    if (event.target === modalOverlay):檢查觸發點擊事件的 DOM 元素(event.target)是否恰好就是遮罩層本身。

完整實作程式碼

<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <title>第十課 - 輕量化 Modal 燈箱效果</title>
  <style>
    body { font-family: sans-serif; padding: 40px; background-color: #f4f6f8; line-height: 1.6; }
    
    /* 觸發按鈕樣式 */
    .open-btn {
      padding: 12px 24px;
      font-size: 16px;
      background-color: #2ecc71;
      color: white;
      border: none;
      border-radius: 6px;
      cursor: pointer;
    }

    /* 全螢幕半透明黑色遮罩 (預設隱藏) */
    .modal-overlay {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      background-color: rgba(0, 0, 0, 0.5); /* 50% 半透明黑 */
      display: flex;
      justify-content: center;
      align-items: center;

      /* 視覺隱藏與動畫準備 */
      opacity: 0;
      visibility: hidden;
      transition: all 0.3s ease;
      z-index: 1000;
    }

    /* 彈窗主體對話框 */
    .modal-box {
      background: white;
      width: 400px;
      padding: 30px;
      border-radius: 8px;
      box-shadow: 0 10px 25px rgba(0,0,0,0.2);
      transform: translateY(-20px); /* 預設往上偏一點,準備做下滑動畫 */
      transition: transform 0.3s ease;
      position: relative;
    }

    /* 當 Modal 處於顯示狀態時的 CSS 類別 */
    .modal-overlay.active {
      opacity: 1;
      visibility: visible;
    }

    .modal-overlay.active .modal-box {
      transform: translateY(0); /* 對話框歸位 */
    }

    /* 關閉按鈕 (x) */
    .close-btn {
      position: absolute;
      top: 15px;
      right: 20px;
      font-size: 22px;
      cursor: pointer;
      color: #999;
    }
    .close-btn:hover { color: #333; }
  </style>
</head>
<body>

  <h2>第十課:Modal 彈窗燈箱展示</h2>
  <p>點擊下方按鈕打開彈窗,注意觀察:彈窗打開時背景頁面無法滾動。</p>
  
  <button id="openModalBtn" class="open-btn">開啟會員條款</button>

  <!-- 故意拉長頁面高度以測試滾動阻擋 -->
  <div style="height: 1500px; margin-top: 20px; background: #eef2f5; padding: 20px;">
    (頁面延伸區塊 - 測試背景鎖定)
  </div>

  <!-- Modal 燈箱 HTML 結構 -->
  <div id="modalOverlay" class="modal-overlay">
    <div class="modal-box">
      <span id="closeModalBtn" class="close-btn">&times;</span>
      <h3>會員服務條款</h3>
      <p>歡迎使用我們的服務!點擊遮罩區域或右上角關閉按鈕即可退出此對話框。</p>
      <p>當這個視窗開啟時,背景的網頁捲軸會被暫時鎖定,避免使用者混淆。</p>
    </div>
  </div>

  <script>
    // ----------------------------------------------------
    // JavaScript 邏輯 (Modal 開關與 body 鎖定)
    // ----------------------------------------------------

    // 第一步:取得所需的 DOM 元素
    const openModalBtn = document.getElementById("openModalBtn");
    const closeModalBtn = document.getElementById("closeModalBtn");
    const modalOverlay = document.getElementById("modalOverlay");

    // 開啟彈窗的函式
    function openModal() {
      // 1. 加上 active 類別,顯示遮罩與播放淡入動畫
      modalOverlay.classList.add("active");
      // 2. 鎖定 body 滾動條,防止背景頁面繼續滑動
      document.body.style.overflow = "hidden";
    }

    // 關閉彈窗的函式
    function closeModal() {
      // 1. 移除 active 類別,隱藏遮罩
      modalOverlay.classList.remove("active");
      // 2. 恢復 body 滾動條
      document.body.style.overflow = "";
    }

    // 第二步:為按鈕綁定開啟與關閉事件
    openModalBtn.addEventListener("click", openModal);
    closeModalBtn.addEventListener("click", closeModal);

    // 第三步:點擊黑色遮罩背景也能關閉彈窗 (提升 UX)
    modalOverlay.addEventListener("click", function(event) {
      // 判斷點擊的目標是否為遮罩層本身 (而不是裡面的 .modal-box)
      if (event.target === modalOverlay) {
        closeModal();
      }
    });
  </script>
</body>
</html>

總結與實用情境

今天我們搞定了現代網頁極具代表性的 UI 組件——Modal 燈箱。結合了動態 Class 切換、視窗防滾動鎖定與事件點擊目標判斷,這套邏輯可以直接套用到未來的購物車確認框、產品快速預覽等功能中。

下集預告

常常在社交平台或新聞網站看到「往下滑動時自動載入更多新卡片」的動態生成效果嗎,下集,

【第十一課】動態生成:無窮滾動/卡片動態渲染(createElement 與 appendChild),我們將學習如何用 JS 動態產出大量 DOM 卡片並掛載到頁面上。


上一篇
第九課:【視窗控制】「回到頂部(Back to Top)」按鈕與平滑滾動
下一篇
【第十一課】動態生成:無窮滾動與卡片動態渲染(createElement 與觸底偵測)
系列文
解決學了JavaScript不知道能做什麼:用DOM操作打造實用網頁效果 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言