iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
JavaScript

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

【第十三課】媒體呈現:動態圖片展示藝廊(Image Gallery & Event Delegation)

  • 分享至 

  • xImage
  •  

前言

在電商商品頁或旅遊網站中,點擊小縮圖即時切換大圖是標準配備。但如果畫面上有幾十張圖片,每張都獨立綁定 Listener 會顯得冗長且佔用效能。今天我們就用「事件委派(Event Delegation)」來優化它。

核心觀念與語法拆解

  1. 事件委派(Event Delegation):將點擊事件綁定在縮圖的父容器 thumbContainer 上,利用事件冒泡機制,透過 event.target.classList.contains('thumb') 精準捕捉子元素點擊。
  2. 動態 DOM 屬性切換:讀取縮圖的 src 與 data-caption,直接覆寫大圖 DOM 的 src 與文字內容,搭配 CSS opacity 做出流暢的切換淡入效果。

1. 事件委派(Event Delegation)與 event.target

  • 為什麼選用這個架構?

    如果縮圖區有 100 張圖片,為每張圖片單獨綁定 addEventListener 會消耗大量記憶體。

    利用 DOM 的事件冒泡機制(Event Bubbling),我們只需要在外層容器(父元素)綁定 一個 點擊事件,就能監聽並處理所有內層縮圖的點擊!

  • 這個機制在幹嘛?

    當點擊縮圖時,點擊事件會向上冒泡傳到父容器。父容器透過 event.target 檢查:「剛才被點擊的到底是不是 <img> 標籤?」,如果是,才執行圖片切換邏輯。

2. 動態替換 DOM 屬性 (mainImg.src 與 mainImg.alt)

  • 為什麼選用這個方式?

    切換大圖不需要重新建立 DOM 元素,直接將縮圖上記錄的高清圖網址(或 src)與文字說明寫入大圖標籤的 src 與 alt 屬性即可,效能較高。

<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <title>第十三課 - 動態圖片展示藝廊</title>
  <style>
    body { font-family: sans-serif; padding: 40px; background-color: #f5f5f5; }
    .gallery-container { max-width: 600px; margin: 0 auto; background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }

    /* 主圖顯示區塊 */
    .main-image-container { width: 100%; height: 350px; overflow: hidden; border-radius: 8px; margin-bottom: 10px; background-color: #eee; }
    .main-image { width: 100%; height: 100%; object-fit: cover; transition: opacity 0.3s ease; }

    /* 圖片說明標題 */
    .image-caption { font-size: 16px; font-weight: bold; color: #333; text-align: center; margin-bottom: 20px; }

    /* 縮圖選單區塊 (父容器) */
    .thumbnail-list { display: flex; gap: 10px; justify-content: center; }

    /* 單張縮圖 */
    .thumb {
      width: 80px;
      height: 60px;
      object-fit: cover;
      border-radius: 6px;
      cursor: pointer;
      opacity: 0.6;
      border: 2px solid transparent;
      transition: all 0.2s ease;
    }
    .thumb:hover { opacity: 0.9; }

    /* 縮圖選取狀態 (Active) */
    .thumb.active {
      opacity: 1;
      border-color: #007bff;
      transform: scale(1.05);
    }
  </style>
</head>
<body>

  <div class="gallery-container">
    <!-- 大圖呈現區 -->
    <div class="main-image-container">
      <img id="mainImg" class="main-image" src="https://picsum.photos/id/1018/600/400" alt="高山湖泊風景">
    </div>
    <div id="imageCaption" class="image-caption">高山湖泊風景</div>

    <!-- 縮圖列表 (使用事件委派:只在父容器綁定事件) -->
    <div id="thumbContainer" class="thumbnail-list">
      <img class="thumb active" src="https://picsum.photos/id/1018/600/400" data-caption="高山湖泊風景" alt="縮圖 1">
      <img class="thumb" src="https://picsum.photos/id/1015/600/400" data-caption="壯麗河流峽谷" alt="縮圖 2">
      <img class="thumb" src="https://picsum.photos/id/1019/600/400" data-caption="海邊晨曦曙光" alt="縮圖 3">
      <img class="thumb" src="https://picsum.photos/id/1039/600/400" data-caption="瀑布森林秘境" alt="縮圖 4">
    </div>
  </div>

  <script>
    // ----------------------------------------------------
    // JavaScript 邏輯 (圖片切換與事件委派)
    // ----------------------------------------------------

    // 第一步:取得大圖 DOM、標題 DOM 與縮圖父容器 DOM
    const mainImg = document.getElementById("mainImg");
    const imageCaption = document.getElementById("imageCaption");
    const thumbContainer = document.getElementById("thumbContainer");

    // 第二步:使用「事件委派」機制,將點擊事件綁定在縮圖的【父容器】上
    thumbContainer.addEventListener("click", function(event) {
      
      // 1. 檢查點擊的對象(event.target)是否為縮圖 (具有 .thumb 類別)
      const clickedThumb = event.target;
      if (!clickedThumb.classList.contains("thumb")) {
        return; // 如果點擊的是父容器的空白處,直接結束不處理
      }

      // 2. 取得被點擊縮圖的圖片網址與自訂標題 (data-caption)
      const newSrc = clickedThumb.src;
      const newCaption = clickedThumb.dataset.caption;

      // 3. 換圖淡出淡入視覺效果:先淡出,換網址後再淡入
      mainImg.style.opacity = "0.3";
      
      setTimeout(function() {
        mainImg.src = newSrc;
        mainImg.alt = newCaption;
        imageCaption.innerText = newCaption;
        mainImg.style.opacity = "1";
      }, 150);

      // 4. 排他思想:清空所有縮圖的 .active 類別,並為當前縮圖加上 .active
      const allThumbs = thumbContainer.querySelectorAll(".thumb");
      allThumbs.forEach(thumb => thumb.classList.remove("active"));
      clickedThumb.classList.add("active");

    });
  </script>
</body>
</html>

總結與實用情境

今天我們掌握了「事件委派」這個前端優化的核心觀念,事件委派不僅能減少記憶體消耗,即使未來透過 API 畫面上動態「新增」了新縮圖,父容器依然能自動監控並處理它們的點擊。

下集預告

在表單填寫或輸入搜尋關鍵字時,如果即時統計使用者輸入了多少字,該怎麼做?

【第十四課】表單與輸入:字數即時統計與限制告警(input 事件與雙向狀態),我們將學習如何掌控表單輸入事件與動態警告樣式。


上一篇
【第十二課】頁籤切換:Tab 頁籤內容切換器(Data Attribute 自訂屬性與狀態控制)
下一篇
【第十四課】表單與輸入:字數即時統計與限制告警(input 事件與狀態監控)
系列文
解決學了JavaScript不知道能做什麼:用DOM操作打造實用網頁效果 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言