نظرة معمّقة على أحداث JavaScript

preventDefault وstopPropagation: حالات استخدام كل طريقة ووظيفتها بالضبط

غالبًا ما تكون معالجة أحداث JavaScript مباشرةً. وينطبق ذلك بشكل خاص عند التعامل مع بنية HTML بسيطة (مسطّحة نسبيًا). تصبح الأمور أكثر تعقيدًا عندما يتم نقل الأحداث (أو نشرها) من خلال تسلسل هرمي للعناصر. ويحدث ذلك عادةً عندما يتواصل المطوّرون مع فريق stopPropagation() و/أو preventDefault() لحلّ المشاكل التي يواجهونها. إذا سبق لك أن فكّرت في تجربة preventDefault() وإذا لم تنجح هذه الطريقة، جرِّب stopPropagation() وإذا لم تنجح هذه الطريقة، جرِّب كليهما، هذه المقالة مناسبة لك. سأشرح بالتفصيل وظائف كل طريقة وحالات استخدامها، وسأقدّم لك مجموعة متنوعة من المثال الجاهزة لاستكشافها. هدفي هو إنهاء الالتباس الذي يصيبك نهائيًا.

قبل التعمّق في الموضوع، من المهمّ التطرّق بشكلٍ موجز إلى نوعَي معالجة الأحداث الممكنَين في JavaScript (في جميع المتصفّحات الحديثة، إذ لم يكن Internet Explorer قبل الإصدار 9 يسمح بالتقاط الأحداث على الإطلاق).

أنماط الأحداث (التسجيل والتوسّع)

تتيح جميع المتصفحات الحديثة تسجيل الأحداث، ولكن نادرًا ما يستخدمها المطوّرون. ومن المثير للاهتمام أنّه كان الشكل الوحيد للأحداث الذي كان Netscape يتوافق معه في الأصل. لم يكن Microsoft Internet Explorer، وهو أكبر منافس لنطاق Netscape، يتيح ميزة تسجيل الأحداث على الإطلاق، بل كان يتيح فقط أسلوبًا آخر للأحداث يُعرف باسم "تدفّق الأحداث". عندما تم تشكيل W3C، تبيّن لهم أنّ هناك مزايا في كلا أسلوبَي تسجيل الأحداث، وأعلنوا أنّ المتصفّحات يجب أن تتوافق مع كليهما، وذلك من خلال مَعلمة ثالثة لمحاولة الطريقة addEventListener. في الأصل، كانت هذه المَعلمة منطقية فقط، ولكن جميع المتصفّحات الحديثة تتيح استخدام عنصر options كمَعلمة ثالثة، ويمكنك استخدامها لتحديد ما إذا كنت تريد استخدام ميزة تسجيل الأحداث أم لا (بالإضافة إلى ميزات أخرى):

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 في مرحلة الفقاعات؟" يُرجى الانتباه جيدًا إلى ما يلي. من الممكن تمامًا الاستماع إلى النقرات (أو أيّ نوع من الأحداث) في كلّ من مرحلتي الالتقاط و الفقاعات. وإذا ربطت معالجات الأحداث في كلتا المرحلتَين (مثلاً من خلال استدعاء .addEventListener() مرّتين، مرّة باستخدام capture = true ومرّة باستخدام capture = false)، سيكون الجواب "نعم"، سيتم تنشيط كلتا معالجتَي الأحداث على العنصر نفسه. من المهم أيضًا ملاحظة أنّه يتم بدء تنفيذها في مراحل مختلفة (مرحلة في مرحلة الالتقاط ومرحلة في مرحلة الفقاعات).

بعد ذلك، سيتم نشر الحدث (يُشار إليه عادةً باسم "الفقاعة" لأنّه يبدو أنّه ينتقل الحدث "لأعلى" شجرة DOM) إلى العنصر الرئيسي، #B، وسيسأل المتصفّح: "هل هناك أيّ عنصر يستمع إلى أحداث النقر على #B في مرحلة الفقاعة؟" في مثالنا، لا يتمّ ضبط أيّ قيمة، وبالتالي لن يتمّ تنفيذ أيّ معالجات.

بعد ذلك، سيتم تجميع الحدث في #A وسيسأل المتصفّح: "هل هناك أيّ عنصر يستمع إلى أحداث النقر على #A في مرحلة التجميع؟"

بعد ذلك، سيتم تصعيد الحدث إلى <body>: "هل هناك أيّ عنصر يستمع إلى أحداث النقر على <body> العنصر في مرحلة التصعيد؟"

بعد ذلك، عنصر <html>: "هل هناك أيّ عنصر يستمع إلى أحداث النقر على عنصر <html> في مرحلة الفقاعات؟

بعد ذلك، document: "هل هناك أيّ عنصر يستمع إلى أحداث النقر على document في مرحلة التزايد العميق؟"

أخيرًا، window: "هل هناك أيّ عنصر يستمع إلى أحداث النقر على النافذة في مرحلة التوسّع؟"

أخيرًا! لقد كانت رحلة طويلة، وربما تكون الفعالية قد تعبت كثيرًا الآن، ولكن صدق أو لا تصدق، هذه هي الرحلة التي يمرّ بها كل حدث. لا يتم ملاحظة ذلك في معظم الأحيان لأنّ المطوّرين عادةً ما يهتمون بمرحلة واحدة من مراحل الحدث أو الأخرى (وعادةً ما تكون مرحلة الصعق).

ننصحك بقضاء بعض الوقت في تجربة ميزة تسجيل الأحداث وتسجيلها وتسجيل بعض الملاحظات في وحدة التحكّم أثناء تشغيل معالِجات الأحداث. من المفيد جدًا معرفة المسار الذي يتّبعه الحدث. في ما يلي مثال يستمع إلى كل عنصر في كلتا المرحلتَين.

<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"

يمكنك التفاعل مع هذه الميزة في العرض التجريبي المباشر أدناه. انقر على العنصر #C وراقِب إخراج وحدة التحكّم.

event.stopPropagation()

بعد التعرّف على مصدر الأحداث وكيفية انتقالها (أي انتشارها) عبر DOM في كلّ من مرحلة الالتقاط ومرحلة التوسّع، يمكننا الآن توجيه انتباهنا إلى event.stopPropagation().

يمكن استدعاء الطريقة stopPropagation() في (معظم) أحداث DOM الأصلية. نقول "معظم" لأنّه هناك عدد قليل من العناصر التي لن يؤدي فيها استدعاء هذه الطريقة إلى أيّ إجراء (لأنّ الحدث لا ينتشر في البداية). تندرج الأحداث مثل focus وblur وload وscroll وبعض الأحداث الأخرى ضمن هذه الفئة. يمكنك الاتصال بالرقم 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، ولكن بدلاً من إيقاف انتقال حدث إلى العناصر الفرعية (الالتقاط) أو العناصر الأصلية (الانتشار)، لا تنطبق هذه الطريقة إلا عندما يكون لديك أكثر من معالِج حدث واحد مرتبط بعنصر واحد. بما أنّ addEventListener() يتيح أسلوب البث المتعدد للأحداث، من الممكن تمامًا ربط معالج الأحداث بعنصر واحد أكثر من مرة. عند حدوث ذلك (في معظم المتصفّحات)، يتم تنفيذ معالجي الأحداث بالترتيب الذي تم ربطهم به. يؤدي استدعاء 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!"

يُرجى العلم أنّ معالِج الأحداث الثالث لا يتم تشغيله أبدًا لأنّ معالِج الأحداث الثاني يستدعي e.stopImmediatePropagation(). إذا استدعينا e.stopPropagation() بدلاً من ذلك، سيظلّ المعالِج الثالث قيد التشغيل.

event.preventDefault()

إذا كان stopPropagation() يمنع انتقال حدث "للأسفل" (الالتقاط) أو "للأعلى" (الفقاعات)، ماذا سيفعل preventDefault() بعد ذلك؟ يبدو أنّه يؤدي وظيفة مشابهة. هل يحدث ذلك؟

ليس فعلاً. على الرغم من أنّه غالبًا ما يتم الخلط بين الاثنين، إلا أنّهما ليسا مرتبطَين ببعضهما كثيرًا. عند رؤية preventDefault()، أضِف كلمة "إجراء" في ذهنك. فكِّر في "منع التلقائي الإجراء".

ما هو الإجراء التلقائي الذي قد تطلبه؟ لا يمكننا الإجابة عن هذا السؤال بوضوح، لأنّ الإجابة تعتمد بشكل كبير على تركيبة العنصر + الحدث المعنيّة. ولكي تزيد الأمور غموضًا، لا يتوفّر أحيانًا أي إجراء تلقائي على الإطلاق.

لنبدأ بمثال بسيط جدًا يسهُل فهمه. ماذا تتوقّع أن يحدث عند النقر على رابط في صفحة ويب؟ من الواضح أنّك تتوقّع من المتصفّح الانتقال إلى عنوان URL المحدّد من خلال هذا الرابط. في هذه الحالة، يكون العنصر علامة رابط والحدث هو حدث نقرة. تؤدي هذه المجموعة (<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> + حدث "إرسال": preventDefault() سيؤدي هذا المزيج إلى منع إرسال نموذج. يكون هذا مفيدًا إذا كنت تريد إجراء عملية التحقّق، وفي حال حدوث خطأ، يمكنك استدعاء preventDefault بشكل مشروط لمنع إرسال النموذج.

  • عنصر <a> + الحدث "click": preventDefault() يمنع هذا المزيج المتصفّح من الانتقال إلى عنوان URL المحدّد في سمة href لعنصر <a>.

  • document + حدث "عجلة الماوس": preventDefault() لمنع التمرير في الصفحة باستخدام عجلة الماوس (سيظل التمرير باستخدام لوحة المفاتيح متاحًا).
    ↜ يتطلّب ذلك الاتصال بـ addEventListener() باستخدام { passive: false }.

  • document + حدث "keydown": preventDefault() هذه المجموعة خطيرة. ويؤدي ذلك إلى جعل الصفحة غير مفيدة إلى حد كبير، ويمنع التنقّل في الصفحة باستخدام لوحة المفاتيح واستخدام مفاتيح التبويب وإبراز النص باستخدام لوحة المفاتيح.

  • 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()، والذي يمنع الإجراء التلقائي. وبالتالي، يتم منع أي إجراءات تلقائية (مثل التمرير باستخدام عجلة الماوس أو التمرير باستخدام لوحة المفاتيح أو تمييز النص أو الانتقال باستخدام مفتاح التبويب أو النقر على الرابط أو عرض قائمة السياق وما إلى ذلك)، ما يؤدي إلى ترك الصفحة في حالة غير مفيدة إلى حدٍ كبير.

العروض التوضيحية المباشرة

للاطّلاع على جميع الأمثلة الواردة في هذه المقالة مرة أخرى في مكان واحد، يمكنك الاطّلاع على العرض الترويجي المضمّن أدناه.

الشكر والتقدير

الصورة الرئيسية تقدّمها Tom Wilson على Unsplash.