preventDefault
และ stopPropagation
: กรณีที่ควรใช้แต่ละวิธีและแต่ละวิธีทํางานอย่างไร
Event.stopPropagation() และ Event.preventDefault()
การจัดการเหตุการณ์ JavaScript มักจะตรงไปตรงมา โดยเฉพาะอย่างยิ่งเมื่อจัดการกับโครงสร้าง HTML ที่เรียบง่าย (ค่อนข้างแบน) อย่างไรก็ตาม สถานการณ์จะซับซ้อนขึ้นเมื่อเหตุการณ์เดินทางไป (หรือแพร่กระจาย) ผ่านลําดับชั้นขององค์ประกอบ ซึ่งมักเกิดขึ้นเมื่อนักพัฒนาแอปติดต่อขอ stopPropagation()
และ/หรือ preventDefault()
เพื่อแก้ปัญหาที่พบ หากคุณเคยคิดว่า "ฉันจะลองใช้ preventDefault()
แล้วหากไม่ได้ผล ฉันจะลองใช้ stopPropagation()
แล้วหากไม่ได้ผล ฉันจะลองใช้ทั้ง 2 อย่าง" บทความนี้เหมาะกับคุณ เราจะอธิบายว่าแต่ละวิธีทํางานอย่างไร กรณีใดควรใช้วิธีใด และแสดงตัวอย่างการใช้งานที่หลากหลายให้คุณได้ดู เป้าหมายของเราคือการขจัดความสับสนของคุณให้หมดไป
แต่ก่อนที่จะเจาะลึกลงไป เราขออธิบายสั้นๆ เกี่ยวกับการจัดการเหตุการณ์ 2 ประเภทที่เป็นไปได้ใน JavaScript (ในเบราว์เซอร์สมัยใหม่ทั้งหมด เนื่องจาก Internet Explorer ก่อนเวอร์ชัน 9 ไม่รองรับการจับเหตุการณ์เลย)
รูปแบบเหตุการณ์ (การบันทึกและการรวม)
เบราว์เซอร์สมัยใหม่ทั้งหมดรองรับการบันทึกเหตุการณ์ แต่นักพัฒนาแอปไม่ค่อยได้ใช้
ที่น่าสนใจคือรูปแบบนี้คือรูปแบบเดียวที่ Netscape รองรับในตอนแรก คู่แข่งรายใหญ่ที่สุดของ Netscape อย่าง Microsoft Internet Explorer นั้นไม่รองรับการบันทึกเหตุการณ์เลย แต่รองรับเหตุการณ์อีกรูปแบบหนึ่งที่เรียกว่าการทําให้เหตุการณ์ปรากฏ เมื่อก่อตั้ง W3C ขึ้น ทางกลุ่มได้พบข้อดีของทั้ง 2 รูปแบบของเหตุการณ์ และประกาศว่าเบราว์เซอร์ควรรองรับทั้ง 2 รูปแบบผ่านพารามิเตอร์ที่ 3 ของเมธอด addEventListener
เดิมพารามิเตอร์ดังกล่าวเป็นเพียงบูลีน แต่เบราว์เซอร์สมัยใหม่ทั้งหมดรองรับออบเจ็กต์ options
เป็นพารามิเตอร์ที่ 3 ซึ่งคุณใช้เพื่อระบุ (นอกเหนือจากสิ่งอื่นๆ) ว่าต้องการใช้การบันทึกเหตุการณ์หรือไม่
someElement.addEventListener('click', myClickHandler, { capture: true | false });
โปรดทราบว่าออบเจ็กต์ options
เป็นสิ่งที่ไม่บังคับเช่นเดียวกับพร็อพเพอร์ตี้ capture
ของออบเจ็กต์ หากละเว้นค่าใดค่าหนึ่ง ค่าเริ่มต้นของ capture
คือ false
ซึ่งหมายความว่าระบบจะใช้การทําให้เหตุการณ์ปรากฏขึ้น
การบันทึกเหตุการณ์
"การฟังในระยะจับภาพ" ของเครื่องจัดการเหตุการณ์หมายความว่าอย่างไร ในการทำความเข้าใจเรื่องนี้ เราจําเป็นต้องทราบแหล่งที่มาของเหตุการณ์และเส้นทางที่เหตุการณ์เดินทาง เหตุการณ์ต่อไปนี้เป็นเหตุการณ์ทั้งหมด แม้ว่าในฐานะนักพัฒนาแอป คุณจะไม่ได้ใช้ประโยชน์ สนใจ หรือคิดถึงเหตุการณ์เหล่านั้นก็ตาม
เหตุการณ์ทั้งหมดเริ่มต้นที่กรอบเวลาและผ่านขั้นตอนการบันทึกก่อน ซึ่งหมายความว่าเมื่อมีการเรียกเหตุการณ์ ระบบจะเริ่มกรอบเวลาและ "เลื่อนลง" ไปยังองค์ประกอบเป้าหมายก่อน ซึ่งกรณีนี้จะเกิดขึ้นแม้ว่าคุณจะฟังเพลงในช่วงที่กำลังมาแรงเท่านั้น ลองดูตัวอย่างมาร์กอัปและ JavaScript ต่อไปนี้
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('#C was clicked');
},
true,
);
เมื่อผู้ใช้คลิกองค์ประกอบ #C
ระบบจะส่งเหตุการณ์ที่มาจาก window
จากนั้นเหตุการณ์นี้จะนำไปใช้กับรายการที่สืบทอดมาดังนี้
window
=> document
=> <html>
=> <body>
=> และอื่นๆ จนกว่าจะถึงเป้าหมาย
ไม่ว่าจะไม่มีการตรวจหาเหตุการณ์การคลิกที่องค์ประกอบ window
หรือ document
หรือ
<html>
หรือองค์ประกอบ <body>
(หรือองค์ประกอบอื่นๆ บนเส้นทางไปยังเป้าหมาย) เหตุการณ์ยังคงเกิดขึ้นที่ window
และเริ่มเส้นทางตามที่อธิบายไป
ในตัวอย่างนี้ เหตุการณ์คลิกจะนำไปใช้ (คำนี้สำคัญเนื่องจากจะเชื่อมโยงโดยตรงกับวิธีการทำงานของเมธอด stopPropagation()
และจะมีการอธิบายในเอกสารนี้ในภายหลัง)จาก window
ไปยังองค์ประกอบเป้าหมาย (ในกรณีนี้คือ #C
) ผ่านองค์ประกอบทุกรายการระหว่าง window
กับ #C
ซึ่งหมายความว่าเหตุการณ์คลิกจะเริ่มที่ window
และเบราว์เซอร์จะถามคำถามต่อไปนี้
"มีสิ่งใดเฝ้าติดตามเหตุการณ์คลิกใน window
ในระยะการบันทึกไหม" หากเป็นเช่นนั้น ตัวแฮนเดิลเหตุการณ์ที่เหมาะสมจะทำงาน ในตัวอย่างของเราไม่มีการดำเนินการใดๆ เกิดขึ้น จึงไม่มีแฮนเดิลใดทำงาน
จากนั้นเหตุการณ์จะนำไปใช้กับ document
และเบราว์เซอร์จะถามว่า "มีสิ่งใดรับฟังเหตุการณ์คลิกใน document
ในระยะการบันทึกไหม" หากเป็นเช่นนั้น ตัวแฮนเดิลเหตุการณ์ที่เหมาะสมจะเริ่มต้นทํางาน
จากนั้นเหตุการณ์จะนำไปใช้กับองค์ประกอบ <html>
และเบราว์เซอร์จะถามว่า "มีสิ่งใดคอยฟังการคลิกองค์ประกอบ <html>
ในเฟสการจับภาพไหม" หากเป็นเช่นนั้น ตัวแฮนเดิลเหตุการณ์ที่เหมาะสมจะทำงาน
จากนั้นเหตุการณ์จะนำไปใช้กับองค์ประกอบ <body>
และเบราว์เซอร์จะถามว่า "มีสิ่งใดรับฟังเหตุการณ์การคลิกในองค์ประกอบ <body>
ในระยะการบันทึกไหม" หากเป็นเช่นนั้น ตัวแฮนเดิลเหตุการณ์ที่เหมาะสมจะทำงาน
จากนั้นเหตุการณ์จะนำไปใช้กับองค์ประกอบ #A
อีกครั้ง เบราว์เซอร์จะถามว่า "มีสิ่งใดรับฟังเหตุการณ์คลิกใน #A
ในระยะการจับภาพหรือไม่ และหากมี ตัวแฮนเดิลเหตุการณ์ที่เหมาะสมจะทำงานหรือไม่
จากนั้นเหตุการณ์จะนำไปใช้กับองค์ประกอบ #B
(และระบบจะถามคำถามเดียวกัน)
สุดท้าย เหตุการณ์จะไปถึงเป้าหมายและเบราว์เซอร์จะถามว่า "มีสิ่งใดคอยฟังเหตุการณ์การคลิกในองค์ประกอบ #C
ในระยะการบันทึกไหม" คำตอบสำหรับครั้งนี้คือ "ใช่" ช่วงเวลาสั้นๆ นี้เมื่อเหตุการณ์อยู่ที่เป้าหมายเรียกว่า "ระยะเป้าหมาย" เมื่อถึงจุดนี้ ตัวแฮนเดิลเหตุการณ์จะทำงาน เบราว์เซอร์จะ console.log "มีการคลิก #C" แล้วเราก็เสร็จแล้ว ถูกต้องไหม
ไม่ถูกต้อง เรายังไม่จบ กระบวนการจะยังคงดำเนินต่อไป แต่ตอนนี้จะเปลี่ยนเป็นระยะการปะทุ
การทําให้เหตุการณ์ปรากฏ
เบราว์เซอร์จะถามคำถามต่อไปนี้
"มีสิ่งใดเฝ้าติดตามเหตุการณ์การคลิกใน #C
ในระยะการบับเบิลไหม" โปรดอ่านข้อมูลต่อไปนี้อย่างละเอียด
คุณสามารถเฝ้าติดตามการคลิก (หรือเหตุการณ์ประเภทใดก็ได้) ทั้งในระยะการบันทึกและระยะการบับเบิล และหากคุณได้ต่อสายให้กับตัวแฮนเดิลเหตุการณ์ทั้ง 2 ช่วง (เช่น โดยการเรียกใช้ .addEventListener()
2 ครั้ง โดย 1 ครั้งใช้ capture = true
และอีก 1 ครั้งใช้ capture = false
) ตัวแฮนเดิลเหตุการณ์ทั้ง 2 รายการจะทํางานสําหรับองค์ประกอบเดียวกันอย่างแน่นอน แต่สิ่งสำคัญที่ควรทราบคือ เหตุการณ์จะทํางานในเฟสที่แตกต่างกัน (เหตุการณ์หนึ่งจะทํางานในเฟสการบันทึก และอีกเหตุการณ์หนึ่งจะทํางานในเฟสการรวม)
ถัดไป เหตุการณ์จะแผ่กระจาย (หรือที่เรียกกันโดยทั่วไปว่า "ทําให้เกิดเหตุการณ์ต่อเนื่อง" เนื่องจากดูเหมือนว่าเหตุการณ์จะ "ขึ้น" ไปตามลําดับชั้น DOM) ไปยังองค์ประกอบหลัก #B
และเบราว์เซอร์จะถามว่า "มีสิ่งใดรับฟังเหตุการณ์การคลิกใน #B
ในระยะการทําให้เกิดเหตุการณ์ต่อเนื่องไหม" ในตัวอย่างของเรา ไม่มีเงื่อนไขใดๆ อยู่เลย ดังนั้นจึงไม่มีตัวแฮนเดิลใดทำงาน
ถัดไป เหตุการณ์จะส่งไปยัง #A
และเบราว์เซอร์จะถามว่า "มีสิ่งใดกำลังรอฟังเหตุการณ์การคลิกใน #A
ในระยะการบับเบิลไหม"
ถัดไป เหตุการณ์จะส่งไปยัง <body>
: "มีสิ่งใดกำลังรอเหตุการณ์คลิกบนองค์ประกอบ <body>
ในเฟสการบับเบิลอยู่ไหม"
ถัดไปคือองค์ประกอบ <html>
: "มีองค์ประกอบใดรับฟังเหตุการณ์คลิกในองค์ประกอบ <html>
ในระยะการบับเบิลไหม
ถัดไปคือ document
: "มีสิ่งใดกำลังรอเหตุการณ์คลิกใน document
ในระยะการทํางานแบบ Bubbling ไหม"
สุดท้ายคือ window
: "มีสิ่งใดกำลังรอเหตุการณ์คลิกในหน้าต่างในระยะการทํางานแบบ Bubbling หรือไม่"
ในที่สุด เส้นทางนี้ยาวนานมาก และตอนนี้กิจกรรมของเราอาจเหนื่อยมากแล้ว แต่เชื่อหรือไม่ว่า นี่เป็นเส้นทางที่ทุกกิจกรรมต้องผ่าน ส่วนใหญ่แล้ว เหตุการณ์นี้จะไม่ได้รับการสังเกตเนื่องจากนักพัฒนาแอปมักสนใจเฉพาะระยะของเหตุการณ์ใดเหตุการณ์หนึ่ง (และมักจะเป็นระยะการเพิ่มขึ้น)
คุณควรลองใช้การบันทึกเหตุการณ์และการทําให้เหตุการณ์ทวีความรุนแรง รวมถึงการบันทึกหมายเหตุบางอย่างลงในคอนโซลเมื่อแฮนเดิลเริ่มทํางาน การดูเส้นทางที่เหตุการณ์เกิดขึ้นจะมีประโยชน์อย่างยิ่ง ต่อไปนี้เป็นตัวอย่างที่คอยฟังองค์ประกอบทุกรายการในทั้ง 2 ช่วง
<html>
<body>
<div id="A">
<div id="B">
<div id="C"></div>
</div>
</div>
</body>
</html>
document.addEventListener(
'click',
function (e) {
console.log('click on document in capturing phase');
},
true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in capturing phase');
},
true,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in capturing phase');
},
true,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in capturing phase');
},
true,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in capturing phase');
},
true,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in capturing phase');
},
true,
);
document.addEventListener(
'click',
function (e) {
console.log('click on document in bubbling phase');
},
false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
'click',
function (e) {
console.log('click on <html> in bubbling phase');
},
false,
);
document.body.addEventListener(
'click',
function (e) {
console.log('click on <body> in bubbling phase');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('click on #A in bubbling phase');
},
false,
);
document.getElementById('B').addEventListener(
'click',
function (e) {
console.log('click on #B in bubbling phase');
},
false,
);
document.getElementById('C').addEventListener(
'click',
function (e) {
console.log('click on #C in bubbling phase');
},
false,
);
เอาต์พุตของคอนโซลจะขึ้นอยู่กับองค์ประกอบที่คุณคลิก หากคุณคลิกองค์ประกอบ "ลึกที่สุด" ในต้นไม้ DOM (องค์ประกอบ #C
) คุณจะเห็นตัวจัดการเหตุการณ์เหล่านี้ทั้งหมดทำงาน องค์ประกอบ #C
ของคอนโซลเอาต์พุต (พร้อมภาพหน้าจอด้วย) มีการจัดรูปแบบ CSS เล็กน้อยเพื่อให้เห็นองค์ประกอบต่างๆ ชัดเจนขึ้น
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"
คุณสามารถลองใช้ฟีเจอร์นี้แบบอินเทอร์แอกทีฟได้ในวิดีโอสาธิตเวอร์ชันที่ใช้จริงด้านล่าง คลิกองค์ประกอบ #C
แล้วดูเอาต์พุตของคอนโซล
event.stopPropagation()
เมื่อทราบแหล่งที่มาของเหตุการณ์และวิธีที่เหตุการณ์เดินทาง (นั่นคือ แพร่กระจาย) ผ่าน DOM ทั้งในระยะการบันทึกและระยะการทําให้เกิดเหตุการณ์แล้ว ตอนนี้เรามาสนใจevent.stopPropagation()
กัน
เรียกใช้เมธอด stopPropagation()
ได้ในเหตุการณ์ DOM เดิม (ส่วนใหญ่) เราใช้คำว่า "ส่วนใหญ่" เนื่องจากมีบางรายการที่การเรียกใช้เมธอดนี้จะไม่ทําอะไรเลย (เนื่องจากเหตุการณ์ไม่แผ่กระจายตั้งแต่แรก) เหตุการณ์อย่าง focus
, blur
, load
, scroll
และอื่นๆ อีก 2-3 รายการจัดอยู่ในหมวดหมู่นี้ คุณสามารถเรียกใช้ stopPropagation()
ได้ แต่ไม่มีอะไรที่น่าสนใจเกิดขึ้น เนื่องจากเหตุการณ์เหล่านี้ไม่ทํางาน
แต่ stopPropagation
ทำอะไรได้บ้าง
เครื่องมือนี้ทำงานได้เกือบจะตรงกับที่ระบุไว้ เมื่อคุณเรียกใช้ เหตุการณ์จะหยุดการนำไปยังองค์ประกอบอื่นๆ ทั้งหมดที่ควรจะนำไป ซึ่งมีผลกับทั้งทิศทาง (การบันทึกและการแจ้งเตือน) ดังนั้น หากคุณเรียกใช้ stopPropagation()
ในทุกที่ของระยะการบันทึก เหตุการณ์จะไม่เข้าสู่ระยะเป้าหมายหรือระยะการรวม หากคุณเรียกใช้ในเฟสการบับเบิล การดำเนินการจะผ่านเฟสการบันทึกไปแล้ว แต่จะหยุด "บับเบิลขึ้น" จากจุดที่คุณเรียกใช้
กลับไปที่ตัวอย่างมาร์กอัปเดิม คุณคิดว่าจะเกิดอะไรขึ้นหากเราเรียกใช้ stopPropagation()
ในระยะการบันทึกที่องค์ประกอบ #B
ซึ่งจะให้ผลลัพธ์ดังต่อไปนี้
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
คุณสามารถลองใช้ฟีเจอร์นี้แบบอินเทอร์แอกทีฟได้ในวิดีโอสาธิตเวอร์ชันที่ใช้จริงด้านล่าง คลิกองค์ประกอบ #C
ในการสาธิตแบบเรียลไทม์ แล้วดูเอาต์พุตคอนโซล
คุณจะหยุดการนำไปใช้งานที่ #A
ในเฟสการขยายการเผยแพร่ได้อย่างไร ซึ่งจะส่งผลให้มีเอาต์พุตดังต่อไปนี้
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
คุณสามารถลองใช้ฟีเจอร์นี้แบบอินเทอร์แอกทีฟได้ในวิดีโอสาธิตเวอร์ชันที่ใช้จริงด้านล่าง คลิกองค์ประกอบ #C
ในการสาธิตแบบเรียลไทม์ แล้วดูเอาต์พุตคอนโซล
อีกคำถามหนึ่งเพื่อความสนุก จะเกิดอะไรขึ้นหากเราเรียก stopPropagation()
ในเฟสเป้าหมายสําหรับ #C
โปรดทราบว่า "ระยะเป้าหมาย" คือชื่อของระยะเวลาที่เหตุการณ์อยู่เป้าหมาย ซึ่งจะให้ผลลัพธ์ดังต่อไปนี้
"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
โปรดทราบว่าเครื่องจัดการเหตุการณ์สําหรับ #C
ซึ่งเราบันทึก "คลิกที่ #C ในเฟสการบันทึก" จะยังคงทำงานอยู่ แต่เครื่องจัดการเหตุการณ์ที่เราบันทึก "คลิกที่ #C ในเฟสการทํางานร่วมกัน" จะไม่ทํางาน การดำเนินการนี้น่าจะเหมาะเจาะ เราเรียก stopPropagation()
จากรายการแรก ดังนั้นจุดที่การนำไปใช้งานของเหตุการณ์จะหยุดลงคือจุดนั้น
คุณสามารถลองใช้ฟีเจอร์นี้แบบอินเทอร์แอกทีฟได้ในวิดีโอสาธิตเวอร์ชันที่ใช้จริงด้านล่าง คลิกองค์ประกอบ #C
ในการสาธิตแบบเรียลไทม์ แล้วดูเอาต์พุตคอนโซล
เราขอแนะนำให้คุณลองใช้ฟีเจอร์ต่างๆ ในเวอร์ชันเดโมแบบเรียลไทม์เหล่านี้ ลองคลิกเฉพาะองค์ประกอบ #A
หรือเฉพาะองค์ประกอบ body
ลองคาดเดาสิ่งที่จะเกิดขึ้น แล้วสังเกตว่าคุณคิดถูกไหม เมื่อถึงจุดนี้ คุณควรจะคาดการณ์ได้อย่างแม่นยำ
event.stopImmediatePropagation()
วิธีการแปลกๆ นี้ไม่ค่อยได้ใช้กันใช่ไหม ซึ่งคล้ายกับ stopPropagation
แต่จะใช้ได้ก็ต่อเมื่อมีตัวแฮนเดิลเหตุการณ์มากกว่า 1 รายการที่เชื่อมต่อกับองค์ประกอบเดียวเท่านั้น แทนที่จะหยุดเหตุการณ์ไม่ให้ไปยังรายการที่สืบทอด (การจับ) หรือรายการหลัก (การทําให้ระบบแสดง) เนื่องจาก addEventListener()
รองรับรูปแบบมัลติแคสต์ของเหตุการณ์ คุณจึงเชื่อมต่อตัวแฮนเดิลเหตุการณ์กับองค์ประกอบเดียวได้มากกว่า 1 ครั้ง เมื่อเกิดกรณีนี้ขึ้น (ในเบราว์เซอร์ส่วนใหญ่) ระบบจะเรียกใช้ตัวแฮนเดิลเหตุการณ์ตามลำดับที่เชื่อมต่อไว้ การเรียก stopImmediatePropagation()
จะป้องกันไม่ให้ตัวแฮนเดิลที่ตามมาทํางาน ลองพิจารณาตัวอย่างต่อไปนี้
<html>
<body>
<div id="A">I am the #A element</div>
</body>
</html>
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run first!');
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I shall run second!');
e.stopImmediatePropagation();
},
false,
);
document.getElementById('A').addEventListener(
'click',
function (e) {
console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
},
false,
);
ตัวอย่างข้างต้นจะแสดงผลลัพธ์คอนโซลดังต่อไปนี้
"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"
โปรดทราบว่าตัวแฮนเดิลเหตุการณ์ที่ 3 จะไม่ทํางานเนื่องจากตัวแฮนเดิลเหตุการณ์ที่ 2 เรียกใช้ e.stopImmediatePropagation()
หากเราเรียกใช้ e.stopPropagation()
แทน ตัวแฮนเดิลที่ 3 จะยังคงทำงานอยู่
event.preventDefault()
หาก stopPropagation()
ป้องกันไม่ให้เหตุการณ์ "เลื่อนลง" (การบันทึก) หรือ "เลื่อนขึ้น" (การบับเบิล) stopPropagation()
จะทําอย่างไรpreventDefault()
ดูเหมือนว่าจะใช้เพื่อวัตถุประสงค์ที่คล้ายกัน ใช่หรือไม่
ไม่ครับ แม้ว่าคนมักจะสับสนระหว่าง 2 อย่างนี้ แต่จริงๆ แล้ว 2 อย่างนี้ไม่ได้เกี่ยวข้องกันมากนัก
เมื่อเห็น preventDefault()
ให้เพิ่มคำว่า "การดำเนินการ" ในใจ คิดถึง "การป้องกันการดำเนินการเริ่มต้น"
และการดำเนินการเริ่มต้นที่คุณอาจถามคืออะไร ขออภัย คำตอบนั้นไม่ชัดเจนนักเนื่องจากขึ้นอยู่กับองค์ประกอบ + เหตุการณ์ที่รวมกัน และยิ่งไปกว่านั้น บางครั้งก็ไม่มีการดำเนินการเริ่มต้นเลย
มาเริ่มด้วยตัวอย่างง่ายๆ เพื่อทำความเข้าใจกัน คุณคาดหวังให้สิ่งใดเกิดขึ้นเมื่อคลิกลิงก์ในหน้าเว็บ แน่นอนว่าคุณคาดหวังให้เบราว์เซอร์ไปยัง URL ที่ลิงก์นั้นระบุ
ในกรณีนี้ องค์ประกอบคือแท็กแอตทริบิวต์ "a" และเหตุการณ์คือเหตุการณ์การคลิก ชุดค่าผสมดังกล่าว (<a>
+
click
) มี "การดำเนินการเริ่มต้น" ในการไปยัง href ของลิงก์ ในกรณีที่คุณต้องการป้องกันไม่ให้เบราว์เซอร์ดำเนินการเริ่มต้นดังกล่าว กล่าวคือ สมมติว่าคุณต้องการป้องกันไม่ให้เบราว์เซอร์ไปยัง URL ที่ระบุโดยแอตทริบิวต์ href
ขององค์ประกอบ <a>
preventDefault()
จะทําสิ่งต่อไปนี้ให้คุณ ลองดูตัวอย่างนี้
<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
'click',
function (e) {
e.preventDefault();
console.log('Maybe we should just play some of their music right here instead?');
},
false,
);
คุณสามารถลองใช้ฟีเจอร์นี้แบบอินเทอร์แอกทีฟได้ในวิดีโอสาธิตเวอร์ชันที่ใช้จริงด้านล่าง คลิกลิงก์ The Avett Brothers แล้วดูเอาต์พุตคอนโซล (และข้อเท็จจริงที่ว่าระบบไม่ได้เปลี่ยนเส้นทางคุณไปยังเว็บไซต์ของ The Avett Brothers)
โดยปกติแล้ว การคลิกลิงก์ที่มีป้ายกำกับว่า The Avett Brothers จะทําให้เรียกดูwww.theavettbrothers.com
ในกรณีนี้ เราได้ต่อสายให้กับตัวแฮนเดิลเหตุการณ์คลิกไปยังองค์ประกอบ <a>
และระบุให้ป้องกันการดำเนินการเริ่มต้น ดังนั้นเมื่อผู้ใช้คลิกลิงก์นี้ ระบบจะไม่นําทางผู้ใช้ไปยังที่ใดเลย แต่คอนโซลจะบันทึกว่า "เราควรเล่นเพลงบางส่วนของศิลปินที่นี่เลยไหม"
องค์ประกอบ/เหตุการณ์อื่นๆ ใดบ้างที่ช่วยให้คุณสามารถป้องกันการดำเนินการเริ่มต้นได้ เราไม่สามารถระบุรายการทั้งหมดได้ และบางครั้งคุณก็ต้องลองดู แต่สรุปสั้นๆ ก็คือ
องค์ประกอบ
<form>
+ เหตุการณ์ "submit":preventDefault()
สําหรับชุดค่าผสมนี้จะป้องกันไม่ให้ส่งแบบฟอร์ม วิธีนี้มีประโยชน์ในกรณีที่คุณต้องการดำเนินการตรวจสอบ และหากการดำเนินการไม่สำเร็จ คุณสามารถเรียกใช้ preventDefault แบบมีเงื่อนไขเพื่อหยุดการส่งแบบฟอร์มได้องค์ประกอบ
<a>
+ เหตุการณ์ "คลิก":preventDefault()
สําหรับชุดค่าผสมนี้จะช่วยป้องกันไม่ให้เบราว์เซอร์ไปยัง URL ที่ระบุไว้ในแอตทริบิวต์ href ขององค์ประกอบ<a>
document
+ เหตุการณ์ "mousewheel":preventDefault()
สําหรับชุดค่าผสมนี้จะช่วยป้องกันไม่ให้เลื่อนหน้าเว็บด้วยปุ่มเลื่อนของเมาส์ (แต่การเลื่อนด้วยแป้นพิมพ์จะยังคงทํางาน)
↜ ซึ่งต้องใช้การเรียกใช้addEventListener()
ด้วย{ passive: false }
document
+ เหตุการณ์ "keydown":preventDefault()
สําหรับชุดค่าผสมนี้ถือว่าอันตราย ซึ่งทำให้หน้าเว็บไร้ประโยชน์อย่างมาก ป้องกันไม่ให้ใช้แป้นพิมพ์เลื่อน การกด Tab และการไฮไลต์แป้นพิมพ์document
+ เหตุการณ์ "mousedown":preventDefault()
สำหรับชุดค่าผสมนี้จะป้องกันไม่ให้ไฮไลต์ข้อความด้วยเมาส์และการดำเนินการ "เริ่มต้น" อื่นๆ ที่จะเรียกให้แสดงเมื่อกดเมาส์ลงองค์ประกอบ
<input>
+ เหตุการณ์ "keypress":preventDefault()
สําหรับชุดค่าผสมนี้จะป้องกันไม่ให้อักขระที่ผู้ใช้พิมพ์ไปถึงองค์ประกอบอินพุต (แต่อย่าทําเช่นนี้ เนื่องจากมีเหตุผลที่ถูกต้องเพียงไม่กี่กรณีเท่านั้น)document
+ เหตุการณ์ "contextmenu":preventDefault()
สําหรับชุดค่าผสมนี้จะช่วยป้องกันไม่ให้เมนูตามบริบทของเบราว์เซอร์ปรากฏขึ้นเมื่อผู้ใช้คลิกขวาหรือกดค้างไว้ (หรือวิธีอื่นๆ ที่เมนูตามบริบทอาจปรากฏขึ้น)
รายการนี้เป็นเพียงตัวอย่างบางส่วนเท่านั้น แต่หวังว่าจะช่วยให้คุณเห็นภาพว่าpreventDefault()
มีการใช้งานอย่างไร
มุกตลกที่ใช้ได้จริงไหม
จะเกิดอะไรขึ้นหากคุณ stopPropagation()
และ preventDefault()
ในระยะการจับภาพโดยเริ่มจากเอกสาร จากนั้นก็เกิดความสนุกสนาน ข้อมูลโค้ดต่อไปนี้จะทำให้หน้าเว็บไร้ประโยชน์
function preventEverything(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });
เราไม่ทราบจริงๆ ว่าเหตุใดคุณจึงต้องการทำเช่นนี้ (ยกเว้นในกรณีที่ต้องการแกล้งคนอื่น) แต่คุณควรพิจารณาสิ่งที่เกิดขึ้นและตระหนักถึงสาเหตุที่ทำให้เกิดสถานการณ์เช่นนี้
เหตุการณ์ทั้งหมดเริ่มต้นที่ window
ดังนั้นในสnippet นี้ เราจะหยุดเหตุการณ์ click
, keydown
, mousedown
, contextmenu
และ mousewheel
ทั้งหมดไม่ให้เข้าถึงองค์ประกอบที่อาจรอรับเหตุการณ์เหล่านั้น เรายังเรียก stopImmediatePropagation
ด้วยเพื่อให้ตัวแฮนเดิลที่เชื่อมโยงกับเอกสารหลังจากนี้ถูกบล็อกด้วย
โปรดทราบว่า stopPropagation()
และ stopImmediatePropagation()
ไม่ใช่ (อย่างน้อยก็ไม่ใช่ส่วนใหญ่) สาเหตุที่ทำให้หน้าเว็บใช้งานไม่ได้ เพียงแค่ป้องกันไม่ให้เหตุการณ์ไปยังที่ที่ควรจะไป
แต่เราเรียก preventDefault()
ว่า preventDefault()
ซึ่งคุณคงจำได้ว่าป้องกันการดำเนินการเริ่มต้น ดังนั้น ระบบจึงป้องกันการดำเนินการเริ่มต้นทั้งหมด (เช่น การเลื่อนด้วยปุ่มลูกล้อของเมาส์ การเลื่อนด้วยแป้นพิมพ์หรือการไฮไลต์หรือการกด Tab การคลิกลิงก์ การแสดงเมนูตามบริบท ฯลฯ) ซึ่งทำให้หน้าเว็บอยู่ในสถานะที่ไม่มีประโยชน์
การสาธิตการใช้งานแบบสด
หากต้องการดูตัวอย่างทั้งหมดจากบทความนี้อีกครั้งในที่เดียว ให้ดูการสาธิตที่ฝังไว้ด้านล่าง
ขอขอบคุณ
รูปภาพหลักโดย Tom Wilson จาก Unsplash