Events are how JavaScript responds to user actions and browser activity. Every click, key press, scroll, and page load generates an event that your code can handle.
addEventListener
addEventListener attaches a callback to an element that runs when the event occurs:
const button = document.querySelector("#submit");
button.addEventListener("click", (event) => {
console.log("Button was clicked");
});
The callback receives an event object with details about what happened:
button.addEventListener("click", (event) => {
console.log(event.target); // the element that was clicked
console.log(event.type); // "click"
console.log(event.clientX, event.clientY); // mouse position
});
Common events
| Event | When it fires |
|---|---|
| click | Element is clicked |
| dblclick | Element is double-clicked |
| mousedown / mouseup | Mouse button is pressed / released |
| keydown / keyup | Key is pressed / released |
| input | Input value changes (fires on every keystroke) |
| change | Input value changes and loses focus (for selects and checkboxes) |
| submit | Form is submitted |
| focus / blur | Element gains / loses focus |
| scroll | Element is scrolled |
| resize | Window is resized |
| load | Page or resource finishes loading |
| DOMContentLoaded | HTML is parsed and DOM is ready |
event.target vs this
Inside an event handler, this and event.currentTarget refer to the element the listener is attached to. event.target refers to the element that actually triggered the event.
These differ when the event bubbles up from a child:
<button id="btn">
<span>Click me</span>
</button>
const button = document.querySelector("#btn");
button.addEventListener("click", (event) => {
console.log(event.target); // <span> — the element actually clicked
console.log(this); // <button> — the element with the listener
console.log(event.currentTarget); // <button> — same as this
});
Event delegation
Instead of adding a listener to every child element, add one to the parent and check event.target:
<ul id="list">
<li data-id="1">Item 1</li>
<li data-id="2">Item 2</li>
<li data-id="3">Item 3</li>
</ul>
const list = document.querySelector("#list");
list.addEventListener("click", (event) => {
const item = event.target.closest("li");
if (!item) return; // clicked outside any li
const id = item.dataset.id;
console.log("Clicked item:", id);
});
event.target.closest(selector) walks up the DOM tree from the target until it finds a matching ancestor. This handles clicks on nested elements inside the <li>.
Event delegation is more efficient — one listener instead of many — and it works for elements added dynamically.
event.preventDefault()
preventDefault() stops the browser’s default behavior for an event:
const form = document.querySelector("#signup");
form.addEventListener("submit", (event) => {
event.preventDefault(); // prevent the page from reloading
// handle form submission with JavaScript instead
});
const link = document.querySelector("a");
link.addEventListener("click", (event) => {
event.preventDefault(); // prevent navigation
// handle the click with JavaScript instead
});
event.stopPropagation()
stopPropagation() prevents the event from bubbling up to parent elements:
const child = document.querySelector(".child");
child.addEventListener("click", (event) => {
event.stopPropagation();
console.log("Child clicked");
});
document.querySelector(".parent").addEventListener("click", () => {
console.log("This will not run");
});
This is rarely needed. Most of the time, you want events to bubble so delegation works.
Removing event listeners
removeEventListener detaches a handler. It requires a reference to the same function that was added:
function handleClick(event) {
console.log("clicked");
}
button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick);
Anonymous functions cannot be removed:
button.addEventListener("click", () => console.log("clicked"));
// Cannot remove this — no reference to the function
Listener options
The third argument to addEventListener is an options object:
button.addEventListener("click", handleClick, {
once: true, // automatically remove after first firing
capture: true, // listen during the capture phase (top-down) instead of bubble phase (bottom-up)
passive: true, // promises not to call preventDefault — improves scroll performance
});
What to carry forward
addEventListener(event, callback)attaches a handler to an element- the callback receives an event object with
target,type, and other details event.targetis the element that triggered the event;thisis the element with the listener- event delegation uses one listener on a parent to handle events from many children
event.preventDefault()stops the browser’s default behaviorevent.stopPropagation()prevents bubbling — rarely needed- use
{ once: true }for one-time listeners - most listeners on permanent elements do not need to be removed
Events are how users interact with your app. The next lesson covers forms and input handling — the most common interactive elements.