紀錄工作經驗、相關知識,解決技術相關問題。

HTML, 新手 Jamp Holes

新手必學 HTML map 標籤:從零建構影像地圖(Image Map)與 DOM 樹狀結構的對應關係

新手必學 HTML map 標籤:從零建構影像地圖(Image Map)與 DOM 樹狀結構的對應關係 特色圖片

在日常前端專案開發中,時常會接到這樣的需求:「設計師給了一張完整的台灣各縣市分佈圖(或是商場樓層平面圖、機械零件拆解圖),希望使用者滑鼠點到不同區塊時,能跳轉到對應的介紹頁面。」對於新手來說,第一直覺往往是把圖片切成十幾張碎圖再用 CSS 拼湊,結果一換螢幕尺寸就全盤崩潰。

其實,瀏覽器原生就內建了專門解決這個問題的強大武器——HTML map 標籤(<map>,即 Client-Side Image Map 客戶端影像地圖)!

只要透過一張 <img> 搭配 HTML map 標籤(<map>)與 <area> 標籤,就能在不裁切圖片的前提下,於單張圖片上精準畫出矩形、圓形甚至任意多邊形的互動熱區(Clickable Hotspots)。更有趣的是,<map> 在瀏覽器 DOM 樹狀結構中的運作方式與一般標籤截然不同——它的「視覺呈現(<img>)」與「互動熱區節點(<map> > <area>)」在 DOM 樹中是完全分離的兩個分支!這篇筆記就帶你從零認識 HTML map 標籤,徹底打通影像地圖與 DOM 樹的底層對應關係。


⚡ 快速簡單使用(30 秒複製即用範本)

如果你趕時間、只想立刻在圖片上加上可點擊的連結區域,直接複製下方最小可用範本(只需替換圖片路徑 src、座標 coords 與連結 href 即可運作):

<!-- 步驟 1:放上底圖,並加上 usemap="#自訂名稱"(注意開頭一定要有 # 號) -->
<img src="product-banner.jpg" alt="產品導覽圖" width="600" height="400" usemap="#quick-map">

<!-- 步驟 2:建立對應的 <map name="自訂名稱">(注意這裡不能有 # 號) -->
<map name="quick-map" id="quick-map">
  <!-- 矩形點擊區 (rect):coords="左上X,左上Y,右下X,右下Y" -->
  <area shape="rect" coords="20,20,280,180" href="/product-a" alt="前往產品 A">

  <!-- 圓形點擊區 (circle):coords="圓心X,圓心Y,半徑R" -->
  <area shape="circle" coords="450,200,80" href="/product-b" alt="前往產品 B">

  <!-- 多邊形點擊區 (poly):coords="X1,Y1,X2,Y2,X3,Y3..."(至少 3 個頂點) -->
  <area shape="poly" coords="100,250,250,250,175,380" href="/product-c" alt="前往產品 C">
</map>

💡 3 秒修改重點:

  1. <img usemap="#quick-map"> 的名字必須跟 <map name="quick-map"> 完全相同(一個有 #,一個沒有 #)。
  2. 座標原點 (0, 0) 在圖片左上角,數值單位為像素(px)。

一、 什麼是 HTML map 標籤?5 秒鐘掌握影像地圖鐵三角速查表

在開始寫程式碼之前,我們先用最白話的方式定義 HTML <map> 標籤:
<map> 是 HTML 中用來定義「客戶端影像地圖(Image Map)」的容器元素。它必須與 <img> 圖片標籤以及內部的 <area> 熱區標籤三者協同運作,才能在單一圖片上建立多個獨立的可點擊超連結區域。

我們可以將這組「影像地圖鐵三角(<img> + <map> + <area>)」的職責與 DOM 樹特性整理成以下速查表:

標籤名稱在影像地圖中的核心職責必備關鍵屬性在 DOM 樹中的節點特性與內容模型
<img>視覺像素宿主(Visual Host):負責在畫面上渲染出底圖,並指定要套用哪一張 <map>。src, alt, usemap="#地圖名稱"(注意:最前面必須加 # 號)空元素(Void Element):在 DOM 樹中不能有任何子節點(無法把 <area> 寫在 <img> 裡面)。
<map>熱區定義容器(Map Container):負責收納所有的 <area> 座標節點,供 <img> 跨節點引用。name="地圖名稱"(不可含 # 號與空白,建議同步加上同名的 id)透明內容模型(Transparent):本身在畫面上不佔據獨立空間(預設 display: none),作為 <area> 的父節點。
<area>幾何互動熱區(Hotspot Node):定義點擊範圍的形狀、像素座標、跳轉連結與無障礙替代文字。shape (rect / circle / poly), coords, href, alt空元素(Void Element):無結束標籤;雖在 CSS 設為 display: none,卻能接收滑鼠點擊與鍵盤 Tab 焦點!

二、 為什麼 map 的 DOM 樹結構這麼特別?解析「視覺」與「DOM 節點」的跨節點映射

很多新手第一次寫 <map> 時都會產生一個強烈的疑問:「為什麼 <area> 熱區明明是畫在 <img> 圖片上面,但在 HTML 裡卻不能寫成 <img><area></img>,而要另外包在一個 <map> 標籤裡?」

1. 歷史與規範原因:img 是沒有結束標籤的 Void Element

根據 HTML 元素內容模型規範,<img> 屬於空元素(Void Element),在 HTML5 解析器(Parser)中遇到 <img> 就會立即閉合,根本不允許擁有任何子節點。

因此,W3C 與 WHATWG 設計了一套在整個 HTML 標準中極為罕見的「跨節點引用映射機制(Hash-Name Reference Mapping)」:

  1. 你在 DOM 樹的某個位置放一張 <img usemap="#taiwan-map">。
  2. 在 DOM 樹的另一個位置(通常是 <img> 的下一個兄弟節點,甚至放在 <footer> 也能運作)放一個 <map name="taiwan-map">,並在裡面塞入多個 <area> 子節點。
  3. 瀏覽器渲染引擎會透過 #taiwan-map 這個名稱索引,將 <map> 內所有 <area> 的幾何座標,虛擬投射疊加(Overlay) 到 <img> 的畫面座標系上!

2. 視覺化對照:DOM 樹父子階層 vs. 瀏覽器畫面碰撞偵測層 (Hit-Testing)

讓我們透過下方的 Mermaid 架構圖,直觀對比 <map> 在「記憶體 DOM 樹」與「使用者畫面視覺層」的對應關係:

. 視覺化對照:DOM 樹父子階層 vs. 瀏覽器畫面碰撞偵測層 (Hit-Testing) 視覺化架構流程圖
圖:. 視覺化對照:DOM 樹父子階層 vs. 瀏覽器畫面碰撞偵測層 (Hit-Testing) 視覺化架構流程圖

從上圖可以清楚看出一個極為關鍵的底層特性:

  • 視覺像素歸 <img> 管:圖片的寬高、載入狀態、濾鏡效果都在 <img> 節點上。
  • 互動事件與無障礙焦點歸 <area> 管:當使用者拿滑鼠點擊圖片上的「熱區 A」,或是按鍵盤 Tab 鍵切換焦點時,瀏覽器觸發的 event.target 絕對不是 <img>,而是位於 <map> 底下的 <area id="zone-a"> 節點!

三、 實戰代碼教學:從零建構 map 影像地圖與 DOM 事件委派

理解了 <map> 與 DOM 樹的對應邏輯後,我們直接動手寫一個完整的生產級範例:建立一張「伺服器機房架構導覽圖」,包含矩形(rect)、圓形(circle)與多邊形(poly)三種 <area> 熱區,並示範如何利用 DOM 樹的父子關係,在 <map> 上透過 事件委派(Event Delegation) 統一攔截所有熱區點擊!

1. area 三大形狀(shape)與座標(coords)語法規則

圖片的座標原點 (0, 0) 永遠位於圖片的左上角(X 軸向右遞增,Y 軸向下遞增,單位為 CSS 像素):

  1. shape="rect"(矩形):coords="x1,y1,x2,y2"(左上角 x1, y1 與右下角 x2, y2)。
  2. shape="circle"(圓形):coords="x,y,r"(圓心座標 x, y 與半徑 r)。
  3. shape="poly"(多邊形):coords="x1,y1,x2,y2,x3,y3,..."(依序填入每個頂點的 x, y 座標,瀏覽器會自動將最後一點與第一點閉合)。

2. 完整 HTML + JavaScript 實戰程式碼

<!-- 檔案名稱:interactive-image-map.html -->
<figure class="architecture-figure">
  <!-- 1. 視覺宿主 <img>:必須明確指定原始 width/height,並以 usemap="#名稱" 綁定 -->
  <img
    src="/images/server-architecture-800x400.png"
    alt="雲端系統架構圖:包含 API 閘道器、負載平衡器與主資料庫叢集"
    width="800"
    height="400"
    usemap="#server-arch-map"
    id="arch-image"
  >

  <!-- 2. 熱區容器 <map>:name 屬性不可加 # 號,同時加上同名 id 確保最大相容性 -->
  <map name="server-arch-map" id="server-arch-map">
    <!-- 矩形熱區:左上角 (40, 80) 到 右下角 (220, 200) -->
    <area
      shape="rect"
      coords="40,80,220,200"
      href="#api-gateway"
      alt="API Gateway 閘道器模組"
      data-component="API Gateway"
      data-tps="15,000 req/s"
    >

    <!-- 圓形熱區:圓心 (400, 140),半徑 65px -->
    <area
      shape="circle"
      coords="400,140,65"
      href="#load-balancer"
      alt="Nginx 負載平衡器節點"
      data-component="Load Balancer"
      data-tps="45,000 req/s"
    >

    <!-- 多邊形熱區:三角形/六邊形資料庫圖示頂點座標 -->
    <area
      shape="poly"
      coords="620,60,740,60,770,140,740,220,620,220,590,140"
      href="#primary-db"
      alt="PostgreSQL 主從資料庫叢集"
      data-component="Primary DB Cluster"
      data-tps="8,200 qps"
    >
  </map>

  <figcaption id="status-panel">請點擊上方架構圖中的任一元件檢視即時狀態。</figcaption>
</figure>

<script>
  const mapElement = document.getElementById('server-arch-map');
  const imgElement = document.getElementById('arch-image');
  const statusPanel = document.getElementById('status-panel');

  // 🟢 最佳實踐:利用 DOM 樹父子冒泡機制,直接在父容器 <map> 上進行事件委派 (Event Delegation)
  mapElement.addEventListener('click', (event) => {
    // 確認觸發點擊的 DOM 節點是否為 <area>
    const area = event.target.closest('area');
    if (!area) return;

    event.preventDefault(); // 阻擋預設錨點跳轉,改由 JS 更新面板

    console.log('觸發點擊的真實 DOM 節點:', area.tagName);
    console.log('該節點在 DOM 樹中的父元素:', area.parentElement.tagName);
    console.log('選定元件:', area.dataset.component, '| 吞吐量:', area.dataset.tps);

    statusPanel.textContent = `目前選取:${area.dataset.component}(效能指標:${area.dataset.tps})`;
  });

  // ⚠️ 驗證實驗:如果把 click 綁在 <img> 上,點選 <area> 熱區時根本不會觸發!
  imgElement.addEventListener('click', () => {
    console.log('你點到了 <img> 上沒有被 <area> 覆蓋的空白背景區域!');
  });
</script>

3. 執行結果輸出 (Expected Output)

當使用者點擊圖片中央的「負載平衡器(圓形熱區)」時,Chrome DevTools Console 的輸出如下:

觸發點擊的真實 DOM 節點: AREA
該節點在 DOM 樹中的父元素: MAP
選定元件: Load Balancer | 吞吐量: 45,000 req/s

而如果你點擊圖片上沒有定義 <area> 的空白背景處,Console 才會印出:

你點到了 <img> 上沒有被 <area> 覆蓋的空白背景區域!

四、 新手必踩的 4 大 map 與 DOM 樹實戰地雷

由於 <map> 採用了「視覺在 <img>、節點在 <map>」的分離式架構,前端新手在撰寫語法、綁定事件與處理 RWD 時極易踩中以下四大陷阱:

地雷 1:把點擊事件綁在 img 上,結果點熱區完全沒反應(DOM 事件冒泡路徑誤判)

這是超過 80% 初學者第一次用 JavaScript 控制 Image Map 時會犯的錯誤。新手常以為熱區在圖片上,所以寫 document.querySelector('img').addEventListener('click', ...)。

讓我們用 Mermaid 序列圖看懂瀏覽器內部的 DOM 事件冒泡路徑(Event Bubbling Path):

地雷 1:把點擊事件綁在  上,結果點熱區完全沒反應(DOM 事件冒泡路徑誤判) 視覺化架構流程圖
圖:地雷 1:把點擊事件綁在 上,結果點熱區完全沒反應(DOM 事件冒泡路徑誤判) 視覺化架構流程圖
  • 解法:如上方實戰範例所示,請永遠將事件監聽器綁定在 <map> 父節點(利用事件委派)或是直接綁定在個別 <area> 節點上。

地雷 2:usemap 忘記加 # 號,或 map name 誤加 # 號導致綁定完全失效

根據 WHATWG HTML Living Standard (Image Maps),<img> 與 <map> 的配對語法有著嚴格的不對稱規定:

<!-- ❌ 錯誤寫法 1:usemap 漏寫 # 號(瀏覽器完全找不到 map) -->
<img src="map.png" usemap="taiwan" alt="台灣地圖">
<map name="taiwan">...</map>

<!-- ❌ 錯誤寫法 2:把 # 號連同寫進 <map name="#taiwan"> 裡 -->
<img src="map.png" usemap="#taiwan" alt="台灣地圖">
<map name="#taiwan">...</map>

<!-- 🟢 正確寫法:usemap 必加 # 號,map 的 name 與 id 不加 # 號 -->
<img src="map.png" usemap="#taiwan" alt="台灣地圖">
<map name="taiwan" id="taiwan">...</map>

此外請注意:usemap 屬性只能掛在 <img> 標籤上(現代 HTML5 規範已正式移除 <input type="image"> 與 <object> 的 usemap 支援;若使用響應式 <picture> 標籤,usemap 也必須寫在 <picture> 最內層的那個 <img> 上)。

地雷 3:企圖對 area 寫 CSS border 或 background-color 來顯示熱區範圍

許多工程師在除錯座標時,會想寫一行 CSS 把熱區框出來:

/* ❌ 完全無效:瀏覽器根本不會畫出紅色邊框或半透明背景 */
area {
  display: block;
  border: 2px solid red;
  background-color: rgba(255, 0, 0, 0.3);
}
  • 底層真相:在瀏覽器的 User Agent 預設樣式表中,<map> 與 <area> 預設為 display: none,它們在 CSS 視覺格式化模型(Visual Formatting Model)中不會產生任何標準的 CSS 矩形盒(Box),因此所有的 border、background、box-shadow 在 <area> 上統統無效!
  • 正確除錯與視覺化方法:
    1. 在瀏覽器中按 Tab 鍵 將鍵盤焦點移至 <area> 上,瀏覽器原生會沿著 coords 描繪出對應形狀的 Focus Outline(焦點外框)。
    2. 若需要在畫面上做出永久的高亮色塊或 Hover 變色效果,現代前端實務會改用 內嵌 <svg> 向量圖層(<path> / <polygon>)疊加在圖片上方,或是使用 <canvas> 繪製。

地雷 4:忽略 area 的 alt 與 href 屬性,導致鍵盤與螢幕閱讀器完全失明

在無障礙網頁(WCAG 2.1 Level A 規範)中,每一個可點擊的 <area> 在無障礙樹(Accessibility Tree)中都等同於一個超連結(role="link"):

  1. 沒有 href 的 <area> 是死節點:如果 <area> 漏寫 href 屬性,它就不會進入鍵盤 Tab 序列(無法被聚焦),螢幕閱讀器也會直接忽略它。
  2. 沒有 alt 的 <area> 違反 WCAG 1.1.1:視障者按 Tab 跳到該熱區時,螢幕閱讀器完全仰賴 <area alt="台北分店資訊"> 來播報該區域的名稱;若漏寫 alt,使用者只會聽到冰冷的網址或「連結」。

五、 上線前必查:HTML map 影像地圖 5 點檢驗清單

在將包含 <map> 影像地圖的頁面部署上線前,請逐一核對以下 5 項檢查點:

  • ☐ 1. usemap 與 name 配對語法正確:<img> 的 usemap="#名稱" 是否確認以 # 開頭?<map> 的 name="名稱" 與 id="名稱" 是否確認一致且不含 # 號與空白字元?
  • ☐ 2. DOM 事件綁定於 <map> 或 <area>:所有的 JavaScript click / mouseenter 事件是否都正確綁定在 <map>(事件委派)或 <area> 節點上,而非誤綁在不會收到熱區冒泡的 <img> 上?
  • ☐ 3. 每個 <area> 皆具備 href 與精確 alt:所有的 <area> 是否都帶有有效的 href 與描述該熱區功能的 alt 替代文字,確保鍵盤 Tab 鍵能順利聚焦並顯示焦點框?
  • ☐ 4. 確認原始圖片尺寸與座標基準一致:<img> 的 width 與 height 是否與計算 coords 時所使用的基準尺寸一致?(若頁面支援 RWD 圖片縮放,是否已搭配 JS ResizeObserver 動態按比例重算 coords,或改用 <svg viewBox> 方案?)
  • ☐ 5. <area> 宣告順序符合重疊優先權:當兩個 <area> 熱區座標發生部分重疊時,HTML 解析器遵循「先寫先贏(First-Match Wins)」原則——排在 DOM 樹較前面的 <area> 會優先攔截點擊,請確認小範圍熱區是否放在大範圍背景熱區之前?

六、 總結與延伸學習

掌握 HTML <map> 與 <area> 標籤,不僅能讓你在處理複雜圖像導覽時免去切圖拼湊的痛苦,更能讓你深刻體會瀏覽器 DOM 樹中「視覺宿主(<img>)」與「邏輯互動節點(<map> > <area>)」分離映射的精妙設計。

當你搞懂了 <map> 的基礎結構與 DOM 事件流向後,接下來在實務開發中還會遇到三個進階挑戰(我們將在後續系列文章中逐一拆解):

  1. 座標計算實戰:如何快速精準計算 rect、circle、poly 的 coords 像素座標而不翻車?
  2. RWD 響應式縮放:當圖片隨手機螢幕縮小 50% 時,如何讓寫死的 <area coords> 自動等比例跟隨縮放?
  3. 無障礙深度優化:如何處理複雜影像地圖的鍵盤導覽順序與螢幕閱讀器體驗?

七、 參考資料與權威規範延伸閱讀

發表迴響