preventDefault
และ stopPropagation
: เมื่อใดควรใช้วิธีใด และแต่ละวิธีทำอะไรได้บ้าง
Event.stopPropagation() และ Event.preventDefault()
การจัดการเหตุการณ์ JavaScript มักจะตรงไปตรงมา โดยเฉพาะอย่างยิ่งเมื่อต้องจัดการกับโครงสร้าง HTML ที่
เรียบง่าย (ค่อนข้างแบน) แต่จะมีความซับซ้อนมากขึ้นเมื่อเหตุการณ์
เดินทาง (หรือแพร่กระจาย) ผ่านลำดับชั้นขององค์ประกอบ โดยปกติแล้ว นักพัฒนาแอปจะติดต่อstopPropagation()
และ/หรือpreventDefault()
เพื่อแก้ปัญหาที่พบ หากคุณเคยคิดว่า "ฉันจะลองpreventDefault()
ก่อน ถ้าไม่ได้ผลก็ลองstopPropagation()
ถ้าไม่ได้ผลอีกก็ลองทั้งสองอย่าง" บทความนี้เหมาะสำหรับคุณ เราจะอธิบายการทำงานของแต่ละวิธีอย่างละเอียด เวลาที่ควรใช้วิธีใด และยกตัวอย่างการทำงานที่หลากหลายให้คุณได้ลองใช้
เป้าหมายของฉันคือการขจัดความสับสนของคุณให้หมดไป
อย่างไรก็ตาม ก่อนที่จะเจาะลึกในรายละเอียด คุณควรทราบโดยสังเขปเกี่ยวกับการจัดการเหตุการณ์ 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 was clicked" แล้วเราก็เสร็จสิ้นใช่ไหม
ผิด เรายังไม่เสร็จเลย กระบวนการจะดำเนินต่อไป แต่จะเปลี่ยนเป็นระยะการแสดงเนื้อหาที่เกี่ยวข้อง
การฟองเหตุการณ์
เบราว์เซอร์จะถามว่า
"มีอะไรที่รอรับเหตุการณ์คลิกใน #C
ในระยะฟองไหม" โปรดตั้งใจฟังตรงนี้
คุณสามารถฟังการคลิก (หรือเหตุการณ์ประเภทใดก็ได้) ในทั้งการจับและ
ระยะฟองได้ และหากคุณเชื่อมต่อตัวแฮนเดิลเหตุการณ์ในทั้ง 2 เฟส (เช่น โดยการเรียกใช้
.addEventListener()
2 ครั้ง ครั้งหนึ่งด้วย capture = true
และอีกครั้งด้วย capture = false
) ก็ใช่
ตัวแฮนเดิลเหตุการณ์ทั้ง 2 จะทํางานสําหรับองค์ประกอบเดียวกันอย่างแน่นอน แต่ก็ควรทราบด้วยว่า
เหตุการณ์จะทริกเกอร์ในระยะต่างๆ (1 รายการในระยะการจับภาพและ 1 รายการในระยะการฟอง)
จากนั้น เหตุการณ์จะเผยแพร่ (โดยทั่วไปเรียกว่า "ฟอง" เนื่องจากดูเหมือนว่าเหตุการณ์
จะเดินทาง "ขึ้น" ต้นไม้ DOM) ไปยังองค์ประกอบหลัก #B
และเบราว์เซอร์จะถามว่า "มี
อะไรที่รอฟังเหตุการณ์คลิกใน #B
ในระยะฟองไหม" ในตัวอย่างของเราไม่มีอะไรเลย ดังนั้น
จึงไม่มีตัวแฮนเดิลใดทำงาน
จากนั้น เหตุการณ์จะส่งต่อไปยัง #A
และเบราว์เซอร์จะถามว่า "มีอะไรที่รอรับเหตุการณ์คลิก
ใน #A
ในระยะการส่งต่อไหม"
จากนั้น เหตุการณ์จะส่งต่อไปยัง <body>
: "มีอะไรที่รอรับฟังเหตุการณ์คลิกใน<body>
องค์ประกอบในระยะการส่งต่อหรือไม่"
ต่อไปคือองค์ประกอบ <html>
: "มีอะไรที่รอฟังเหตุการณ์คลิกในองค์ประกอบ <html>
ใน
ระยะฟองไหม
จากนั้น document
: "มีอะไรที่รอรับเหตุการณ์คลิกใน document
ในระยะฟองสบู่อยู่ไหม
"
สุดท้าย window
: "มีอะไรที่รอรับฟังเหตุการณ์คลิกในหน้าต่างในระยะฟองไหม"
ในที่สุด การเดินทางครั้งนี้ยาวนานมาก และตอนนี้กิจกรรมของเราอาจเหนื่อยล้าไปแล้ว แต่ไม่ว่าคุณจะเชื่อหรือไม่ การเดินทางนี้เป็นสิ่งที่ทุกกิจกรรมต้องเผชิญ โดยส่วนใหญ่แล้วเราจะไม่สังเกตเห็นสิ่งนี้เนื่องจาก โดยปกติแล้วนักพัฒนาซอฟต์แวร์จะสนใจเฉพาะเฟสเหตุการณ์ใดเฟสหนึ่งเท่านั้น (และมักจะเป็น เฟสฟองสบู่)
คุณควรใช้เวลาสักครู่เพื่อลองเล่นกับการดักจับเหตุการณ์และการฟองเหตุการณ์ รวมถึงบันทึก โน้ตบางอย่างไปยังคอนโซลเมื่อตัวแฮนเดิลเริ่มทำงาน การดูเส้นทางที่เหตุการณ์ ใช้เป็นข้อมูลเชิงลึกที่มีประโยชน์มาก ตัวอย่างด้านล่างนี้จะรับฟังทุกองค์ประกอบในทั้ง 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
) คุณจะเห็นตัวแฮนเดิลเหตุการณ์เหล่านี้
ทำงานทั้งหมด เมื่อใช้การจัดรูปแบบ CSS เล็กน้อยเพื่อให้เห็นได้ชัดเจนยิ่งขึ้นว่าองค์ประกอบใดคือองค์ประกอบใด นี่คือเอาต์พุตของคอนโซล
#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"
"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"
event.stopPropagation()
เมื่อเข้าใจว่าเหตุการณ์เกิดขึ้นที่ใดและเดินทางอย่างไร (เช่น แพร่กระจาย) ผ่าน DOM
ทั้งในระยะการจับภาพและระยะการฟอง เราก็สามารถหันมาสนใจevent.stopPropagation()
ได้แล้ว
คุณเรียกใช้เมธอด stopPropagation()
ได้ในเหตุการณ์ DOM ดั้งเดิม (ส่วนใหญ่) ที่บอกว่า "ส่วนใหญ่" ก็เพราะมีบางรายการที่การเรียกใช้เมธอดนี้จะไม่มีผลใดๆ (เนื่องจากเหตุการณ์ไม่ได้เผยแพร่ตั้งแต่แรก) กิจกรรมต่างๆ เช่น focus
, blur
, load
, scroll
และอื่นๆ อีกเล็กน้อยจะอยู่ในหมวดหมู่นี้
คุณโทรหา stopPropagation()
ได้ แต่จะไม่มีอะไรเกิดขึ้นเนื่องจากเหตุการณ์เหล่านี้
ไม่ได้แพร่กระจาย
แต่ stopPropagation
ทำอะไรได้บ้าง
โดยจะทำงานตามที่ระบุไว้ เมื่อเรียกใช้ เหตุการณ์จะหยุด
ส่งต่อไปยังองค์ประกอบใดๆ ที่จะส่งต่อไปยังองค์ประกอบนั้น ซึ่งเป็นจริงทั้ง 2 ทิศทาง
(การจับภาพและการส่งต่อ) ดังนั้นหากคุณเรียกใช้ 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"
แล้วจะหยุดการแพร่กระจายที่ #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"
อีกอันเพื่อความสนุก จะเกิดอะไรขึ้นหากเราเรียกใช้ 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()
จากอดีต ดังนั้นจุดนี้คือจุดที่การแพร่กระจายของเหตุการณ์จะสิ้นสุดลง
ฉันขอแนะนำให้คุณลองเล่นในเดโมแบบสดเหล่านี้ ลองคลิกเฉพาะองค์ประกอบ #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()
แทน ตัวแฮนเดิลที่สาม
ก็จะยังคงทำงาน
event.preventDefault()
หาก stopPropagation()
ป้องกันไม่ให้เหตุการณ์ "ลง" (จับ) หรือ "ขึ้น"
(ฟอง) แล้ว preventDefault()
จะทำอะไร ดูเหมือนว่าฟีเจอร์นี้จะทำงานคล้ายกัน Does
it?
ไม่ครับ แม้ว่าทั้ง 2 อย่างนี้มักจะทำให้เกิดความสับสน แต่จริงๆ แล้วทั้ง 2 อย่างนี้ไม่ได้มีความเกี่ยวข้องกันมากนัก
เมื่อเห็น preventDefault()
ให้เพิ่มคำว่า "การดำเนินการ" ในใจ คิดว่า "ป้องกันการดำเนินการเริ่มต้น"
และคุณอาจถามว่าการดำเนินการเริ่มต้นคืออะไร ขออภัย คำตอบสำหรับคำถามนั้นไม่ชัดเจนนัก เนื่องจากขึ้นอยู่กับการผสมผสานขององค์ประกอบและเหตุการณ์ที่เป็นปัญหา และที่ทำให้สับสนยิ่งกว่าเดิมคือ บางครั้งก็ไม่มีการดำเนินการเริ่มต้นเลย
มาเริ่มด้วยตัวอย่างที่ง่ายมากเพื่อให้เข้าใจกัน คุณคาดหวังว่าจะเกิดอะไรขึ้นเมื่อคลิกลิงก์ในหน้าเว็บ แน่นอนว่าคุณคงคาดหวังให้เบราว์เซอร์ไปยัง URL ที่ลิงก์นั้นระบุ
ในกรณีนี้ องค์ประกอบคือแท็ก Anchor และเหตุการณ์คือเหตุการณ์คลิก โดยการกดปุ่มร่วมกัน (<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 จะนำไปสู่การเรียกดู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
ดังนั้นในข้อมูลโค้ดนี้ เราจึงหยุดเหตุการณ์ทั้งหมดของ click
, keydown
, mousedown
, contextmenu
และ mousewheel
ไม่ให้ไปถึงองค์ประกอบใดๆ ที่อาจรอฟังเหตุการณ์เหล่านั้น นอกจากนี้ เรายังเรียก stopImmediatePropagation
เพื่อให้ตัวแฮนเดิลที่เชื่อมต่อกับเอกสารหลังจากเอกสารนี้ไม่สามารถทำงานได้เช่นกัน
โปรดทราบว่า stopPropagation()
และ stopImmediatePropagation()
ไม่ใช่ (อย่างน้อยก็ไม่ใช่ส่วนใหญ่) สิ่งที่
ทำให้หน้าเว็บใช้ไม่ได้ โดยจะป้องกันไม่ให้เหตุการณ์ไปถึงที่ที่ควรจะไป
แต่เรายังเรียกใช้ preventDefault()
ซึ่งคุณคงจำได้ว่าเป็นการป้องกันการดำเนินการเริ่มต้น ดังนั้นการดำเนินการเริ่มต้นใดๆ (เช่น การเลื่อนด้วยล้อเมาส์ การเลื่อนด้วยแป้นพิมพ์ การไฮไลต์หรือการกด Tab การคลิกลิงก์
การแสดงเมนูตามบริบท ฯลฯ) จะถูกป้องกันทั้งหมด ทำให้หน้าเว็บอยู่ในสถานะที่ค่อนข้างไร้ประโยชน์
คำขอบคุณ
รูปภาพหลักโดย Tom Wilson ใน Unsplash