DOM Manipulation & Event
DOM (Document Object Model) เป็นมาตรฐานของ W3C ที่แทนเอกสาร HTML/XML เป็นโครงสร้าง ต้นไม้ของ object ที่โปรแกรมเข้าถึงและแก้ไขได้
DOM คืออะไร
- โครงสร้างแบบ Tree Structure
- เป็นตัวแทน user interface ที่เป็นกลางตามมาตรฐาน
- ให้นักพัฒนาเข้าถึงและเปลี่ยน เนื้อหา โครงสร้าง และสไตล์ ของเอกสาร HTML
- ปกติใช้ JavaScript ในการจัดการ DOM
<h1 id="title">Hello, World!</h1>
<button id="changeTitle">Change Title</button>
<script>
var titleElement = document.getElementById('title');
titleElement.textContent = 'Hello, DOM!';
var button = document.getElementById('changeTitle');
button.addEventListener('click', function() {
titleElement.textContent = 'Title Changed!';
});
</script>
เลือก element 5 วิธี
| เมธอด | เลือกอะไร | คืนอะไร |
|---|---|---|
getElementById('header') | element ที่มี id ตรงกัน | 1 element |
getElementsByClassName('text') | ทุก element ที่มี class นั้น | collection — ต้องใส่ index เช่น [0] |
getElementsByTagName('div') | ทุก element ของ tag นั้น | collection |
querySelector('.text') | ตัวแรกที่ตรงกับ CSS selector | 1 element |
querySelectorAll('.text') | ทุกตัวที่ตรงกับ CSS selector | NodeList |
var paragraphs = document.getElementsByClassName('text');
console.log(paragraphs[0]); // ต้องระบุ index ถึงจะได้ element เดียว
var first = document.querySelector('.text'); // ได้ตัวแรกเลย
var all = document.querySelectorAll('.text'); // ได้ทั้งหมด
var parent = child.parentElement; // เดินขึ้นไปหา parent
สังเกตชื่อ:
getElementsBy... มี s = ได้หลายตัว ต้องใส่ index
ส่วน getElementById ไม่มี s = ได้ตัวเดียวinnerHTML / innerText / textContent
| innerHTML | innerText | textContent | |
|---|---|---|---|
| ตอน set | สร้าง element จริงจากแท็กที่ใส่ | แสดงแท็กเป็นข้อความธรรมดา | แสดงแท็กเป็นข้อความธรรมดา |
| ตอน get | ได้ HTML ทั้งก้อนรวมแท็ก | ได้เฉพาะข้อความที่มองเห็น (สนใจการขึ้นบรรทัด) | ได้ข้อความทั้งหมดรวมที่ซ่อนอยู่ |
| ความปลอดภัย | เสี่ยง XSS ถ้าใส่ข้อมูลจากผู้ใช้ | ปลอดภัยกว่า | ปลอดภัยกว่า |
let text = "<div style='background-color:red;'>Hello Dolly</div>";
document.getElementById("myP").innerHTML = text; // ได้กล่องสีแดงจริง ๆ
document.getElementById("myP").textContent = text; // เห็นเป็นตัวอักษร <div ...> ทั้งบรรทัด
อย่าเอาข้อมูลจากผู้ใช้ยัดใส่
innerHTML ตรง ๆ เพราะสคริปต์ของผู้โจมตีจะถูกสร้างขึ้นมาจริง (XSS)attribute & class
// attribute image.setAttribute('src', 'new-image.jpg'); console.log(image.getAttribute('src')); // new-image.jpg button.removeAttribute('disabled'); console.log(button.hasAttribute('disabled')); // false element.href = "https://www.w3schools.com"; // property บางตัวตั้งตรง ๆ ได้ // class div.classList.add('newClass'); div.classList.remove('oldClass'); console.log(div.className);
สร้าง / ลบ / โคลน element
// สร้างและเพิ่ม var newDiv = document.createElement('div'); newDiv.classList.add('my-class'); newDiv.id = 'myDiv'; newDiv.textContent = 'Hello, World!'; document.body.appendChild(newDiv); // ต่อท้ายสุด document.body.prepend(newDiv); // แทรกหน้าสุด // ลบ newDiv.remove(); // ลบตัวเอง parentElement.removeChild(childElement);// ให้ parent ลบลูก // โคลน document.querySelectorAll("div[data-items='posts']")[0].cloneNode(true); // true = ได้ลูกทุกตัวมาด้วย // false = ได้เฉพาะตัวมันเอง
ผูก event 3 วิธี
<!-- 1. เขียนใน attribute ของ HTML ตรง ๆ --> <button onclick="alert('Button clicked!')">Click Me</button> // 2. ใช้ DOM property button.onclick = function() { alert('Button clicked!'); }; // 3. ใช้ addEventListener ← วิธีที่แนะนำ button.addEventListener('click', function() { alert('Button clicked!'); });
ทำไม addEventListener ดีที่สุด: ผูก handler ได้หลายตัวกับ event เดียวกัน
ส่วน
onclick ตั้งใหม่ทับของเดิมเสมอ และยังกำหนด capturing/bubbling ได้event ที่ต้องรู้จัก
| กลุ่ม | event |
|---|---|
| Mouse | click, mouseover, mouseout |
| Keyboard | keydown, keyup |
| Form | submit, change, input |
| Focus | focus (ได้โฟกัส), blur (เสียโฟกัส) |
| Animation | animationstart, animationiteration, animationend |
div.addEventListener('mouseover', function() { div.style.backgroundColor = 'red'; });
div.addEventListener('mouseout', function() { div.style.backgroundColor = 'blue'; });
input.addEventListener('keydown', function(event) { console.log('Key down: ' + event.key); });
input.addEventListener('keyup', function(event) { console.log('Key up: ' + event.key); });
Bubbling vs Capturing
ผูก event ไว้หลายชั้น = event ตัวเดียวจะวิ่งผ่านทุกชั้น
// 1. Bubbling (ค่าปริยาย) — พารามิเตอร์ที่ 3 เป็น false หรือไม่ใส่ button.addEventListener('click', function(){ console.log('Button clicked!'); }, false); document.body.addEventListener('click', function(){ console.log('Body clicked!'); }); // ผลลัพธ์: Button clicked! → Body clicked! // 2. Capturing — ใส่ true button.addEventListener('click', function(){ console.log('Button clicked!'); }, true); document.body.addEventListener('click', function(){ console.log('Body clicked!'); }, true);
กับดักข้อสอบ: ถ้าสลับลำดับการเขียนโค้ดผูก event ผลลัพธ์ไม่เปลี่ยน —
ลำดับขึ้นกับโครงสร้าง DOM และเฟส (capturing/bubbling) ไม่ใช่ลำดับบรรทัดโค้ด
target vs currentTarget
body.addEventListener('click', function(event) {
console.log('Target:', event.target); // element ที่ถูกคลิกจริง เช่น button
console.log('Current Target:', event.currentTarget); // element ที่ผูก handler ไว้ เช่น body
});
ความต่างนี้คือหัวใจของ Event Delegation — ผูก listener ที่ parent ตัวเดียว
แล้วใช้
event.target ดูว่าลูกตัวไหนถูกคลิก ประหยัดหน่วยความจำเมื่อมีปุ่มจำนวนมากstopPropagation
button.addEventListener('click', function(event) {
event.stopPropagation(); // หยุดไม่ให้ event วิ่งต่อไปชั้นบน
console.log('Button clicked!');
}, false);
document.body.addEventListener('click', function(event) {
console.log('Body clicked!'); // จะไม่ถูกเรียกอีกต่อไป
}, false);