AiTechWorlds
AiTechWorlds
Events are how web pages respond to user actions clicks, keystrokes, form submissions, mouse movements. Understanding how events work under the hood, especially event bubbling and delegation, is what separates junior from senior web developers.
const button = document.querySelector("#submit");
// addEventListener is the modern approach
button.addEventListener("click", function(event) {
console.log("Clicked!", event);
});
// Arrow function shorthand
button.addEventListener("click", (e) => handleClick(e));
// Separate handler (best for removing listeners later)
function handleClick(event) {
console.log("Button clicked");
}
button.addEventListener("click", handleClick);
// Remove a listener — you need the exact same function reference
button.removeEventListener("click", handleClick);button.addEventListener("click", (event) => {
event.type; // "click"
event.target; // the element that was clicked
event.currentTarget; // the element the listener is on
event.timeStamp; // when it happened (milliseconds)
// Mouse events
event.clientX; // cursor X relative to viewport
event.clientY; // cursor Y relative to viewport
event.button; // 0=left, 1=middle, 2=right
// Keyboard events
event.key; // "Enter", "a", "ArrowUp"
event.code; // "KeyA", "Enter" (physical key)
event.shiftKey; // modifier key states
event.ctrlKey;
event.altKey;
event.metaKey; // Cmd on Mac, Windows key on PC
// Prevent default browser behavior
event.preventDefault(); // stop form submit, stop link navigation
// Stop event from bubbling up
event.stopPropagation();
});// Mouse
el.addEventListener("click", handler);
el.addEventListener("dblclick", handler);
el.addEventListener("mousedown", handler);
el.addEventListener("mouseup", handler);
el.addEventListener("mouseover", handler); // fires on child elements too
el.addEventListener("mouseenter", handler); // only fires on the element itself
el.addEventListener("mouseleave", handler);
el.addEventListener("mousemove", handler);
// Keyboard (on document or input elements)
document.addEventListener("keydown", handler); // fires while held
document.addEventListener("keypress", handler); // deprecated
document.addEventListener("keyup", handler); // fires on release
// Form
form.addEventListener("submit", handler);
input.addEventListener("change", handler); // after value changes & loses focus
input.addEventListener("input", handler); // every keystroke
input.addEventListener("focus", handler);
input.addEventListener("blur", handler); // loses focus
// Window/document
window.addEventListener("load", handler); // page fully loaded
document.addEventListener("DOMContentLoaded", handler); // DOM ready (before images)
window.addEventListener("resize", handler);
window.addEventListener("scroll", handler);
window.addEventListener("beforeunload", handler); // user leaving pageEvents bubble up through the DOM from the target element all the way to the document:
// HTML: <div id="outer"><div id="inner"><button>Click</button></div></div>
document.querySelector("button").addEventListener("click", () => {
console.log("button"); // fires first
});
document.querySelector("#inner").addEventListener("click", () => {
console.log("inner"); // fires second
});
document.querySelector("#outer").addEventListener("click", () => {
console.log("outer"); // fires third
});
// Clicking the button logs: "button", "inner", "outer"
// Stop bubbling
button.addEventListener("click", (e) => {
e.stopPropagation(); // inner and outer don't receive the event
handleButtonClick();
});Instead of adding listeners to every child, add ONE listener to a parent:
// Avoid — listener on every item (slow, doesn't work for dynamic items)
document.querySelectorAll(".list-item").forEach(item => {
item.addEventListener("click", handleItemClick);
});
// Prefer — one listener on the parent
const list = document.querySelector(".list");
list.addEventListener("click", (e) => {
// Find the closest list-item ancestor of what was clicked
const item = e.target.closest(".list-item");
if (!item) return; // click was on the list but not an item
handleItemClick(item);
});Why delegation is powerful:
// Full example: delegated actions
const table = document.querySelector("#users-table");
table.addEventListener("click", (e) => {
const row = e.target.closest("tr[data-user-id]");
if (!row) return;
const userId = row.dataset.userId;
if (e.target.matches(".edit-btn")) editUser(userId);
if (e.target.matches(".delete-btn")) deleteUser(userId);
if (e.target.matches(".view-btn")) viewUser(userId);
});// Prevent form submission
form.addEventListener("submit", (e) => {
e.preventDefault(); // stops page reload
validateAndSubmit(form);
});
// Prevent link navigation
link.addEventListener("click", (e) => {
e.preventDefault();
router.navigate(link.href); // use custom router instead
});
// Prevent context menu on right-click
canvas.addEventListener("contextmenu", (e) => e.preventDefault());// Options object as third argument
button.addEventListener("click", handler, {
once: true, // auto-removes after first fire
passive: true, // tells browser the handler won't call preventDefault (perf boost)
capture: true // capture phase instead of bubble phase
});
// Passive listeners are especially important for scroll/touch
window.addEventListener("scroll", onScroll, { passive: true }); // 60fps!
window.addEventListener("touchstart", onTouch, { passive: false }); // can preventDefaultSome events fire constantly (scroll, resize, mousemove). Throttle/debounce limits how often your handler runs:
// Debounce — wait until the user stops
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// Only search after user stops typing for 300ms
const search = debounce((query) => fetchResults(query), 300);
input.addEventListener("input", (e) => search(e.target.value));
// Throttle — fire at most once per interval
function throttle(fn, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Handle scroll at most 60fps
const onScroll = throttle(() => updateScrollProgress(), 16);
window.addEventListener("scroll", onScroll, { passive: true });Next lesson: Forms validation, submission, and handling all input types in JavaScript.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises