نظرة معمّقة على أحداث 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,
);

ستعتمد نتيجة وحدة التحكم على العنصر الذي تنقر عليه. إذا نقرت على العنصر "الأعمق" في شجرة نموذج العناصر في المستند (العنصر #C)، سترى كل واحد من معالِجات الأحداث يتم تنشيطها. باستخدام بعض أنماط CSS لتمييز العناصر بوضوح، إليك عنصر #C لوحدة التحكّم فيconsole output (مع لقطة شاشة أيضًا):

"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 + حدث "contextplaylist": 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.