Focus Events เกิดขึ้นเมื่อองค์ประกอบได้รับหรือสูญเสียโฟกัส (Focus) มี 4 เหตุการณ์หลัก ซึ่งแตกต่างกันที่ "การกระจาย" (Propagation) และ "ลำดับการยิง"
ประเภท Focus Events
| เหตุการณ์ | คำอธิบาย | Bubbles |
|---|---|---|
| focus | เกิดเมื่อองค์ประกอบได้รับโฟกัส | ❌ ไม่ |
| blur | เกิดเมื่อองค์ประกอบสูญเสียโฟกัส | ❌ ไม่ |
| focusin | เกิดก่อน focus (เมื่อกำลังจะได้รับโฟกัส) | ✅ ใช่ |
| focusout | เกิดก่อน blur (เมื่อกำลังจะสูญเสียโฟกัส) | ✅ ใช่ |
ลำดับการยิง (Event Order)
// เมื่อคลิกจาก Input A ไป Input B:
1. focusout (บน A) - Bubbles
2. blur (บน A) - No bubble
3. focusin (บน B) - Bubbles
4. focus (บน B) - No bubble
ตัวอย่าง: เปลี่ยนสีพื้นหลังเมื่อ Focus
<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Focus Events</title>
<style>
body { font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; padding: 20px; }
input { padding: 10px; margin: 10px 0; display: block; width: 300px; font-size: 16px; }
.info { background: #e7f3ff; padding: 15px; border-radius: 4px; margin: 10px 0; }
</style>
</head>
<body>
<h1>Focus Events ใน JavaScript</h1>
<form id="form">
<input type="text" placeholder="Username" autocomplete="username">
<input class="passW" type="password" placeholder="Password" autocomplete="current-password">
</form>
<div class="info">ลองคลิกที่ช่อง Password เพื่อดู focus event ทำงาน</div>
<script>
let passEle = document.querySelector(".passW");
// focus - ไม่ Bubble
passEle.addEventListener("focus", () => {
passEle.style.backgroundColor = "#90EE90"; // เขียวอ่อน
console.log("focus event fired");
});
// blur - ไม่ Bubble
passEle.addEventListener("blur", () => {
passEle.style.backgroundColor = ""; // คืนค่าเดิม
console.log("blur event fired");
});
// focusin - Bubble (จับได้ที่ Parent)
document.getElementById("form").addEventListener("focusin", (e) => {
console.log("focusin on:", e.target.tagName, e.target.type);
}, true); // ใช้ Capture phase เพราะ focusin ไม่ bubble ปกติในบาง Browser
// focusout - Bubble
document.getElementById("form").addEventListener("focusout", (e) => {
console.log("focusout from:", e.target.tagName, e.target.type);
});
</script>
</body>
</html>
ผลลัพธ์
คลิกช่อง Password → พื้นหลังสีเขียวอ่อน (focus)
คลิกนอกพื้นที่/Tab ไปช่องอื่น → พื้นหลังคืนค่าเดิม (blur)
การใช้งานจริง (Use Cases)
1. Validation แบบ Real-time (on blur)
emailInput.addEventListener("blur", () => {
if (emailInput.value && !isValidEmail(emailInput.value)) {
showError(emailInput, "รูปแบบอีเมลไม่ถูกต้อง");
} else {
clearError(emailInput);
}
});
2. Floating Label Pattern
input.addEventListener("focus", () => label.classList.add("active"));
input.addEventListener("blur", () => {
if (!input.value) label.classList.remove("active");
});
3. Keyboard Navigation (Focus Management)
// กด Escape ปิด Modal และคืน Focus กลับ Trigger Button
modal.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
closeModal();
triggerButton.focus(); // คืน Focus
}
});
// Trap Focus ภายใน Modal
modal.addEventListener("focusin", (e) => {
if (!modal.contains(e.target)) {
firstFocusableElement.focus();
}
});
4. ใช้ focusin/focusout สำหรับ Event Delegation
// จับ Focus ทุก Input ใน Form ได้ที่ Form Element
form.addEventListener("focusin", (e) => {
if (e.target.matches("input, textarea, select")) {
e.target.style.borderColor = "#667eea";
}
});
form.addEventListener("focusout", (e) => {
if (e.target.matches("input, textarea, select")) {
e.target.style.borderColor = "#ccc";
}
});
Focus และ Accessibility
- ใช้
tabindexควบคุมลำดับ Tab Order - อย่าเอา Outline เริ่มต้นออก (
outline: none) ถ้าไม่มี Style 替代 - ใช้
:focus-visibleCSS สำหรับแสดง Focus เฉพาะ Keyboard Navigation autofocusattribute - โฟกัสอัตโนมัติตอนโหลด (ใช้แค่ 1 ตัวต่อหน้า)