📖 ما هي الأحداث وكيف تعمل؟
الحدث (Event) هو أي تفاعل يحدث في المتصفح — نقر بالماوس، ضغط مفتاح، تحميل صفحة، تغيير حجم النافذة. JavaScript تستمع لهذه الأحداث وتستجيب لها بتنفيذ كود معين.
💡 نموذج الأحداث في المتصفح:
- المستخدم يُجرى فعلاً (ينقر زراً مثلاً)
- المتصفح يُنشئ كائن حدث (Event Object) يحتوي تفاصيل الحدث
- الحدث يتنقل في شجرة DOM (Capturing → Target → Bubbling)
- 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("تم النقر!");
});
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);
});
searchInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
console.log("إرسال البحث!");
performSearch(e.target.value);
}
if (e.key === "Escape") {
e.target.value = "";
}
});
🪟 أحداث النافذة
تتعلق بنافذة المتصفح نفسها، وليس بعناصر محددة:
window.addEventListener("load", () => {
console.log("✅ الصفحة جاهزة!");
});
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM جاهز!");
});
let resizeTimeout;
window.addEventListener("resize", () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
console.log(`الحجم: ${window.innerWidth}x${window.innerHeight}`);
}, 200);
});
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);
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);
console.log(event.code);
console.log(event.ctrlKey);
console.log(event.shiftKey);
});
🛑 preventDefault و stopPropagation
هاتان الدالتان تتحكمان في سلوك الحدث، لكن بطريقتين مختلفتين:
| الدالة |
ما تفعله |
استخدام شائع |
preventDefault() |
تمنع السلوك الافتراضي للمتصفح |
منع إرسال نموذج، منع فتح رابط |
stopPropagation() |
توقف انتشار الحدث للعناصر الأب |
منع تفعيل مستمعات الأب |
document.querySelector("a").addEventListener("click", (e) => {
e.preventDefault();
console.log("تم منع الانتقال!");
});
document.getElementById("child").addEventListener("click", (e) => {
e.stopPropagation();
console.log("نقر على الابن فقط");
});
🎯 Event Delegation — أداء أفضل
بدلاً من إضافة مستمع لكل عنصر، أضف مستمعاً واحداً للأب واستخدم event.target لمعرفة العنصر المُنقر:
document.querySelectorAll(".delete-btn").forEach(btn => {
btn.addEventListener("click", deleteItem);
});
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: البديل القديم