رویدادها (Events)

Event چیست، Event Listener، رویدادهای ماوس و صفحه‌کلید و فرم، Bubbling و Capturing، Delegation، preventDefault و stopPropagation، Custom Events و رویدادهای Pointer و Touch.

Event چیست؟

رویداد سیگنالی است که از تعامل کاربر یا سیستم صادر می‌شود — کلیک، تایپ، اسکرول، لود:

المان / پنجره → رویداد → handler (تابع)

Event Listener — شنونده رویداد

const btn = document.querySelector("#save");

// روش مدرن — addEventListener:
btn.addEventListener("click", function (e) {
    console.log("کلیک شد!", e.target);
});

// چند شنونده برای یک رویداد:
btn.addEventListener("click", () => console.log("اول"));
btn.addEventListener("click", () => console.log("دوم"));
// هر دو اجرا می‌شوند!

// حذف — باید همان مرجع تابع باشد:
function handler(e) { console.log("یک بار"); }
btn.addEventListener("click", handler);
btn.removeEventListener("click", handler);
با removeEventListener حتماً anonymous function کار نمی‌کند — مرجع تابع لازم است.

شیء Event

btn.addEventListener("click", (e) => {
    console.log(e.type);         // "click"
    console.log(e.target);       // منبع واقعی کلیک
    console.log(e.currentTarget);// شنونده فعلی
    console.log(e.clientX, e.clientY); // مختصات
    console.log(e.timeStamp);    // زمان از لود صفحه
    console.log(e.ctrlKey);      // Ctrl فشرده؟
});

رویدادهای ماوس

const box = document.querySelector(".box");

box.addEventListener("click", e => console.log("کلیک"));
box.addEventListener("dblclick", e => console.log("دوبل کلیک"));

box.addEventListener("mousedown", e => console.log("پرس شده"));
box.addEventListener("mouseup", e => console.log("رها شده"));

box.addEventListener("mouseenter", e => console.log("ورود — بدون حباب"));
box.addEventListener("mouseleave", e => console.log("خروج"));

box.addEventListener("mousemove", e => {
    // مختصات نسبت به عنصر:
    const rect = box.getBoundingClientRect();
    console.log(e.clientX - rect.left, e.clientY - rect.top);
});

// contextmenu — کلیک راست:
box.addEventListener("contextmenu", e => {
    e.preventDefault();  // منوی پیش‌فرض نیاید
    console.log("منوی سفارشی!");
});

رویدادهای صفحه‌کلید

document.addEventListener("keydown", (e) => {
    console.log(e.key);      // "a", "Enter", "Escape"
    console.log(e.code);     // "KeyA" — فیزیکی
    console.log(e.repeat);   // نگه‌داشته و تکرار؟

    // میانبر Ctrl+S:
    if (e.ctrlKey && e.key === "s") {
        e.preventDefault();
        console.log("ذخیره!");
    }

    // فقط عدد:
    if (!/^\d$/.test(e.key) && !["Backspace", "Delete", "Arrow"].includes(e.key)) {
        e.preventDefault();
    }
});

document.addEventListener("keyup", e => console.log("رها شد:", e.key));
document.addEventListener("keypress", e => console.log("کاراکتر:", e.key));

رویدادهای فرم و ورودی

const input = document.querySelector("input");

// input — هر تغییر فوری:
input.addEventListener("input", (e) => {
    console.log("مقدار:", e.target.value);
});

// change — بعد از blur اگر عوض شده:
input.addEventListener("change", (e) => {
    console.log("تغییر نهایی:", e.target.value);
});

// focus و blur:
input.addEventListener("focus", () => input.classList.add("active"));
input.addEventListener("blur", () => input.classList.remove("active"));

// submit — روی form:
const form = document.querySelector("form");
form.addEventListener("submit", (e) => {
    e.preventDefault();  // جلوگیری از رفرش!
    console.log("ارسال شد");
});

// reset:
form.addEventListener("reset", () => console.log("ریست"));

Clipboard — کپی و رونوشت

// کپی:
document.querySelector("#copy").addEventListener("click", async () => {
    await navigator.clipboard.writeText("متن کپی");
    console.log("کپی شد!");
});

// خواندن:
document.querySelector("#paste").addEventListener("click", async () => {
    const text = await navigator.clipboard.readText();
    console.log("خوانده شد:", text);
});

// رویداد copy/paste:
document.addEventListener("copy", e => {
    e.clipboardData.setData("text/plain", "متن سفارشی");
    e.preventDefault();
});

Drag & Drop — کشیدن و رها کردن

const draggable = document.querySelector(".draggable");
const dropzone = document.querySelector(".dropzone");

// قابل کشیدن:
draggable.addEventListener("dragstart", (e) => {
    e.dataTransfer.setData("text/plain", e.target.id);
    e.dataTransfer.effectAllowed = "move";
});

// مقصد:
dropzone.addEventListener("dragover", (e) => {
    e.preventDefault();  // لازم برای drop!
    dropzone.classList.add("over");
});

dropzone.addEventListener("dragleave", () => {
    dropzone.classList.remove("over");
});

dropzone.addEventListener("drop", (e) => {
    e.preventDefault();
    const id = e.dataTransfer.getData("text/plain");
    const el = document.getElementById(id);
    dropzone.appendChild(el);
    dropzone.classList.remove("over");
});

Touch و Pointer Events

const area = document.querySelector(".touch-area");

// Touch — موبایل:
area.addEventListener("touchstart", (e) => {
    console.log("لمس‌ها:", e.touches.length);   // تعداد انگشت
    console.log("اولین:", e.touches[0].clientX);
});

area.addEventListener("touchmove", (e) => {
    const t = e.touches[0];
    console.log("حرکت:", t.clientX, t.clientY);
});

// Pointer — ماوس + لمس + قلم همه:
area.addEventListener("pointerdown", (e) => {
    console.log(e.pointerType);  // "mouse" | "touch" | "pen"
    console.log(e.pressure);     // فشار
    area.setPointerCapture(e.pointerId);
});

area.addEventListener("pointermove", (e) => {
    if (e.buttons === 1) {  // دکمه چپ فشرده
        console.log("کشیدن به:", e.clientX, e.clientY);
    }
});

area.addEventListener("pointerup", (e) => {
    console.log("پایان");
});

Bubbling و Capturing

رویداد در سه فاز حرکت می‌کند:

// Capturing: document → ... → target (پایین‌روی)
// Target: خود عنصر
// Bubbling: target → ... → document (بالاروی)

// grandparent → parent → child:
document.querySelector(".grand").addEventListener("click", () =>
    console.log("grand"), true);  // true = capturing — اول!

document.querySelector(".parent").addEventListener("click", () =>
    console.log("parent"));  // bubbling — دوم

document.querySelector(".child").addEventListener("click", (e) => {
    console.log("child");   // هدف — سوم (وسط)
    // e.stopPropagation(); // جلوگیری از ادامه حباب
});

// کلیک روی child: grand(capture) → child → parent

Event Delegation — نمایندگی

به‌جای شنونده برای هر عنصر، یکی روی والد — مقیاس‌پذیر و سریع:

// بد — ۱۰۰۰ شنونده برای ۱۰۰۰ دکمه ❌
// خوب — ۱ شنونده روی والد ✓
document.querySelector("#list").addEventListener("click", (e) => {
    // closest — پیدا کردن دکمه واقعی:
    const btn = e.target.closest("button[data-action]");
    if (!btn) return;

    const action = btn.dataset.action;
    const id = btn.dataset.id;

    switch (action) {
        case "edit":   editItem(id);   break;
        case "delete": deleteItem(id); break;
        case "view":   viewItem(id);   break;
    }
});

// عناصر داینامیک هم خودکار پوشش داده می‌شوند!

preventDefault و stopPropagation

document.querySelector("a").addEventListener("click", (e) => {
    e.preventDefault();      // رفتار پیش‌فرض (Navigation) نباشد
    console.log("لینک خنثی شد");
});

document.querySelector(".inner").addEventListener("click", (e) => {
    e.stopPropagation();     // به والد نرو
    e.preventDefault();      // و رفتار پیش‌فرض هم نه
    console.log("فقط inner");
});

// stopImmediatePropagation — حتی شنونده‌های خود عنصر:
btn.addEventListener("click", (e) => {
    e.stopImmediatePropagation();
    console.log("فقط من!");
});
btn.addEventListener("click", () => console.log("من هرگز!"));

Custom Events — رویداد سفارشی

// ساخت:
const event = new CustomEvent("userLogin", {
    detail: { userId: 42, name: "علی" },
    bubbles: true
});

// گوش دادن:
document.addEventListener("userLogin", (e) => {
    console.log("کاربر وارد شد:", e.detail.name);
});

// پرتاب:
document.dispatchEvent(event);

// کاربرد — ارتباط بین اجزای مستقل:
class Cart extends EventTarget {
            #items = [];

            add(item) {
                this.#items.push(item);
                this.dispatchEvent(new CustomEvent("change", {
                    detail: { count: this.#items.length, item }
                }));
            }
        }

const cart = new Cart();
cart.addEventListener("change", e => {
    console.log("سبد آپدیت:", e.detail.count);
});

گزینه‌های addEventListener

el.addEventListener("click", handler, {
    once: true,      // فقط یک بار — خودش حذف می‌شود
    passive: true,   // به مرورگر بگو preventDefault نمی‌کنیم — سریع‌تر برای scroll/touch
    capture: true,   // فاز capture
    signal: controller.signal  // AbortController برای حذف
});

// حذف گروهی با AbortController:
const controller = new AbortController();
el.addEventListener("click", h1, { signal: controller.signal });
el.addEventListener("mouseover", h2, { signal: controller.signal });

// همه یک‌جا حذف:
controller.abort();

جمع‌بندی

کارروش
شنیدنaddEventListener(type, fn)
عناصر داینامیکDelegation با closest
خنثی‌سازی پیش‌فرضpreventDefault
قطع حبابstopPropagation
یک‌بار{ once: true }
سفارشیCustomEvent

این مبحث بخشی از دوره آموزش کامل جاوا اسکریپت است.

ابزارهای مرتبط