لماذا يعتبر "click outside" كابوسًا لكل مطور واجهات أمامية؟
\n\nتخيل: أنت تكتب نافذة مشروطة، أو قائمة منسدلة، أو قائمة اختيار. يفتحها المستخدم، ثم ينقر في مكان فارغ. منطقي؟ نعم. سهل التنفيذ؟ هنا تبدأ المشاكل.
\n\nسؤال "How do I detect a click outside an element?" هو أحد أكثر الأسئلة شيوعًا على Stack Overflow. آلاف المطورين يحاولون فهم لماذا يعمل كودهم أحيانًا ولا يعمل أحيانًا أخرى. اليوم سنستعرض أهم الأخطاء عند اكتشاف النقر خارج العنصر في JavaScript ونتعلم كيفية القيام بذلك بشكل صحيح.
\n\nالمقالة مكتوبة للموقع التعليمي pythonlib.ru، لكن الكود سيكون بلغة JavaScript خالصة - لا أطر عمل، فقط عمل شاق. هيا بنا!
\n\nالخطأ رقم 1: الاستماع للنقر على العنصر نفسه
\n\nالخطأ الأكثر سذاجة ولكن انتشارًا. يحاول المبتدئون إرفاق معالج حدث بالعنصر وانتظار النقر خارجه. يبدو كالتالي: "أنا لا أعرف متى سينقر المستخدم خارجًا، لكنني سأضع علامة على هذا الحدث".
\n\nهذا لا يعمل. حدث click يتم تشغيله فقط عندما ينقر المستخدم على العنصر. إذا كان النقر خارجًا - لا يتم إنشاء الحدث. أنت ببساطة لن تعلم بوجود النقر.
كيف تفعل ذلك بشكل صحيح؟
\nيجب الاستماع للنقر على المستند (document) (أو على window)، ثم التحقق مما إذا كان النقر داخل العنصر الخاص بك أم خارجه.
const myElement = document.querySelector('.my-dropdown');\n\ndocument.addEventListener('click', function(event) {\n // نتحقق مما إذا تم النقر داخل myElement\n if (!myElement.contains(event.target)) {\n // النقر من الخارج - نغلق!\n myElement.style.display = 'none';\n }\n});\n\nهذا هو الأساس. ولكن هناك أيضًا بعض المزالق هنا. ننتقل إلى الخطأ التالي.
\n\nالخطأ رقم 2: تجاهل انتشار الأحداث (Event Bubbling)
\n\nلقد كتبت الكود أعلاه، لكنه لا يزال لا يعمل؟ على الأرجح المشكلة في انتشار الأحداث. عندما تنقر على عنصر فرعي، يتم تشغيل الحدث عليه أولاً، ثم "ينتشر" لأعلى عبر شجرة DOM حتى document.
إذا كان هناك event.stopPropagation() في الطريق - سيموت الحدث ولن يصل إلى document. لن يتم تشغيل المعالج الخاص بك.
كيف تتجنب ذلك؟
\nلا تستخدم أبدًا stopPropagation() دون ضرورة قصوى. إذا كنت تكتب مكونًا يعترض النقر (مثل زر داخل قائمة منسدلة)، استخدم event.stopImmediatePropagation() فقط إذا كنت متأكدًا من أنه لن يكسر المعالجات الخارجية.
الحل الأفضل هو عدم الاعتماد على الانتشار، بل إرفاق المعالج في مرحلة الغوص (capture phase). للقيام بذلك، مرر الوسيطة الثالثة true إلى addEventListener:
document.addEventListener('click', function(event) {\n // الكود الخاص بك\n}, true); // مرحلة capture: الحدث يصل أولاً إلى document، ثم ينزل لأسفل\n\nالآن سيتم التقاط الحدث قبل أن يوقفه أي شخص. لكن هذا سلاح ذو حدين - استخدمه بحذر.
\n\nالخطأ رقم 3: المقارنة غير الصحيحة مع event.target
\n\nالكلاسيكية: تتحقق من event.target === myElement. هذا يعمل فقط إذا تم النقر بالضبط على العنصر نفسه. ولكن ماذا لو كان داخل العنصر صورة أو زر أو مجرد نص؟
النقر على عقدة فرعية سيعطي event.target مساويًا لتلك العقدة الفرعية، وليس للعنصر الخاص بك. لن يتم استيفاء الشرط، ولن يتم إغلاق العنصر.
الحل: استخدم Node.contains()
\nالطريقة contains() تتحقق مما إذا كانت العقدة الممررة هي سليل للعقدة الحالية (أو العقدة نفسها). هذا مثالي لمهمتنا.
// صحيح\nif (!myElement.contains(event.target)) {\n // النقر من الخارج\n}\n\n// غير صحيح\nif (event.target !== myElement) {\n // سيتجاهل النقر على العناصر الفرعية\n}