Event Object: Treasure Trove of Event Information
What Is the Event Object
When you click a button, press a key, or move your mouse, the browser doesn't just simply trigger an event. It creates an object containing rich information, recording every aspect of the event: where it occurred, when it occurred, which keys or buttons were involved, which element it occurred on... This object is the Event Object.
Think of the event object as a detailed event report. Just as police record time, location, involved persons, and evidence when arriving at a crime scene, the event object records all data related to that event. With this report, our code can make precise responses based on specific circumstances.
Whenever an event handler or listener is called, the browser automatically creates an event object and passes it as the first parameter:
button.addEventListener("click", function (event) {
// event is the event object
console.log(event);
});Universal Event Properties
All types of event objects inherit from the base Event interface and have some universal properties.
type - Event Type
The type property is a string representing the event type:
button.addEventListener("click", function (event) {
console.log(event.type); // "click"
});
input.addEventListener("input", function (event) {
console.log(event.type); // "input"
});
document.addEventListener("DOMContentLoaded", function (event) {
console.log(event.type); // "DOMContentLoaded"
});If the same handler is used to handle multiple events, you can distinguish based on type:
function handleEvent(event) {
switch (event.type) {
case "mouseenter":
element.style.backgroundColor = "lightblue";
break;
case "mouseleave":
element.style.backgroundColor = "";
break;
case "click":
console.log("Element clicked");
break;
}
}
element.addEventListener("mouseenter", handleEvent);
element.addEventListener("mouseleave", handleEvent);
element.addEventListener("click", handleEvent);target - Event Target
The target property points to the element that actually triggered the event, which is the element the user truly interacted with:
<div id="container">
<button id="btn">
<span>Click me</span>
</button>
</div>const container = document.getElementById("container");
container.addEventListener("click", function (event) {
console.log("Clicked element:", event.target);
// If clicking span: <span>Click me</span>
// If clicking button: <button id="btn">...</button>
// If clicking div's blank area: <div id="container">...</div>
});target is particularly useful in event delegation, allowing you to determine which specific child element was clicked:
const list = document.querySelector(".todo-list");
list.addEventListener("click", function (event) {
if (event.target.matches(".delete-btn")) {
deleteItem(event.target);
} else if (event.target.matches(".edit-btn")) {
editItem(event.target);
} else if (event.target.matches(".checkbox")) {
toggleItem(event.target);
}
});currentTarget - Current Handling Element
currentTarget points to the element that the event handler is bound to, which is the element that called addEventListener:
<div id="outer">
<div id="inner">
<button id="btn">Click</button>
</div>
</div>const outer = document.getElementById("outer");
outer.addEventListener("click", function (event) {
console.log("target:", event.target.id); // btn (actually clicked)
console.log("currentTarget:", event.currentTarget.id); // outer (event bound to)
console.log("this:", this.id); // outer (this points to currentTarget)
});In regular functions, the this keyword is equivalent to event.currentTarget:
button.addEventListener("click", function (event) {
console.log(this === event.currentTarget); // true
console.log(this === event.target); // true (if clicking button directly)
});But in arrow functions, this inherits from the outer scope and is unrelated to currentTarget:
button.addEventListener("click", (event) => {
console.log(this === event.currentTarget); // false
console.log(event.currentTarget); // button element
console.log(this); // outer scope's this
});timeStamp - Event Timestamp
The timeStamp property represents the time when the event was created, as milliseconds from page load:
document.addEventListener("click", function (event) {
console.log("Event occurred at:", event.timeStamp, "ms");
});You can use timestamps to calculate operation intervals:
let lastClickTime = 0;
button.addEventListener("click", function (event) {
const interval = event.timeStamp - lastClickTime;
if (interval < 300) {
console.log("Double-click detected");
}
lastClickTime = event.timeStamp;
});isTrusted - Is Trusted
The isTrusted property indicates whether the event was triggered by user operation (true) or created by script (false):
button.addEventListener("click", function (event) {
if (event.isTrusted) {
console.log("Genuine user click");
} else {
console.log("Script-triggered click");
}
});
// User click: isTrusted is true
// Code trigger: isTrusted is false
button.click();This property can be used to prevent malicious automatic script operations.
Event Flow Related Properties
eventPhase - Event Phase
eventPhase indicates which phase of the event flow the event is currently in:
Event.NONE(0): Event not triggeredEvent.CAPTURING_PHASE(1): Capture phaseEvent.AT_TARGET(2): Target phaseEvent.BUBBLING_PHASE(3): Bubble phase
element.addEventListener(
"click",
function (event) {
console.log("Current phase:", event.eventPhase);
switch (event.eventPhase) {
case Event.CAPTURING_PHASE:
console.log("Capture phase");
break;
case Event.AT_TARGET:
console.log("Target phase");
break;
case Event.BUBBLING_PHASE:
console.log("Bubble phase");
break;
}
},
true
); // Capture phasebubbles - Whether Bubbles
The bubbles property indicates whether this event bubbles:
button.addEventListener("click", function (event) {
console.log("Bubbles:", event.bubbles); // true
});
button.addEventListener("focus", function (event) {
console.log("Bubbles:", event.bubbles); // false
});Most mouse and keyboard events bubble, but some events (such as focus, blur, scroll) don't bubble.
cancelable - Whether Cancelable
The cancelable property indicates whether the event's default behavior can be canceled:
link.addEventListener("click", function (event) {
console.log("Cancelable:", event.cancelable); // true
if (event.cancelable) {
event.preventDefault(); // Can prevent navigation
}
});Mouse Event Properties
Mouse event objects (MouseEvent) contain detailed information related to mouse operations.
Mouse Position
document.addEventListener("click", function (event) {
// Coordinates relative to viewport
console.log("clientX:", event.clientX);
console.log("clientY:", event.clientY);
// Coordinates relative to entire page (including scroll)
console.log("pageX:", event.pageX);
console.log("pageY:", event.pageY);
// Coordinates relative to screen
console.log("screenX:", event.screenX);
console.log("screenY:", event.screenY);
// Coordinates relative to target element
console.log("offsetX:", event.offsetX);
console.log("offsetY:", event.offsetY);
});Coordinate System Comparison:
clientX/Y: Relative to browser window's visible area, excluding scrollpageX/Y: Relative to entire document, including scrolled partscreenX/Y: Relative to entire screenoffsetX/Y: Relative to element that triggered the event
The most commonly used in practical applications are clientX/Y and pageX/Y:
// Create tooltip following mouse
document.addEventListener("mousemove", function (event) {
const tooltip = document.getElementById("tooltip");
tooltip.style.left = event.clientX + 10 + "px";
tooltip.style.top = event.clientY + 10 + "px";
});
// Draw on canvas
canvas.addEventListener("click", function (event) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
drawPoint(x, y);
});Mouse Buttons
element.addEventListener("mousedown", function (event) {
console.log("Button number:", event.button);
switch (event.button) {
case 0:
console.log("Left button");
break;
case 1:
console.log("Middle button (wheel)");
break;
case 2:
console.log("Right button");
break;
}
});
element.addEventListener("click", function (event) {
// buttons property is a bitmask, can detect multiple buttons pressed simultaneously
console.log("Button state:", event.buttons);
// 1 = left, 2 = right, 4 = middle
});Modifier Keys
Detect whether Ctrl, Shift, Alt, or Meta (Windows key or Command key) were pressed simultaneously:
document.addEventListener("click", function (event) {
console.log("Ctrl:", event.ctrlKey);
console.log("Shift:", event.shiftKey);
console.log("Alt:", event.altKey);
console.log("Meta:", event.metaKey);
if (event.ctrlKey && event.key === "s") {
event.preventDefault();
console.log("Ctrl+S shortcut");
}
});Practical applications:
// Multi-select functionality
itemList.addEventListener("click", function (event) {
if (!event.target.matches(".item")) return;
if (event.ctrlKey || event.metaKey) {
// Ctrl/Cmd + click: add to selection
toggleItemSelection(event.target);
} else if (event.shiftKey) {
// Shift + click: range selection
selectRange(lastSelectedItem, event.target);
} else {
// Normal click: single selection
selectSingleItem(event.target);
}
});
// Right-click menu
element.addEventListener("contextmenu", function (event) {
if (event.shiftKey) {
// Shift + right-click: show browser default menu
return;
}
event.preventDefault();
showCustomMenu(event.clientX, event.clientY);
});Movement Distance
movementX and movementY represent the mouse's movement distance relative to the previous event:
let totalMovement = 0;
document.addEventListener("mousemove", function (event) {
const distance = Math.sqrt(event.movementX ** 2 + event.movementY ** 2);
totalMovement += distance;
console.log("Total movement distance:", totalMovement);
});Keyboard Event Properties
Keyboard event objects (KeyboardEvent) contain key information.
key - Key Value
The key property returns a string representation of the pressed key:
document.addEventListener("keydown", function (event) {
console.log("Key:", event.key);
// Letters and numbers: 'a', 'b', '1', '2'
// Special keys: 'Enter', 'Escape', 'Tab', 'Backspace'
// Arrow keys: 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'
// Function keys: 'F1', 'F2', ... 'F12'
// Modifier keys: 'Control', 'Shift', 'Alt', 'Meta'
});Practical applications:
// Shortcuts
document.addEventListener("keydown", function (event) {
if (event.ctrlKey || event.metaKey) {
switch (event.key) {
case "s":
event.preventDefault();
saveDocument();
break;
case "z":
event.preventDefault();
undo();
break;
case "y":
event.preventDefault();
redo();
break;
}
}
if (event.key === "Escape") {
closeModal();
}
});
// Arrow key navigation
document.addEventListener("keydown", function (event) {
switch (event.key) {
case "ArrowUp":
event.preventDefault();
selectPreviousItem();
break;
case "ArrowDown":
event.preventDefault();
selectNextItem();
break;
case "Enter":
confirmSelection();
break;
}
});code - Physical Key Code
The code property returns the physical position code of the key, unaffected by keyboard layout:
document.addEventListener("keydown", function (event) {
console.log("key:", event.key); // Changes based on layout
console.log("code:", event.code); // Physical position remains same
// US keyboard: key = 'q', code = 'KeyQ'
// French keyboard: key = 'a', code = 'KeyQ' (same physical key)
});In game development, code is commonly used to ensure consistent key mapping:
const keys = {};
document.addEventListener("keydown", function (event) {
keys[event.code] = true;
});
document.addEventListener("keyup", function (event) {
keys[event.code] = false;
});
function gameLoop() {
if (keys["KeyW"]) {
moveForward();
}
if (keys["KeyS"]) {
moveBackward();
}
if (keys["KeyA"]) {
moveLeft();
}
if (keys["KeyD"]) {
moveRight();
}
requestAnimationFrame(gameLoop);
}repeat - Whether Repeating
The repeat property indicates whether the key press is a repeated trigger due to long press:
document.addEventListener("keydown", function (event) {
if (event.repeat) {
console.log("Key repeat");
return; // Ignore repeat events
}
console.log("First press");
});Methods for Controlling Event Behavior
The event object provides several methods to control event propagation and default behavior.
preventDefault() - Prevent Default Behavior
Many events have browser-preset default behaviors; preventDefault() can prevent these behaviors:
// Prevent form submission
form.addEventListener("submit", function (event) {
event.preventDefault();
// Use AJAX to submit
const formData = new FormData(event.target);
submitFormViaAjax(formData);
});
// Prevent link navigation
link.addEventListener("click", function (event) {
event.preventDefault();
// Use router navigation
navigateTo(event.target.href);
});
// Prevent right-click menu
element.addEventListener("contextmenu", function (event) {
event.preventDefault();
// Show custom menu
showCustomMenu(event.clientX, event.clientY);
});
// Prevent drag default behavior
dropZone.addEventListener("dragover", function (event) {
event.preventDefault(); // Allow dropping
});
dropZone.addEventListener("drop", function (event) {
event.preventDefault(); // Prevent browser from opening file
const files = event.dataTransfer.files;
handleFiles(files);
});stopPropagation() - Stop Propagation
stopPropagation() prevents the event from continuing to propagate (bubble or capture):
button.addEventListener("click", function (event) {
event.stopPropagation(); // Event won't bubble to parent elements
console.log("Only this handler executes");
});
document.addEventListener("click", function () {
// If clicking button, this handler won't execute
console.log("Document clicked");
});Typical application scenarios:
// Modal internal click doesn't close
const modal = document.querySelector(".modal");
const modalContent = modal.querySelector(".modal-content");
modal.addEventListener("click", function () {
closeModal(); // Clicking background closes
});
modalContent.addEventListener("click", function (event) {
event.stopPropagation(); // Clicking content doesn't close
});
// Dropdown menu
const dropdown = document.querySelector(".dropdown");
const menu = dropdown.querySelector(".menu");
dropdown.addEventListener("click", function (event) {
event.stopPropagation();
toggleMenu();
});
document.addEventListener("click", function () {
closeAllMenus(); // Clicking elsewhere closes all menus
});stopImmediatePropagation() - Stop Immediately
stopImmediatePropagation() not only prevents event propagation but also prevents other listeners on the same element from executing:
button.addEventListener("click", function (event) {
console.log("First listener");
event.stopImmediatePropagation();
});
button.addEventListener("click", function () {
console.log("Second listener"); // Won't execute
});
button.addEventListener("click", function () {
console.log("Third listener"); // Won't execute
});
// After clicking, only outputs: First listenerWhile stopPropagation() only prevents propagation and doesn't affect other listeners on the same element:
button.addEventListener("click", function (event) {
console.log("First listener");
event.stopPropagation();
});
button.addEventListener("click", function () {
console.log("Second listener"); // Still executes
});
// After clicking, outputs:
// First listener
// Second listenerdefaultPrevented - Check If Prevented
The defaultPrevented property can check whether preventDefault() has been called:
link.addEventListener("click", function (event) {
if (!event.defaultPrevented) {
// Other listeners haven't prevented default behavior
console.log("Will navigate");
} else {
console.log("Navigation prevented");
}
});Real-World Application Scenarios
Custom Context Menu
const contextMenu = document.getElementById("context-menu");
document.addEventListener("contextmenu", function (event) {
event.preventDefault();
// Position menu
contextMenu.style.left = event.clientX + "px";
contextMenu.style.top = event.clientY + "px";
contextMenu.style.display = "block";
// Store target element
contextMenu.dataset.target = event.target.id;
});
// Click elsewhere to close menu
document.addEventListener("click", function () {
contextMenu.style.display = "none";
});
// Menu item click
contextMenu.addEventListener("click", function (event) {
event.stopPropagation();
const action = event.target.dataset.action;
const targetId = this.dataset.target;
const targetElement = document.getElementById(targetId);
switch (action) {
case "copy":
copyElement(targetElement);
break;
case "delete":
deleteElement(targetElement);
break;
case "edit":
editElement(targetElement);
break;
}
this.style.display = "none";
});Drag-and-Drop Sorting
let draggedElement = null;
const items = document.querySelectorAll(".sortable-item");
items.forEach((item) => {
item.addEventListener("dragstart", function (event) {
draggedElement = this;
event.dataTransfer.effectAllowed = "move";
this.classList.add("dragging");
});
item.addEventListener("dragend", function () {
this.classList.remove("dragging");
});
item.addEventListener("dragover", function (event) {
event.preventDefault(); // Allow dropping
event.dataTransfer.dropEffect = "move";
if (draggedElement !== this) {
// Determine insertion position based on mouse position
const rect = this.getBoundingClientRect();
const midpoint = rect.top + rect.height / 2;
if (event.clientY < midpoint) {
this.parentNode.insertBefore(draggedElement, this);
} else {
this.parentNode.insertBefore(draggedElement, this.nextSibling);
}
}
});
});Keyboard Navigation
class KeyboardNavigator {
constructor(items) {
this.items = Array.from(items);
this.currentIndex = 0;
this.init();
}
init() {
document.addEventListener("keydown", this.handleKeyDown.bind(this));
}
handleKeyDown(event) {
switch (event.key) {
case "ArrowDown":
case "j":
event.preventDefault();
this.selectNext();
break;
case "ArrowUp":
case "k":
event.preventDefault();
this.selectPrevious();
break;
case "Enter":
event.preventDefault();
this.activateCurrent();
break;
case "Home":
event.preventDefault();
this.selectFirst();
break;
case "End":
event.preventDefault();
this.selectLast();
break;
}
}
selectNext() {
this.currentIndex = Math.min(this.currentIndex + 1, this.items.length - 1);
this.updateSelection();
}
selectPrevious() {
this.currentIndex = Math.max(this.currentIndex - 1, 0);
this.updateSelection();
}
selectFirst() {
this.currentIndex = 0;
this.updateSelection();
}
selectLast() {
this.currentIndex = this.items.length - 1;
this.updateSelection();
}
updateSelection() {
this.items.forEach((item, index) => {
if (index === this.currentIndex) {
item.classList.add("selected");
item.scrollIntoView({ block: "nearest" });
} else {
item.classList.remove("selected");
}
});
}
activateCurrent() {
const current = this.items[this.currentIndex];
current.click();
}
}
// Usage
const navigator = new KeyboardNavigator(document.querySelectorAll(".nav-item"));Double-Click to Edit
const items = document.querySelectorAll(".editable-item");
items.forEach((item) => {
let clickCount = 0;
let clickTimer = null;
item.addEventListener("click", function (event) {
clickCount++;
if (clickCount === 1) {
clickTimer = setTimeout(() => {
// Single click
selectItem(this);
clickCount = 0;
}, 300);
} else if (clickCount === 2) {
// Double click
clearTimeout(clickTimer);
clickCount = 0;
startEditing(this);
}
});
});
function startEditing(element) {
const input = document.createElement("input");
input.value = element.textContent;
input.classList.add("edit-input");
const finishEdit = () => {
element.textContent = input.value;
input.remove();
};
input.addEventListener("blur", finishEdit);
input.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
finishEdit();
} else if (event.key === "Escape") {
input.remove();
}
});
element.textContent = "";
element.appendChild(input);
input.focus();
input.select();
}Summary
The event object is the most central part of the event system, containing all information about the event. Through this chapter, you should master:
- Universal Properties:
type,target,currentTarget,timeStamp, etc. - Mouse Events: Position coordinates, button states, modifier keys
- Keyboard Events:
key,code, modifier keys,repeat - Control Methods:
preventDefault(),stopPropagation(),stopImmediatePropagation() - Practical Applications: Custom menus, drag-and-drop, keyboard navigation, etc.