JavaScript Notes
Learn JavaScript mouse events: click, dblclick, mousemove, mouseenter, mouseleave, drag & drop, coordinates, and event properties with examples.
Introduction
Mouse events are the most commonly used events in JavaScript. Whenever a user clicks on a webpage, hovers, scrolls, or drags — all of these are handled through mouse events.
Mouse events are events triggered by user mouse (or trackpad/touchpad) actions. The browser automatically creates a MouseEvent object that contains all information about the event — such as the mouse position, which button was pressed, and which modifier keys (Ctrl, Shift) were held.
Real-world use cases:
- Handling button clicks
- Drag-and-drop functionality
- Custom right-click context menus
- Image sliders and carousels
- Drawing applications (Canvas)
- Tooltip and hover effects
- Mouse position tracking (games, animations)
Mouse events are an important subset of DOM events and inherit from the Event interface. Every mouse event generates a MouseEvent object that provides coordinates, button info, and modifier key states.
Mouse Event Types and Coordinate System
Types of Mouse Events
1. click Event
When a user clicks on an element with the left mouse button (mousedown + mouseup on the same element), the click event fires.
const btn = document.getElementById("myBtn");
btn.addEventListener("click", function(event) {
console.log("Button clicked!");
console.log("Click position:", event.clientX, event.clientY);
});Button clicked! Click position: 245 120
2. dblclick Event
Fires on a double click. Note: 2 click events also fire before dblclick.
const box = document.getElementById("box");
box.addEventListener("dblclick", function(event) {
console.log("Double clicked!");
console.log("Detail (click count):", event.detail);
});Double clicked! Detail (click count): 2
3. mousedown Event
Fires immediately when a mouse button is pressed (before release). Left, right, or middle — any button.
const area = document.getElementById("area");
area.addEventListener("mousedown", function(event) {
console.log("Mouse button pressed down");
console.log("Button code:", event.button);
// 0 = left, 1 = middle, 2 = right
});Mouse button pressed down Button code: 0
4. mouseup Event
Fires when a mouse button is released.
const area = document.getElementById("area");
area.addEventListener("mouseup", function(event) {
console.log("Mouse button released");
console.log("Button code:", event.button);
});Mouse button released Button code: 0
5. mousemove Event
Fires continuously when the mouse pointer moves. Warning: This fires very frequently — be mindful of performance.
const canvas = document.getElementById("canvas");
canvas.addEventListener("mousemove", function(event) {
console.log("Mouse position:", event.clientX, event.clientY);
});
// Fires on every pixel move!Mouse position: 100 50 Mouse position: 101 50 Mouse position: 102 51 Mouse position: 103 51
6. mouseenter Event
Fires when the pointer enters the element's boundary. Does NOT bubble. Does not fire again when entering child elements.
const card = document.getElementById("card");
card.addEventListener("mouseenter", function(event) {
console.log("Mouse entered the card");
console.log("Related target:", event.relatedTarget?.tagName);
});Mouse entered the card Related target: BODY
7. mouseleave Event
Fires when the pointer leaves the element. Does NOT bubble. Does not fire when leaving child elements.
const card = document.getElementById("card");
card.addEventListener("mouseleave", function(event) {
console.log("Mouse left the card");
console.log("Going to:", event.relatedTarget?.tagName);
});Mouse left the card Going to: BODY
8. mouseover Event
Similar to mouseenter BUT this bubbles. Also fires when moving to child elements (on the parent).
const parent = document.getElementById("parent");
parent.addEventListener("mouseover", function(event) {
console.log("Mouseover on:", event.target.tagName);
console.log("Currently over:", event.target.id);
});Mouseover on: DIV Currently over: parent Mouseover on: SPAN Currently over: child
9. mouseout Event
Similar to mouseleave BUT this bubbles. Also fires when moving to the element's child.
const parent = document.getElementById("parent");
parent.addEventListener("mouseout", function(event) {
console.log("Mouseout from:", event.target.tagName);
console.log("Going to:", event.relatedTarget?.tagName);
});Mouseout from: DIV Going to: SPAN
10. contextmenu Event
Fires on right-click. Used for building custom context menus.
const element = document.getElementById("myElement");
element.addEventListener("contextmenu", function(event) {
event.preventDefault(); // Default menu block
console.log("Custom context menu at:", event.clientX, event.clientY);
});Custom context menu at: 300 200
11. wheel Event
Fires on mouse wheel scroll. deltaY positive = scroll down, negative = scroll up.
const scrollArea = document.getElementById("scrollArea");
scrollArea.addEventListener("wheel", function(event) {
event.preventDefault();
console.log("Scroll delta Y:", event.deltaY);
console.log("Scroll direction:", event.deltaY > 0 ? "Down" : "Up");
});Scroll delta Y: 100 Scroll direction: Down
Mouse Event Properties
Every mouse event generates a MouseEvent object that contains these important properties:
Position Properties
| Property | Description | Relative To |
|---|---|---|
clientX | Horizontal position | Viewport (visible area) |
clientY | Vertical position | Viewport (visible area) |
pageX | Horizontal position | Full document (includes scroll) |
pageY | Vertical position | Full document (includes scroll) |
offsetX | Horizontal position | Target element's padding edge |
offsetY | Vertical position | Target element's padding edge |
screenX | Horizontal position | User's screen/monitor |
screenY | Vertical position | User's screen/monitor |
Button Properties
| Property | Description |
|---|---|
button | Which button was pressed (0=left, 1=middle, 2=right) |
buttons | Bitmask of currently pressed buttons |
buttons bitmask values:
- 0 = no button
- 1 = left button
- 2 = right button
- 4 = middle button
- Multiple buttons: values add up (e.g., 3 = left + right)
Modifier Key Properties
| Property | Description |
|---|---|
ctrlKey | true if Ctrl key was held |
shiftKey | true if Shift key was held |
altKey | true if Alt key was held |
metaKey | true if Meta/Cmd key was held |
document.addEventListener("click", function(event) {
console.log("=== Mouse Event Properties ===");
console.log("clientX:", event.clientX, "clientY:", event.clientY);
console.log("pageX:", event.pageX, "pageY:", event.pageY);
console.log("offsetX:", event.offsetX, "offsetY:", event.offsetY);
console.log("button:", event.button);
console.log("ctrlKey:", event.ctrlKey);
console.log("shiftKey:", event.shiftKey);
console.log("altKey:", event.altKey);
});=== Mouse Event Properties === clientX: 250 clientY: 180 pageX: 250 pageY: 580 offsetX: 50 offsetY: 30 button: 0 ctrlKey: false shiftKey: false altKey: false
Code Examples
Example 1: Click Counter and Tracker
Click #1 Position: (120, 45) Target: DIV Timestamp: 1523.45ms Click #3 Position: (150, 60) Target: DIV Timestamp: 2890.12ms ---
Example 2: Real-time Mouse Position Tracker
// Mouse position tracker with all coordinate types
const tracker = document.getElementById("tracker");
const display = document.getElementById("display");
tracker.addEventListener("mousemove", function(event) {
const info = {
client: `clientX: ${event.clientX}, clientY: ${event.clientY}`,
page: `pageX: ${event.pageX}, pageY: ${event.pageY}`,
offset: `offsetX: ${event.offsetX}, offsetY: ${event.offsetY}`,
screen: `screenX: ${event.screenX}, screenY: ${event.screenY}`
};
console.log("=== Position Update ===");
console.log("Viewport:", info.client);
console.log("Page:", info.page);
console.log("Element:", info.offset);
console.log("Screen:", info.screen);
});
// Throttled version for better performance
function throttle(fn, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn.apply(this, args);
}
};
}
tracker.addEventListener("mousemove", throttle(function(event) {
console.log(`Throttled: (${event.clientX}, ${event.clientY})`);
}, 100));=== Position Update === Viewport: clientX: 340, clientY: 125 Page: pageX: 340, pageY: 525 Element: offsetX: 140, offsetY: 25 Screen: screenX: 840, screenY: 325 Throttled: (340, 125)
Example 3: Drag Simulation (Mousedown + Mousemove + Mouseup)
// Simple drag simulation
const draggable = document.getElementById("draggable");
let isDragging = false;
let startX, startY, initialLeft, initialTop;
draggable.addEventListener("mousedown", function(event) {
isDragging = true;
startX = event.clientX;
startY = event.clientY;
initialLeft = draggable.offsetLeft;
initialTop = draggable.offsetTop;
console.log(`Drag START at (${startX}, ${startY})`);
console.log(`Element position: left=${initialLeft}, top=${initialTop}`);
});
document.addEventListener("mousemove", function(event) {
if (!isDragging) return;
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
const newLeft = initialLeft + deltaX;
const newTop = initialTop + deltaY;
draggable.style.left = newLeft + "px";
draggable.style.top = newTop + "px";
console.log(`Dragging... moved (${deltaX}, ${deltaY}) → position (${newLeft}, ${newTop})`);
});
document.addEventListener("mouseup", function(event) {
if (!isDragging) return;
isDragging = false;
const totalDeltaX = event.clientX - startX;
const totalDeltaY = event.clientY - startY;
console.log(`Drag END. Total movement: (${totalDeltaX}, ${totalDeltaY})`);
});Drag START at (200, 150) Element position: left=100, top=50 Dragging... moved (10, 5) → position (110, 55) Dragging... moved (25, 12) → position (125, 62) Dragging... moved (50, 30) → position (150, 80) Dragging... moved (80, 45) → position (180, 95) Drag END. Total movement: (80, 45)
Example 4: Hover Effects with mouseenter and mouseleave
Card 1: mouseenter → scale up From element: outside Card 1: mouseleave → scale down Going to: outside Card 2: mouseenter → scale up From element: outside Hover started Hover ended. Duration: 1523ms
Example 5: Custom Right-Click Context Menu
Button pressed: Right (code: 2) Custom context menu opened Position: (350, 200) Menu items: [Copy, Paste, Delete, Properties] Menu action selected: Delete Context menu closed
Example 6: mouseenter vs mouseover Difference Demo
--- Move: outside → parent --- mouseenter #1 - target: parent mouseover #1 - target: parent --- Move: parent → child --- mouseover #2 - target: child --- Move: child → parent --- mouseover #3 - target: parent --- Move: parent → outside --- Total mouseover fires: 3 Total mouseenter fires: 1 mouseover fires MORE because it responds to child transitions
Example 7: Mouse Button and Modifier Key Detection
=== Mouse Button Event === Button: Left (Primary) Buttons bitmask: 1 Modifiers: Ctrl → Ctrl+Click detected! (Open in new tab behavior)
Example 8: Wheel Event for Custom Scroll Behavior
// Custom zoom with mouse wheel
const zoomArea = document.getElementById("zoomArea");
let zoomLevel = 100;
zoomArea.addEventListener("wheel", function(event) {
event.preventDefault(); // Prevent page scroll
// deltaY: positive = scroll down, negative = scroll up
const direction = event.deltaY > 0 ? "out" : "in";
const step = 10;
if (direction === "in" && zoomLevel < 200) {
zoomLevel += step;
} else if (direction === "out" && zoomLevel > 50) {
zoomLevel -= step;
}
this.style.transform = `scale(${zoomLevel / 100})`;
console.log(`Wheel: deltaY=${event.deltaY}, direction=${direction}`);
console.log(`Zoom level: ${zoomLevel}%`);
console.log(`deltaMode: ${event.deltaMode} (0=pixels, 1=lines, 2=pages)`);
});Wheel: deltaY=-100, direction=in Zoom level: 110% deltaMode: 0 (0=pixels, 1=lines, 2=pages) Wheel: deltaY=-100, direction=in Zoom level: 120% deltaMode: 0 (0=pixels, 1=lines, 2=pages) Wheel: deltaY=100, direction=out Zoom level: 110% deltaMode: 0 (0=pixels, 1=lines, 2=pages)
mouseenter vs mouseover – Key Difference
These two events seem confusing but there is one critical difference between them:
Key takeaway: mouseenter only fires when the mouse actually crosses the element's BOUNDARY. It does not fire when moving between child elements. mouseover fires every time the target changes, even if it's a child element.
🚫 Common Mistakes
Mistake 1: Using mouseover for hover effects (causes flickering)
// ❌ WRONG - mouseover fires repeatedly when moving over child elements
const card = document.getElementById("card");
card.addEventListener("mouseover", function() {
this.classList.add("highlight");
console.log("mouseover fired!"); // Fires multiple times!
});
card.addEventListener("mouseout", function() {
this.classList.remove("highlight");
console.log("mouseout fired!"); // Fires when entering child too!
});mouseover fired! mouseout fired! mouseover fired! mouseout fired! mouseover fired!
// ✅ CORRECT - mouseenter/mouseleave for simple hover effects
const card = document.getElementById("card");
card.addEventListener("mouseenter", function() {
this.classList.add("highlight");
console.log("mouseenter fired!"); // Fires once!
});
card.addEventListener("mouseleave", function() {
this.classList.remove("highlight");
console.log("mouseleave fired!"); // Fires once!
});mouseenter fired! mouseleave fired!
Mistake 2: Not throttling mousemove events
// ❌ WRONG - runs expensive code on every pixel movement
document.addEventListener("mousemove", function(event) {
// This fires 60+ times per second!
updateUI(event.clientX, event.clientY);
recalculateLayout();
sendToServer(event.clientX, event.clientY);
console.log("Expensive operation on every move!");
});Expensive operation on every move! Expensive operation on every move! Expensive operation on every move! // ... hundreds of times per second, causing lag
// ✅ CORRECT - throttle mousemove for performance
function throttle(fn, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn.apply(this, args);
}
};
}
document.addEventListener("mousemove", throttle(function(event) {
updateUI(event.clientX, event.clientY);
console.log("Throttled: runs max 10 times/sec");
}, 100)); // Max 10 times per secondThrottled: runs max 10 times/sec Throttled: runs max 10 times/sec Throttled: runs max 10 times/sec
Mistake 3: Confusing event.button values
// ❌ WRONG - assuming button === 1 is left click
element.addEventListener("mousedown", function(event) {
if (event.button === 1) { // This is MIDDLE button, not left!
console.log("Left click"); // WRONG!
}
});// Only fires on middle click, not left click!
// ✅ CORRECT - button values: 0=left, 1=middle, 2=right
element.addEventListener("mousedown", function(event) {
if (event.button === 0) {
console.log("Left click"); // Correct!
} else if (event.button === 1) {
console.log("Middle click");
} else if (event.button === 2) {
console.log("Right click");
}
});Left click
Mistake 4: Attaching mousemove to wrong element for drag
// ❌ WRONG - mousemove on the draggable element
const box = document.getElementById("box");
box.addEventListener("mousedown", function() {
// If mouse moves too fast, it leaves the box and drag breaks!
box.addEventListener("mousemove", function(event) {
box.style.left = event.clientX + "px";
console.log("Dragging..."); // Stops working when cursor leaves box
});
});Dragging... Dragging... // Drag breaks when mouse moves too fast and leaves the element!
// ✅ CORRECT - mousemove on document, mousedown on element
const box = document.getElementById("box");
let isDragging = false;
box.addEventListener("mousedown", function(event) {
isDragging = true;
event.preventDefault(); // Prevent text selection
console.log("Drag started");
});
document.addEventListener("mousemove", function(event) {
if (!isDragging) return;
box.style.left = event.clientX + "px";
box.style.top = event.clientY + "px";
console.log("Smooth dragging..."); // Works even if cursor moves fast
});
document.addEventListener("mouseup", function() {
if (isDragging) {
isDragging = false;
console.log("Drag ended");
}
});Drag started Smooth dragging... Smooth dragging... Smooth dragging... Drag ended
Mistake 5: Forgetting preventDefault on contextmenu
// ❌ WRONG - custom menu shows BUT default menu also appears
element.addEventListener("contextmenu", function(event) {
// Missing event.preventDefault()!
showCustomMenu(event.clientX, event.clientY);
console.log("Custom menu shown");
// Browser's default right-click menu ALSO appears on top!
});Custom menu shown // But browser default menu covers it!
// ✅ CORRECT - prevent default to show ONLY custom menu
element.addEventListener("contextmenu", function(event) {
event.preventDefault(); // Block browser default menu
showCustomMenu(event.clientX, event.clientY);
console.log("Custom menu shown (no default menu)");
});Custom menu shown (no default menu)
Mistake 6: Not handling text selection during drag
// ❌ WRONG - text gets selected while dragging
box.addEventListener("mousedown", function(event) {
isDragging = true;
// Text on the page gets selected while dragging!
});// User sees highlighted text while dragging - bad UX
// ✅ CORRECT - prevent selection during drag
box.addEventListener("mousedown", function(event) {
isDragging = true;
event.preventDefault(); // Prevents text selection
document.body.style.userSelect = "none"; // Extra safety
console.log("Drag started, text selection blocked");
});
document.addEventListener("mouseup", function() {
isDragging = false;
document.body.style.userSelect = ""; // Restore selection
console.log("Drag ended, text selection restored");
});Drag started, text selection blocked Drag ended, text selection restored
🎯 Key Takeaways
- Mouse events fall into 3 categories: Click events (click, dblclick, mousedown, mouseup), Movement events (mousemove, mouseenter, mouseleave, mouseover, mouseout), and Other events (contextmenu, wheel).
- Understand coordinate systems:
clientX/Yis relative to the viewport,pageX/Yincludes scroll offset, andoffsetX/Yis relative to the target element.
- mouseenter vs mouseover:
mouseenterdoes not bubble and does not fire on child elements. Usemouseenter/mouseleavefor simple hover effects.
- Remember event.button values: 0 = Left, 1 = Middle, 2 = Right. These are NOT 1-based!
- ALWAYS throttle mousemove: This event fires on every pixel. Without throttling, you will have performance issues.
- For drag, attach mousemove to document: Attaching it to the element causes drag to break when the mouse moves fast.
- Don't forget event.preventDefault() in contextmenu: Otherwise the browser's default menu will also show alongside your custom menu.
- Event order: mousedown → mouseup → click → (repeat) → dblclick. 2 complete click cycles occur before dblclick.
- relatedTarget property: In mouseenter/mouseleave and mouseover/mouseout, this indicates where the mouse came from or where it's going.
- Block text selection during drag: Use
event.preventDefault()anduser-select: nonefor a smooth drag experience.
❓ Interview Questions
Q1: What is the difference between mouseenter and mouseover?
Answer: The mouseenter event does not bubble and only fires when the mouse pointer crosses the element's boundary. If the mouse moves to a child element, mouseenter does not fire again on the parent. The mouseover event bubbles, and whenever the mouse moves to any new element (including children), it bubbles up to the parent. Use mouseenter/mouseleave for simple hover effects, and mouseover/mouseout for event delegation.
Q2: What is the difference between clientX, pageX, and offsetX?
Answer:
clientX/clientY: Mouse position relative to the viewport (visible browser area). Not affected by scroll.pageX/pageY: Mouse position relative to the full document. Formula:pageX = clientX + window.scrollX. Includes scroll position.offsetX/offsetY: Mouse position relative to the target element's padding edge. Shows the exact position within the element.
Q3: What is the correct sequence of mouse events for drag and drop?
Answer: Correct approach:
mousedownon the draggable element — mark drag startmousemoveon document — update element position (not on the element itself, otherwise fast mouse movement will break the drag)mouseupon document — end the drag- Call
event.preventDefault()inmousedownto prevent text selection
Q4: What is the difference between event.button and event.buttons?
Answer:
event.button: A single value indicating which button was pressed/released in THIS event. Values: 0=left, 1=middle, 2=right.event.buttons: A bitmask indicating which buttons are CURRENTLY pressed. Values are additive: 1=left, 2=right, 4=middle. Example: left+right pressed = 3 (1+2).
button is useful in mousedown/mouseup, while buttons is useful in mousemove (to check if a button is STILL held).
Q5: How do you optimize the mousemove event?
Answer: mousemove fires very frequently (60+ times/second). Optimization techniques:
- Throttling: Limit execution to a time interval (e.g., max 10 calls/sec)
- requestAnimationFrame: Sync with the browser's paint cycle
- Debouncing: Execute after a delay following the last event
- Conditional check: Only process when needed (e.g.,
if (!isDragging) return)
// requestAnimationFrame approach
let rafId = null;
document.addEventListener("mousemove", function(event) {
if (rafId) return;
rafId = requestAnimationFrame(function() {
processMouseMove(event);
rafId = null;
});
});Q6: What is the exact internal sequence of a click event?
Answer: When a user clicks, these events fire in this order:
mousedown— button press hone pemouseup— button release hone peclick— fires after mousedown + mouseup occur on the same element
For double click:
mousedown→mouseup→clickmousedown→mouseup→clickdblclick
If mousedown and mouseup occur on different elements, click does not fire.
Q7: What steps do you follow to implement a custom context menu?
Answer:
- Listen for the
contextmenuevent on the target element - Call
event.preventDefault()to block the default menu - Position the custom menu using
event.clientX/clientY - Add a
clicklistener on document to close the menu - Add click handlers on menu items for actions
Important: For accessibility, also handle keyboard-based context menu (Shift+F10), and keep the menu within the viewport (edge detection).
📝 FAQ
Q: Do mouse events fire on touch devices?
Answer: Yes, mostly! Mobile browsers simulate mouse events on touch for compatibility. After a touch, this sequence occurs: touchstart → touchend → mousemove → mousedown → mouseup → click. However, mousemove, mouseenter, and mouseleave don't work properly on touch. Best practice: In touch-heavy apps, use Pointer Events (pointerdown, pointermove, pointerup) which handle both mouse and touch.
Q: When should event.preventDefault() be used in mouse events?
Answer: Common cases:
- In
contextmenu: To block the default right-click menu - In
mousedown: To prevent text selection (during drag) - In
wheel: To prevent page scroll (custom scroll/zoom) - On
clickfor anchor tags: To prevent default navigation
Caution: Don't use unnecessary preventDefault() — it impacts accessibility.
Q: Why does mousemove cause performance issues?
Answer: mousemove fires on every pixel movement — meaning 60-100+ times per second. If this event handler contains DOM manipulation, network calls, or complex calculations, it blocks the browser's main thread and makes the page laggy. Solution: Use throttling (time-based limit), requestAnimationFrame (paint cycle sync), or debouncing (delay after last event).
Q: Can we programmatically trigger mouse events?
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for JavaScript Mouse Events – Complete Guide with Examples (2026).
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this JavaScript Master Course topic.
Search Terms
javascript-complete-guide, javascript master course, javascript, complete, guide, events, mouse, javascript mouse events – complete guide with examples (2026)
Related JavaScript Master Course Topics