לדלג לתוכן

3.4 אירועים הרצאה

מה זה אירוע - event

אירוע הוא כל דבר שקורה בדף - המשתמש לחץ על כפתור, הזיז את העכבר, הקליד על המקלדת, שינה את גודל החלון, או שהדף סיים להיטען. JavaScript מאפשר לנו "להקשיב" לאירועים האלה ולהגיב אליהם.

  • בלי אירועים, אתרים היו סטטיים ולא אינטראקטיביים
  • אירועים הם הבסיס לכל אינטראקציה עם המשתמש
  • כמעט כל דבר שקורה בדפדפן יוצר אירוע

addEventListener - הדרך הנכונה

המתודה addEventListener מאפשרת לנו להוסיף listener (מאזין) שירוץ כשאירוע מסוים קורה:

<button id="my-btn">Click me!</button>
const btn = document.getElementById("my-btn");

// add a click event listener
btn.addEventListener("click", function() {
    console.log("Button was clicked!");
});

התחביר: element.addEventListener(eventType, handlerFunction)

  • הפרמטר הראשון הוא סוג האירוע כמחרוזת
  • הפרמטר השני הוא הפונקציה שתרוץ כשהאירוע קורה (נקראת handler או callback)
  • אפשר להוסיף כמה listeners לאותו אירוע על אותו אלמנט
const btn = document.getElementById("my-btn");

// multiple listeners on the same event
btn.addEventListener("click", function() {
    console.log("First handler");
});

btn.addEventListener("click", function() {
    console.log("Second handler");
});

// both will run when the button is clicked

removeEventListener - הסרת מאזין

כדי להסיר listener, צריך לשמור reference לפונקציה:

const btn = document.getElementById("my-btn");

// WRONG - can't remove anonymous functions
btn.addEventListener("click", function() {
    console.log("Can't remove this!");
});

// RIGHT - save the function reference
function handleClick() {
    console.log("This can be removed!");
}

btn.addEventListener("click", handleClick);

// later, remove it
btn.removeEventListener("click", handleClick);
  • חייבים להעביר את אותה פונקציה בדיוק (אותו reference)
  • פונקציות אנונימיות לא ניתנות להסרה כי אין להן reference

סוגי אירועים

אירועי עכבר - mouse events

<div id="box" style="width: 200px; height: 200px; background: lightblue; padding: 20px;">
    Hover and click me!
</div>
const box = document.getElementById("box");

// click - left mouse button click (down + up)
box.addEventListener("click", function() {
    console.log("clicked!");
});

// dblclick - double click
box.addEventListener("dblclick", function() {
    console.log("double clicked!");
});

// mousedown - mouse button pressed down
box.addEventListener("mousedown", function() {
    console.log("mouse button down");
});

// mouseup - mouse button released
box.addEventListener("mouseup", function() {
    console.log("mouse button up");
});

// mouseenter - mouse enters the element (doesn't bubble)
box.addEventListener("mouseenter", function() {
    box.style.backgroundColor = "lightgreen";
});

// mouseleave - mouse leaves the element (doesn't bubble)
box.addEventListener("mouseleave", function() {
    box.style.backgroundColor = "lightblue";
});

// mousemove - mouse moves inside the element
box.addEventListener("mousemove", function(event) {
    console.log("Mouse at:", event.clientX, event.clientY);
});

// contextmenu - right click (can prevent default context menu)
box.addEventListener("contextmenu", function(event) {
    event.preventDefault(); // prevent the default context menu
    console.log("Right clicked! Custom menu could appear here.");
});

ההבדל בין mouseenter/mouseleave ל-mouseover/mouseout:

// mouseover/mouseout - triggers also when entering/leaving child elements
// mouseenter/mouseleave - only triggers for the element itself, not children
// usually mouseenter/mouseleave is what you want

אירועי מקלדת - keyboard events

<input type="text" id="text-input" placeholder="Type something...">
const input = document.getElementById("text-input");

// keydown - fires when a key is pressed
input.addEventListener("keydown", function(event) {
    console.log("Key down:", event.key);
});

// keyup - fires when a key is released
input.addEventListener("keyup", function(event) {
    console.log("Key up:", event.key);
});

// keypress is DEPRECATED - don't use it
// use keydown instead
  • keydown קורה ברגע שהמקש נלחץ
  • keyup קורה כשהמקש משוחרר
  • keypress הוא deprecated - לא להשתמש בו

אירועי פוקוס - focus events

const input = document.getElementById("text-input");

// focus - element receives focus
input.addEventListener("focus", function() {
    console.log("Input focused!");
    input.style.borderColor = "blue";
});

// blur - element loses focus
input.addEventListener("blur", function() {
    console.log("Input lost focus!");
    input.style.borderColor = "";
});

// focusin / focusout - like focus/blur but they bubble
// useful for event delegation on forms

אירועי טופס - form events

<form id="my-form">
    <input type="text" id="name" name="name">
    <select id="color">
        <option value="red">Red</option>
        <option value="blue">Blue</option>
    </select>
    <button type="submit">Submit</button>
</form>
const form = document.getElementById("my-form");
const nameInput = document.getElementById("name");
const colorSelect = document.getElementById("color");

// submit - form is submitted
form.addEventListener("submit", function(event) {
    event.preventDefault(); // prevent page reload
    console.log("Form submitted!");
    console.log("Name:", nameInput.value);
    console.log("Color:", colorSelect.value);
});

// input - fires on every change (typing, pasting, etc.)
nameInput.addEventListener("input", function() {
    console.log("Current value:", nameInput.value);
});

// change - fires when value changes AND element loses focus
// for select/checkbox/radio, fires immediately on change
colorSelect.addEventListener("change", function() {
    console.log("Color changed to:", colorSelect.value);
});

// reset - form is reset
form.addEventListener("reset", function() {
    console.log("Form was reset!");
});
  • input - נורה על כל שינוי בזמן אמת (מתאים לחיפוש חי, ולידציה תוך כדי הקלדה)
  • change - נורה כשהערך שונה וה-input מאבד פוקוס (או מיד ב-select, checkbox, radio)

אירועי חלון - window events

// load - everything is loaded (images, CSS, etc.)
window.addEventListener("load", function() {
    console.log("Page fully loaded!");
});

// DOMContentLoaded - DOM is ready
document.addEventListener("DOMContentLoaded", function() {
    console.log("DOM is ready!");
});

// resize - window is resized
window.addEventListener("resize", function() {
    console.log("Window size:", window.innerWidth, "x", window.innerHeight);
});

// scroll - page is scrolled
window.addEventListener("scroll", function() {
    console.log("Scroll position:", window.scrollY);
});

// beforeunload - user is about to leave the page
window.addEventListener("beforeunload", function(event) {
    // show a confirmation dialog
    event.preventDefault();
    // some browsers require returnValue
    event.returnValue = "";
});

אירועי לוח - clipboard events

const input = document.getElementById("text-input");

// copy
input.addEventListener("copy", function(event) {
    console.log("Text copied!");
});

// paste
input.addEventListener("paste", function(event) {
    console.log("Text pasted!");
    // access clipboard data
    const pastedText = event.clipboardData.getData("text");
    console.log("Pasted:", pastedText);
});

// cut
input.addEventListener("cut", function(event) {
    console.log("Text cut!");
});

אירועי גרירה - drag events

<div id="draggable" draggable="true" style="width: 100px; height: 100px; background: orange;">
    Drag me!
</div>
<div id="drop-zone" style="width: 300px; height: 300px; border: 2px dashed gray;">
    Drop here!
</div>
const draggable = document.getElementById("draggable");
const dropZone = document.getElementById("drop-zone");

// on the draggable element
draggable.addEventListener("dragstart", function(event) {
    event.dataTransfer.setData("text/plain", "dragged!");
    console.log("Drag started!");
});

draggable.addEventListener("dragend", function() {
    console.log("Drag ended!");
});

// on the drop zone
dropZone.addEventListener("dragover", function(event) {
    event.preventDefault(); // required to allow drop
    dropZone.style.backgroundColor = "#e3f2fd";
});

dropZone.addEventListener("dragleave", function() {
    dropZone.style.backgroundColor = "";
});

dropZone.addEventListener("drop", function(event) {
    event.preventDefault();
    const data = event.dataTransfer.getData("text/plain");
    console.log("Dropped:", data);
    dropZone.style.backgroundColor = "";
    dropZone.appendChild(draggable);
});

אירועי מגע - touch events

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

box.addEventListener("touchstart", function(event) {
    console.log("Touch started!");
    console.log("Touch position:", event.touches[0].clientX, event.touches[0].clientY);
});

box.addEventListener("touchmove", function(event) {
    console.log("Touch moving...");
});

box.addEventListener("touchend", function() {
    console.log("Touch ended!");
});

אובייקט האירוע - event object

כל handler מקבל אובייקט אירוע שמכיל מידע על מה שקרה:

const btn = document.getElementById("my-btn");

btn.addEventListener("click", function(event) {
    // the event object contains information about the event
    console.log("Event type:", event.type);         // "click"
    console.log("Target element:", event.target);    // the element that was clicked
    console.log("Current target:", event.currentTarget); // the element the listener is on
});

event.target מול event.currentTarget

<div id="parent" style="padding: 20px; background: lightblue;">
    <button id="child">Click me</button>
</div>
const parent = document.getElementById("parent");

parent.addEventListener("click", function(event) {
    console.log("target:", event.target);        // the element that was actually clicked
    console.log("currentTarget:", event.currentTarget); // always the element with the listener
});

// if you click the button:
// target = <button> (what you clicked)
// currentTarget = <div> (where the listener is)

// if you click the div (not the button):
// target = <div> (what you clicked)
// currentTarget = <div> (where the listener is)
  • event.target - האלמנט שהמשתמש באמת לחץ עליו
  • event.currentTarget - האלמנט שה-listener רשום עליו
  • ההבדל חשוב כשעובדים עם event delegation (נלמד בשיעור הבא)

preventDefault - ביטול התנהגות ברירת מחדל

// prevent form submission (page reload)
const form = document.getElementById("my-form");
form.addEventListener("submit", function(event) {
    event.preventDefault();
    console.log("Form handled by JavaScript instead!");
});

// prevent link navigation
const link = document.querySelector("a");
link.addEventListener("click", function(event) {
    event.preventDefault();
    console.log("Link click prevented!");
});

// prevent right-click context menu
document.addEventListener("contextmenu", function(event) {
    event.preventDefault();
});

stopPropagation - עצירת בעבוע

כשאירוע קורה על אלמנט, הוא "מבעבע" (bubbles) למעלה לכל ההורים. אפשר לעצור את זה:

<div id="outer" style="padding: 30px; background: lightcoral;">
    <div id="inner" style="padding: 30px; background: lightblue;">
        <button id="btn">Click</button>
    </div>
</div>
document.getElementById("outer").addEventListener("click", function() {
    console.log("Outer clicked");
});

document.getElementById("inner").addEventListener("click", function() {
    console.log("Inner clicked");
});

document.getElementById("btn").addEventListener("click", function(event) {
    console.log("Button clicked");
    event.stopPropagation(); // stops the event from bubbling up
});

// without stopPropagation: clicking the button prints all three
// with stopPropagation: clicking the button prints only "Button clicked"

נלמד על בעבוע לעומק בשיעור הבא.

מאפייני אירועי עכבר

document.addEventListener("click", function(event) {
    // position relative to the viewport (visible area)
    console.log("clientX:", event.clientX);
    console.log("clientY:", event.clientY);

    // position relative to the entire page (including scroll)
    console.log("pageX:", event.pageX);
    console.log("pageY:", event.pageY);

    // position relative to the screen
    console.log("screenX:", event.screenX);
    console.log("screenY:", event.screenY);

    // which button was pressed (0=left, 1=middle, 2=right)
    console.log("button:", event.button);
});

מאפייני אירועי מקלדת

document.addEventListener("keydown", function(event) {
    // the key that was pressed (readable name)
    console.log("key:", event.key); // "a", "Enter", "ArrowUp", "Shift"

    // the physical key code
    console.log("code:", event.code); // "KeyA", "Enter", "ArrowUp", "ShiftLeft"

    // modifier keys
    console.log("Alt:", event.altKey);
    console.log("Ctrl:", event.ctrlKey);
    console.log("Shift:", event.shiftKey);
    console.log("Meta (Cmd/Win):", event.metaKey);
});
  • event.key - שם המקש (מושפע ממצב Shift ושפה)
  • event.code - קוד המקש הפיזי (תמיד אותו דבר)
// example: detect keyboard shortcuts
document.addEventListener("keydown", function(event) {
    // Ctrl+S (or Cmd+S on Mac)
    if ((event.ctrlKey || event.metaKey) && event.key === "s") {
        event.preventDefault(); // prevent browser save dialog
        console.log("Save shortcut detected!");
    }

    // Escape key
    if (event.key === "Escape") {
        console.log("Escape pressed!");
    }

    // Arrow keys
    if (event.key === "ArrowUp") {
        console.log("Up arrow pressed!");
    }
});

למה לא להשתמש ב-inline handlers

יש דרך ישנה לטפל באירועים ישירות ב-HTML:

<!-- DON'T do this -->
<button onclick="alert('clicked!')">Click me</button>
<button onclick="handleClick()">Click me</button>

למה זה רע:
- ערבוב של HTML ו-JavaScript
- לא ניתן להוסיף כמה handlers לאותו אירוע
- קשה לתחזוקה בפרויקטים גדולים
- בעיות אבטחה (Content Security Policy)
- תמיד נשתמש ב-addEventListener

אפשרויות ל-addEventListener

// once - the handler runs only once, then automatically removes itself
const btn = document.getElementById("my-btn");

btn.addEventListener("click", function() {
    console.log("This will only run once!");
}, { once: true });

// passive - tells the browser this handler won't call preventDefault
// improves scroll performance
window.addEventListener("scroll", function() {
    console.log("Scrolling...");
}, { passive: true });
  • once: true - ה-handler ירוץ פעם אחת בלבד ואחר כך יוסר אוטומטית
  • passive: true - אומר לדפדפן שה-handler לא יקרא ל-preventDefault, מה שמאפשר ביצועים טובים יותר (חשוב במיוחד ל-scroll ו-touch)

אירועים מותאמים - CustomEvent

אפשר ליצור אירועים משלנו:

// create a custom event
const myEvent = new CustomEvent("userLoggedIn", {
    detail: { username: "john", role: "admin" }
});

// listen for the custom event
document.addEventListener("userLoggedIn", function(event) {
    console.log("User logged in:", event.detail.username);
    console.log("Role:", event.detail.role);
});

// dispatch (trigger) the event
document.dispatchEvent(myEvent);
  • CustomEvent מאפשר ליצור אירועים עם מידע מותאם אישית
  • detail מכיל את המידע שרוצים להעביר עם האירוע
  • dispatchEvent שולח את האירוע
  • שימושי ליצירת תקשורת בין חלקים שונים של האפליקציה

דוגמאות מעשיות

דוגמה 1 - מעקב אחרי העכבר

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Mouse Tracker</title>
    <style>
        body { margin: 0; height: 100vh; font-family: Arial, sans-serif; }
        #coords { position: fixed; top: 10px; right: 10px; background: rgba(0,0,0,0.7); color: white; padding: 10px; border-radius: 5px; }
        #dot { width: 20px; height: 20px; background: red; border-radius: 50%; position: absolute; pointer-events: none; transform: translate(-50%, -50%); }
    </style>
</head>
<body>
    <div id="coords">X: 0, Y: 0</div>
    <div id="dot"></div>

    <script>
        const coords = document.getElementById("coords");
        const dot = document.getElementById("dot");

        document.addEventListener("mousemove", function(event) {
            coords.textContent = "X: " + event.clientX + ", Y: " + event.clientY;
            dot.style.left = event.clientX + "px";
            dot.style.top = event.clientY + "px";
        });
    </script>
</body>
</html>

דוגמה 2 - ולידציה בזמן אמת

<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
    <meta charset="UTF-8">
    <title>Live Validation</title>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; }
        .form-group { margin: 15px 0; }
        label { display: block; margin-bottom: 5px; }
        input { padding: 8px; width: 300px; border: 2px solid #ddd; border-radius: 4px; }
        input.valid { border-color: green; }
        input.invalid { border-color: red; }
        .error-message { color: red; font-size: 14px; margin-top: 5px; }
        .success-message { color: green; font-size: 14px; margin-top: 5px; }
    </style>
</head>
<body>
    <h1>Registration Form</h1>
    <form id="registration-form">
        <div class="form-group">
            <label for="username">Username (at least 3 characters):</label>
            <input type="text" id="username">
            <div id="username-feedback"></div>
        </div>
        <div class="form-group">
            <label for="email">Email:</label>
            <input type="email" id="email">
            <div id="email-feedback"></div>
        </div>
        <div class="form-group">
            <label for="password">Password (at least 6 characters):</label>
            <input type="password" id="password">
            <div id="password-feedback"></div>
        </div>
        <button type="submit">Register</button>
    </form>

    <script>
        const form = document.getElementById("registration-form");
        const usernameInput = document.getElementById("username");
        const emailInput = document.getElementById("email");
        const passwordInput = document.getElementById("password");

        function showFeedback(feedbackEl, inputEl, isValid, message) {
            feedbackEl.textContent = message;
            if (isValid) {
                feedbackEl.className = "success-message";
                inputEl.className = "valid";
            } else {
                feedbackEl.className = "error-message";
                inputEl.className = "invalid";
            }
        }

        // validate username on input (real-time)
        usernameInput.addEventListener("input", function() {
            const feedback = document.getElementById("username-feedback");
            const value = usernameInput.value.trim();

            if (value.length === 0) {
                feedback.textContent = "";
                usernameInput.className = "";
            } else if (value.length < 3) {
                showFeedback(feedback, usernameInput, false,
                    "Username must be at least 3 characters");
            } else {
                showFeedback(feedback, usernameInput, true, "Username looks good!");
            }
        });

        // validate email on blur (when leaving the field)
        emailInput.addEventListener("blur", function() {
            const feedback = document.getElementById("email-feedback");
            const value = emailInput.value.trim();

            if (value === "") return;

            if (value.includes("@") && value.includes(".")) {
                showFeedback(feedback, emailInput, true, "Email looks valid!");
            } else {
                showFeedback(feedback, emailInput, false, "Please enter a valid email");
            }
        });

        // validate password on input
        passwordInput.addEventListener("input", function() {
            const feedback = document.getElementById("password-feedback");
            const value = passwordInput.value;

            if (value.length === 0) {
                feedback.textContent = "";
                passwordInput.className = "";
            } else if (value.length < 6) {
                showFeedback(feedback, passwordInput, false,
                    "Password must be at least 6 characters (" + value.length + "/6)");
            } else {
                showFeedback(feedback, passwordInput, true, "Password is strong enough!");
            }
        });

        // handle form submission
        form.addEventListener("submit", function(event) {
            event.preventDefault(); // prevent page reload
            console.log("Form submitted with:");
            console.log("Username:", usernameInput.value);
            console.log("Email:", emailInput.value);
            console.log("Password:", passwordInput.value);
        });
    </script>
</body>
</html>

דוגמה 3 - קיצורי מקלדת

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Keyboard Shortcuts</title>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; }
        #modal { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                 background: white; padding: 30px; border: 2px solid #333; border-radius: 10px;
                 box-shadow: 0 5px 30px rgba(0,0,0,0.3); z-index: 100; }
        #overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
                   background: rgba(0,0,0,0.5); z-index: 99; }
        #log { border: 1px solid #ddd; padding: 10px; height: 200px; overflow-y: auto; }
        .key-display { font-family: monospace; background: #eee; padding: 2px 6px; border-radius: 3px; border: 1px solid #ccc; }
    </style>
</head>
<body>
    <h1>Keyboard Shortcuts Demo</h1>
    <p>Try these shortcuts:</p>
    <ul>
        <li><span class="key-display">Ctrl+K</span> - Open search modal</li>
        <li><span class="key-display">Escape</span> - Close modal</li>
        <li><span class="key-display">Ctrl+B</span> - Toggle bold text</li>
    </ul>
    <p id="styled-text">This text can be toggled bold with Ctrl+B</p>
    <div id="log"></div>

    <div id="overlay"></div>
    <div id="modal">
        <h2>Search</h2>
        <input type="text" id="search-input" placeholder="Search...">
    </div>

    <script>
        const modal = document.getElementById("modal");
        const overlay = document.getElementById("overlay");
        const searchInput = document.getElementById("search-input");
        const styledText = document.getElementById("styled-text");
        const log = document.getElementById("log");

        function addToLog(message) {
            const entry = document.createElement("div");
            entry.textContent = new Date().toLocaleTimeString() + " - " + message;
            log.prepend(entry);
        }

        function openModal() {
            modal.style.display = "block";
            overlay.style.display = "block";
            searchInput.focus();
            addToLog("Modal opened (Ctrl+K)");
        }

        function closeModal() {
            modal.style.display = "none";
            overlay.style.display = "none";
            addToLog("Modal closed (Escape)");
        }

        document.addEventListener("keydown", function(event) {
            // Ctrl+K - open search
            if ((event.ctrlKey || event.metaKey) && event.key === "k") {
                event.preventDefault();
                openModal();
            }

            // Escape - close modal
            if (event.key === "Escape") {
                closeModal();
            }

            // Ctrl+B - toggle bold
            if ((event.ctrlKey || event.metaKey) && event.key === "b") {
                event.preventDefault();
                if (styledText.style.fontWeight === "bold") {
                    styledText.style.fontWeight = "normal";
                } else {
                    styledText.style.fontWeight = "bold";
                }
                addToLog("Bold toggled (Ctrl+B)");
            }
        });

        // close modal when clicking overlay
        overlay.addEventListener("click", closeModal);
    </script>
</body>
</html>

סיכום סוגי אירועים

קטגוריה אירועים
עכבר click, dblclick, mousedown, mouseup, mouseenter, mouseleave, mouseover, mouseout, mousemove, contextmenu
מקלדת keydown, keyup
פוקוס focus, blur, focusin, focusout
טופס submit, change, input, reset
חלון load, DOMContentLoaded, resize, scroll, beforeunload
לוח copy, cut, paste
גרירה drag, dragstart, dragend, dragover, dragleave, drop
מגע touchstart, touchmove, touchend

סיכום אובייקט האירוע

מאפיין/מתודה תיאור
event.type סוג האירוע
event.target האלמנט שהפעיל את האירוע
event.currentTarget האלמנט שה-listener רשום עליו
event.preventDefault() ביטול התנהגות ברירת מחדל
event.stopPropagation() עצירת בעבוע
event.clientX/Y מיקום עכבר ביחס לחלון
event.pageX/Y מיקום עכבר ביחס לדף
event.key שם המקש שנלחץ
event.code קוד המקש הפיזי
event.altKey/ctrlKey/shiftKey/metaKey מקשי modifier
event.button כפתור עכבר (0=שמאל, 1=אמצע, 2=ימין)
  • תמיד נשתמש ב-addEventListener (לא inline handlers)
  • אובייקט האירוע מכיל את כל המידע שצריך על מה שקרה
  • preventDefault מבטל את ההתנהגות הרגילה (שליחת טופס, ניווט לקישור)
  • stopPropagation עוצר את בעבוע האירוע להורים (נלמד בהרחבה בשיעור הבא)
  • אפשרות once: true שימושית כשצריך handler חד-פעמי