事件處理(Event Handling)#

事件(Event)是使用者或瀏覽器觸發的動作,例如點擊按鈕、輸入文字、頁面載入完成等。 JavaScript 透過 addEventListener 監聽這些事件,讓網頁能夠即時回應互動行為。


事件監聽#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const btn = document.querySelector("#btn");

// addEventListener(推薦)
btn.addEventListener("click", function(event) {
    console.log("按鈕被點擊了!");
    console.log(event); // Event 物件
});

// 箭頭函式版本
btn.addEventListener("click", (e) => {
    console.log("點擊座標:", e.clientX, e.clientY);
});

// 移除事件監聽(需傳入同一個函式參考)
function handleClick() {
    console.log("點擊");
}
btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick);

常用事件類型#

滑鼠事件#

1
2
3
4
5
6
el.addEventListener("click", e => {});       // 點擊
el.addEventListener("dblclick", e => {});    // 雙擊
el.addEventListener("mouseenter", e => {}); // 滑鼠進入
el.addEventListener("mouseleave", e => {}); // 滑鼠離開
el.addEventListener("mousemove", e => {});  // 滑鼠移動
el.addEventListener("contextmenu", e => {}); // 右鍵選單

鍵盤事件#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
document.addEventListener("keydown", e => {
    console.log(e.key);    // 按鍵名稱,如 "Enter"、"a"
    console.log(e.code);   // 實體按鍵,如 "KeyA"、"Enter"
    console.log(e.ctrlKey); // 是否按住 Ctrl

    if (e.key === "Enter") {
        console.log("按下 Enter");
    }
    if (e.ctrlKey && e.key === "s") {
        e.preventDefault(); // 阻止預設行為
        console.log("儲存");
    }
});

document.addEventListener("keyup", e => {});

表單事件#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const form = document.querySelector("form");
const input = document.querySelector("input");

// 表單提交
form.addEventListener("submit", e => {
    e.preventDefault(); // 阻止頁面重整
    console.log("表單提交:", input.value);
});

// 輸入框變化
input.addEventListener("input", e => {
    console.log("即時輸入:", e.target.value);
});

input.addEventListener("change", e => {
    console.log("值改變(失焦後):", e.target.value);
});

input.addEventListener("focus", e => {
    console.log("獲得焦點");
});

input.addEventListener("blur", e => {
    console.log("失去焦點");
});

視窗事件#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
window.addEventListener("load", () => {
    console.log("所有資源載入完成");
});

document.addEventListener("DOMContentLoaded", () => {
    console.log("DOM 解析完成(不等圖片)");
});

window.addEventListener("resize", () => {
    console.log("視窗大小:", window.innerWidth, window.innerHeight);
});

window.addEventListener("scroll", () => {
    console.log("捲動位置:", window.scrollY);
});

Event 物件#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
document.addEventListener("click", e => {
    e.target;          // 觸發事件的元素
    e.currentTarget;   // 綁定事件的元素(可能不同)
    e.type;            // 事件類型,如 "click"
    e.clientX;         // 相對視窗的 X 座標
    e.clientY;         // 相對視窗的 Y 座標
    e.pageX;           // 相對整個頁面的 X 座標

    e.preventDefault();  // 阻止預設行為(如連結跳轉)
    e.stopPropagation(); // 阻止事件冒泡
});

事件冒泡(Event Bubbling)#

事件從觸發元素向上傳播到父元素:

1
2
3
4
5
<div id="outer">
  <div id="inner">
    <button id="btn">點擊</button>
  </div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
document.querySelector("#btn").addEventListener("click", e => {
    console.log("button"); // 1. 先觸發
});
document.querySelector("#inner").addEventListener("click", e => {
    console.log("inner"); // 2. 再觸發
});
document.querySelector("#outer").addEventListener("click", e => {
    console.log("outer"); // 3. 最後觸發
});

// 點擊按鈕輸出:button → inner → outer

事件委派(Event Delegation)#

利用冒泡機制,在父元素統一監聽子元素事件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 不好的做法:對每個 li 個別綁定
document.querySelectorAll("li").forEach(li => {
    li.addEventListener("click", e => {
        li.classList.toggle("selected");
    });
});

// 好的做法:事件委派到 ul(動態新增的 li 也會生效)
const list = document.querySelector("ul");
list.addEventListener("click", e => {
    if (e.target.tagName === "LI") {
        e.target.classList.toggle("selected");
    }
});

只觸發一次#

1
2
3
btn.addEventListener("click", () => {
    console.log("只觸發一次");
}, { once: true });

實際範例:標籤頁切換#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<div class="tabs">
  <button class="tab-btn active" data-tab="tab1">標籤一</button>
  <button class="tab-btn" data-tab="tab2">標籤二</button>
  <button class="tab-btn" data-tab="tab3">標籤三</button>
</div>
<div id="tab1" class="tab-content">內容一</div>
<div id="tab2" class="tab-content" hidden>內容二</div>
<div id="tab3" class="tab-content" hidden>內容三</div>

<script>
const tabBtns = document.querySelectorAll(".tab-btn");
const tabContents = document.querySelectorAll(".tab-content");

tabBtns.forEach(btn => {
    btn.addEventListener("click", () => {
        // 移除所有 active
        tabBtns.forEach(b => b.classList.remove("active"));
        tabContents.forEach(c => c.hidden = true);

        // 啟用目前的
        btn.classList.add("active");
        document.querySelector(`#${btn.dataset.tab}`).hidden = false;
    });
});
</script>

Reference#