← الدرس السابق المستوى 3 - الدرس 8 من 10

🎯 الأحداث (Events) في JavaScript: الدليل الشامل للتفاعل

تعلم كيف تستجيب لتفاعلات المستخدم باستخدام Event Listeners وأفضل الممارسات

تقدمك في المستوى الثالث 80%

📋 محتويات الدرس

  1. ما هي الأحداث وكيف تعمل؟
  2. أحداث الماوس: click, dblclick, mouseover, mouseout
  3. أحداث لوحة المفاتيح: keydown, keyup, keypress
  4. أحداث النماذج: input, change, submit, focus, blur
  5. أحداث النافذة: load, resize, scroll
  6. addEventListener — الطريقة الحديثة والمستحسنة
  7. كائن الحدث (Event Object) وخصائصه
  8. preventDefault و stopPropagation
  9. Event Delegation — أداء أفضل للعناصر الديناميكية
  10. إزالة Event Listeners
  11. أمثلة واقعية: نموذج، معرض صور، قائمة منسدلة
  12. اختبر فهمك — اختبار تفاعلي

📖 ما هي الأحداث وكيف تعمل؟

الحدث (Event) هو أي تفاعل يحدث في المتصفح — نقر بالماوس، ضغط مفتاح، تحميل صفحة، تغيير حجم النافذة. JavaScript تستمع لهذه الأحداث وتستجيب لها بتنفيذ كود معين.

💡 نموذج الأحداث في المتصفح:
  1. المستخدم يُجرى فعلاً (ينقر زراً مثلاً)
  2. المتصفح يُنشئ كائن حدث (Event Object) يحتوي تفاصيل الحدث
  3. الحدث يتنقل في شجرة DOM (Capturing → Target → Bubbling)
  4. JavaScript تستجيب بتنفيذ الـ Event Listener المُسجّل

مثال من الحياة: تخيل مطعم. العميل (المستخدم) يضغط الجرس (الحدث). النادل (JavaScript) يسمع الجرس ويأتي (يستجيب). كل عميل له طلب مختلف، والنادل يتعامل مع كل طلب حسب نوعه.

🖱️ أحداث الماوس

أكثر الأحداث استخداماً في تطبيقات الويب. تتيح للمستخدم التفاعل مع العناصر المرئية:

الحدث يُطلق عندما استخدام شائع
clickنقر وإفلات بالماوسأزرار، روابط، عناصر قابلة للنقر
dblclickنقر مزدوج سريعتعديل نص، فتح ملف
mousedownضغط زر الماوس (قبل الإفلات)سحب وإفلات (Drag & Drop)
mouseupإفلات زر الماوستأكيد النقر
mouseoverدخول الماوس لمنطقة العنصرتأثيرات hover، معاينة
mouseoutخروج الماوس من منطقة العنصرإزالة تأثير hover
mousemoveتحريك الماوس فوق العنصرتتبع الماوس، رسوم متحركة
contextmenuنقر بالزر الأيمنقائمة سياق مخصصة
let button = document.getElementById("myBtn"); // النقر العادي button.addEventListener("click", () => { console.log("تم النقر!"); }); // تأثير hover let card = document.querySelector(".product-card"); card.addEventListener("mouseover", () => { card.style.transform = "scale(1.05)"; card.style.boxShadow = "0 10px 20px rgba(0,0,0,0.2)"; }); card.addEventListener("mouseout", () => { card.style.transform = "scale(1)"; card.style.boxShadow = "none"; });

⌨️ أحداث لوحة المفاتيح

تُستخدم للتعامل مع إدخال المستخدم عبر لوحة المفاتيح. مهمة جداً في النماذج والألعاب:

الحدث يُطلق عندما الفرق
keydown ضغط أي مفتاح (قبل ظهور الحرف) يُطلق لكل المفاتيح حتى Ctrl, Shift
keypress ضغط مفتاح يُنتج حرفاً لا يعمل مع المفاتيح الخاصة (deprecated)
keyup إفلات المفتاح القيمة النهائية متاحة
let searchInput = document.getElementById("search"); // البحث الفوري مع الكتابة searchInput.addEventListener("input", (e) => { console.log("يبحث عن:", e.target.value); }); // ضغط Enter للإرسال searchInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { console.log("إرسال البحث!"); performSearch(e.target.value); } // Escape لمسح البحث if (e.key === "Escape") { e.target.value = ""; } });

📝 أحداث النماذج

تُستخدم للتحقق من صحة البيانات وتوفير تجربة مستخدم سلسة:

let form = document.querySelector("form"); let emailInput = document.getElementById("email"); // input — مع كل حرف يُكتب emailInput.addEventListener("input", (e) => { let value = e.target.value; let isValid = value.includes("@"); if (isValid) { emailInput.classList.remove("invalid"); emailInput.classList.add("valid"); } else { emailInput.classList.remove("valid"); emailInput.classList.add("invalid"); } }); // focus — عند دخول الحقل emailInput.addEventListener("focus", () => { console.log("المستخدم في حقل البريد"); }); // blur — عند مغادرة الحقل emailInput.addEventListener("blur", () => { if (!emailInput.value.includes("@")) { alert("⚠️ يرجى إدخال بريد صالح"); } }); // submit — عند إرسال النموذج form.addEventListener("submit", (e) => { e.preventDefault(); // منع الإرسال الافتراضي console.log("النموذج جاهز للإرسال!"); // sendDataToServer(); });

🪟 أحداث النافذة

تتعلق بنافذة المتصفح نفسها، وليس بعناصر محددة:

// load — عند اكتمال تحميل الصفحة window.addEventListener("load", () => { console.log("✅ الصفحة جاهزة!"); // إخفاء شاشة التحميل }); // DOMContentLoaded — عند اكتمال تحميل HTML (أسرع من load) document.addEventListener("DOMContentLoaded", () => { console.log("DOM جاهز!"); }); // resize — عند تغيير حجم النافذة let resizeTimeout; window.addEventListener("resize", () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { console.log(`الحجم: ${window.innerWidth}x${window.innerHeight}`); }, 200); // Debounce لتجنب التكرار }); // scroll — عند التمرير window.addEventListener("scroll", () => { let scrollY = window.scrollY; if (scrollY > 500) { document.getElementById("backToTop").style.display = "block"; } });

⚡ addEventListener — الطريقة الحديثة والمستحسنة

هناك 3 طرق لإضافة أحداث. addEventListener هي الأفضل دائماً:

الطريقة الصيغة مستمعات متعددة إمكانية الإزالة
HTML Attribute onclick="..." ✗ لا ✗ لا
DOM Property element.onclick = ... ✗ لا ✗ لا
addEventListener element.addEventListener(...) ✓ نعم ✓ نعم
✅ لماذا addEventListener أفضل؟
  • يمكن إضافة مستمعات متعددة لنفس الحدث
  • يمكن إزالة المستمع لاحقاً
  • يدعم خيارات متقدمة (once, capture, passive)
  • يفصل HTML عن JavaScript (MVC أفضل)

📦 كائن الحدث (Event Object)

عندما يُطلق حدث، يُمرّر كائن يحتوي معلومات مفصلة عن الحدث:

button.addEventListener("click", (event) => { // معلومات عامة console.log(event.type); // "click" console.log(event.target); // العنصر الذي تم النقر عليه console.log(event.currentTarget); // العنصر المُسجّل عليه المستمع console.log(event.timeStamp); // وقت الحدث بالملي ثانية // معلومات الماوس console.log(event.clientX, event.clientY); // موقع النقر بالنسبة للنافذة console.log(event.offsetX, event.offsetY); // موقع النقر بالنسبة للعنصر // معلومات لوحة المفاتيح console.log(event.key); // "Enter" console.log(event.code); // "Enter" (فيزيائي) console.log(event.ctrlKey); // true/false console.log(event.shiftKey); // true/false });

🛑 preventDefault و stopPropagation

هاتان الدالتان تتحكمان في سلوك الحدث، لكن بطريقتين مختلفتين:

الدالة ما تفعله استخدام شائع
preventDefault() تمنع السلوك الافتراضي للمتصفح منع إرسال نموذج، منع فتح رابط
stopPropagation() توقف انتشار الحدث للعناصر الأب منع تفعيل مستمعات الأب
// preventDefault — منع السلوك الافتراضي document.querySelector("a").addEventListener("click", (e) => { e.preventDefault(); // لا يفتح الرابط console.log("تم منع الانتقال!"); }); // stopPropagation — منع الانتشار document.getElementById("child").addEventListener("click", (e) => { e.stopPropagation(); // لا يصل الحدث للأب console.log("نقر على الابن فقط"); });

🎯 Event Delegation — أداء أفضل

بدلاً من إضافة مستمع لكل عنصر، أضف مستمعاً واحداً للأب واستخدم event.target لمعرفة العنصر المُنقر:

// ❌ طريقة سيئة — مستمع لكل زر document.querySelectorAll(".delete-btn").forEach(btn => { btn.addEventListener("click", deleteItem); // بطيء مع 1000 زر! }); // ✅ طريقة ممتازة — Event Delegation document.getElementById("todoList").addEventListener("click", (e) => { if (e.target.classList.contains("delete-btn")) { e.target.closest("li").remove(); } if (e.target.classList.contains("edit-btn")) { editItem(e.target.closest("li")); } });
✅ فوائد Event Delegation:
  • أداء أفضل: مستمع واحد بدلاً من آلاف
  • 🔄 يعمل مع العناصر الديناميكية: العناصر المُضافة لاحقاً تُعمل تلقائياً
  • 🧹 ذاكرة أقل: لا حاجة لإزالة مستمعات عند حذف عناصر

🗑️ إزالة Event Listeners

مهم لمنع تسريبات الذاكرة (Memory Leaks) خاصة في التطبيقات الكبيرة:

function handleClick() { console.log("نقر!"); } // إضافة button.addEventListener("click", handleClick); // إزالة (يجب استخدام نفس الدالة المرجعية) button.removeEventListener("click", handleClick); // مستمع يُنفذ مرة واحدة فقط button.addEventListener("click", () => { console.log("مرة واحدة فقط!"); }, { once: true });

💼 أمثلة واقعية من التطبيقات

1. نموذج تسجيل مع التحقق المباشر

const form = document.getElementById("registerForm"); form.addEventListener("submit", (e) => { e.preventDefault(); const email = form.querySelector("[name='email']").value; const password = form.querySelector("[name='password']").value; if (password.length < 8) { alert("كلمة المرور يجب أن تكون 8 أحرف على الأقل"); return; } console.log("✅ التسجيل ناجح!", { email }); });

2. معرض صور مع أزرار التنقل

const gallery = document.getElementById("gallery"); const images = ["img1.jpg", "img2.jpg", "img3.jpg"]; let currentIndex = 0; document.getElementById("next").addEventListener("click", () => { currentIndex = (currentIndex + 1) % images.length; gallery.src = images[currentIndex]; }); document.getElementById("prev").addEventListener("click", () => { currentIndex = (currentIndex - 1 + images.length) % images.length; gallery.src = images[currentIndex]; });

3. قائمة منسدلة (Dropdown)

const dropdown = document.querySelector(".dropdown"); const toggle = dropdown.querySelector(".dropdown-toggle"); const menu = dropdown.querySelector(".dropdown-menu"); toggle.addEventListener("click", (e) => { e.stopPropagation(); menu.classList.toggle("show"); }); // إغلاق عند النقر خارج القائمة document.addEventListener("click", (e) => { if (!dropdown.contains(e.target)) { menu.classList.remove("show"); } });

🚀 الخطوة التالية: LocalStorage و JSON

في الدرس التاسع، ستتعلم كيف تحفظ البيانات في المتصفح:

  • 💾 localStorage: حفظ البيانات بشكل دائم
  • 📦 sessionStorage: حفظ البيانات للجلسة فقط
  • 🔄 JSON: تحويل الكائنات لنصوص والعكس
  • 🍪 Cookies: البديل القديم
📚 مصادر إضافية:
← الدرس السابق: DOM