Mouse, pointer & touch
Handle clicks, hovers, drags, pens, and touch screens with mouse events, pointer events, pointer capture, and HTML drag and drop.
- 01Read mouse button dataUse
button,buttons,relatedTarget, and click-family events accurately. - 02Choose pointer eventsHandle mouse, pen, and touch with one event model and know when compatibility mouse events appear.
- 03Build reliable draggingUse pointer capture or HTML drag and drop depending on the interface.
One vocabulary for pointing at things
Interfaces used to be designed for a mouse first: move the cursor, press a button, release it, maybe double-click. Phones, tablets, drawing pens, trackpads, and hybrid laptops changed the job. A modern web app needs to answer the same question for many devices: where is the pointer, what is it doing, and should this page react?
This lesson gives you three layers of tools. Mouse events are still important for ordinary clicks, hover, and right-click menus. Pointer events are the newer general-purpose API for mouse, pen, and touch. HTML drag and drop is a specialized browser feature for moving draggable items into drop zones.
Instead of writing one control path for a mouse and a different one for a finger, pointer events give you one set of buttons. The event tells you which device pressed the button.
- In real life: One remote with shared buttons
- In JavaScript:
pointerdown,pointermove, andpointerup - In real life: TV, speaker, or projector mode
- In JavaScript:
pointerType:mouse,pen, ortouch - In real life: A device-specific extra, like stylus pressure
- In JavaScript: Properties such as
pressure, when hardware reports them
Where the analogy stops: A real universal remote can control devices at once. A pointer event describes one pointer stream at a time; multi-touch uses separate pointerId values.
A pointer is an input point on the page: a mouse cursor, a finger touching the screen, or a pen tip. Pointer events report that point with device details.
mouseover vs mouseenter
INTERACTIVEHover events are about boundaries. mouseover and mouseout bubble, so a parent can hear movement involving its children. They also carry relatedTarget, the element the pointer came from or is going to. That is useful for event delegation, but noisy for a card with nested icons and text.
If the visitor only cares that they entered the house, use the once-per-house signal. If you want a parent to hear about movement through rooms, use the bubbling signal.
- In real life: Doorbell rings entering the house
- In JavaScript:
mouseenterfires when the pointer enters the element - In real life: Doorbell rings again in each room
- In JavaScript:
mouseoverbubbles as the pointer crosses children - In real life: Looking back at the previous room
- In JavaScript:
relatedTargetpoints to where the pointer came from
Where the analogy stops: A real house has physical rooms. In the DOM, a tiny SVG path, text node boundary, or nested button can act like another room for events.
const parent = document.querySelector(".card"); parent.addEventListener("mouseover", (event) => { console.log("over", event.target.className); console.log("from", event.relatedTarget?.className ?? "outside");}); parent.addEventListener("mouseenter", () => { console.log("entered the whole card");});Move slowly across the emoji, title, and button inside the card.
mouseover/mouseout bubble as you cross child boundaries. mouseenter/mouseleave describe entering or leaving the whole card.
relatedTarget when the browser provides it; moving from outside often reports outside.Pointer events have matching hover names: pointerover/pointerout bubble, while pointerenter/pointerleave do not. Choose the mouse version when you only care about mouse compatibility; choose pointer versions when the same logic belongs with a pointer-driven widget.
Pointer events
UNIVERSALPointer events include pointerdown, pointermove, pointerup, pointercancel, pointerover, pointerout, pointerenter, and pointerleave. They look a lot like mouse events, but add pointerId, pointerType, isPrimary, pressure, and other hardware-aware properties.
Browsers fire compatibility mouse events after pointer events for most inputs, especially so older mouse-based code still works. That means you should not blindly handle both pointerdown and mousedown for the same action, or you may run it twice. Pick pointer events for custom drawing, sliders, resizing, and gestures.
pointercancel appearspointercancel means the browser stopped sending that pointer stream to your code. A common cause is touch scrolling or zooming. For a custom draggable surface, CSS touch-action: none tells the browser your component handles that gesture.
| Question | Use | Details | When it fits |
|---|---|---|---|
| Changed button | event.button | 0 main, 1 middle/auxiliary, 2 secondary | Ask: which button caused this event? |
| Held buttons | event.buttons | Bitmask: 1 primary, 2 secondary, 4 auxiliary | Ask: what is still down during a move? |
| Nested hover | mouseover / mouseout | Bubble and include relatedTarget | Good for delegation or one handler on a parent. |
| Whole hover | mouseenter / mouseleave | Do not bubble | Good when child elements should not restart hover. |
| Universal input | pointerdown / pointermove / pointerup | Mouse, pen, and touch with pointerType | Good for drawing, sliders, resizing, and gestures. |
| Native card transfer | HTML DnD | dragstart, dragover, drop, dataTransfer | Good for desktop-like item movement; touch support varies. |
Pointer capture
INTERACTIVEDragging is fragile if the pointer must stay exactly over the tiny thing being dragged. A fast hand can leave a slider thumb or resize handle before the next move event. setPointerCapture(event.pointerId) retargets later pointer events for that same pointer to the capturing element until release. Browsers automatically release capture after pointerup, and you can also call releasePointerCapture().
You do not need the pointer to stay on the thumb. As long as you hold the string, the thumb can keep following the pointer.
- In real life: Your hand keeps the string
- In JavaScript: The element keeps receiving that pointer’s events
- In real life: The kite drifts away
- In JavaScript: The pointer may move outside the element
- In real life: Letting go
- In JavaScript:
pointeruporreleasePointerCapture()ends capture
Where the analogy stops: A kite string can pull back on the kite. Pointer capture does not move the pointer; it only changes which element receives subsequent events.
thumb.addEventListener("pointerdown", (event) => { thumb.setPointerCapture(event.pointerId); drag(event.clientX);}); thumb.addEventListener("pointermove", (event) => { if (event.buttons === 1) drag(event.clientX);}); thumb.addEventListener("pointerup", (event) => { thumb.releasePointerCapture(event.pointerId);});With capture on, the thumb keeps receiving pointer moves for that pointer until pointerup, even if you drag outside the thumb.
touch-action: none so a touch drag is treated as the component’s gesture instead of page scrolling.thumb.addEventListener("pointerdown", (event) => { thumb.setPointerCapture(event.pointerId); drag(event.clientX);}); thumb.addEventListener("pointermove", (event) => { if (event.buttons === 1) drag(event.clientX);}); thumb.addEventListener("pointerup", (event) => { thumb.releasePointerCapture(event.pointerId);});Drag and drop
INTERACTIVEHTML drag and drop is a separate API for moving data between a draggable source and a drop target. Set draggable="true", write data in dragstart with event.dataTransfer.setData(), allow a target by calling preventDefault() in dragover (and often dragenter), then read the data in drop. The data store is protected while dragging, so custom data is reliably readable in drop, not during dragover.
The browser will not deliver a box to an address unless the address says it accepts deliveries. That is the surprising job of dragover.
- In real life:
dragstartpacks the box - In JavaScript:
dataTransfer.setData()stores the card id - In real life:
dragoveraccepts the address - In JavaScript:
preventDefault()says this target accepts drops - In real life:
dropunpacks - In JavaScript: Read the id and move the element or update state
- In real life:
dragendcloses the job - In JavaScript: Clean up visual state whether the drop worked or not
Where the analogy stops: Moving companies work for every package. Browser drag and drop is mouse-oriented; touch support varies, so many apps build touch-friendly dragging with pointer events and capture instead.
card.draggable = true; card.addEventListener("dragstart", (event) => { event.dataTransfer.setData("text/plain", card.id);}); target.addEventListener("dragover", (event) => { event.preventDefault();}); target.addEventListener("drop", (event) => { const id = event.dataTransfer.getData("text/plain"); target.append(document.getElementById(id));});Use a mouse or trackpad. Many touch devices do not run HTML drag and drop the same way.
Drag a card to another list.
card.draggable = true; card.addEventListener("dragstart", (event) => { event.dataTransfer.setData("text/plain", card.id);}); target.addEventListener("dragover", (event) => { event.preventDefault();}); target.addEventListener("drop", (event) => { const id = event.dataTransfer.getData("text/plain"); target.append(document.getElementById(id));});Where you’ll use this
PRACTICALReal front-end work often mixes these APIs. A design tool canvas uses pointer events for mouse, pen, and touch. A splitter captures the pointer so it keeps resizing even when your hand outruns the handle. A file manager might use HTML drag and drop for desktop cards, but a pointer-event implementation for touch screens.
element.addEventListener("pointerdown", (event) => { if (event.button !== 0) return; element.setPointerCapture(event.pointerId);}); element.addEventListener("pointermove", (event) => { if (event.buttons !== 1) return; drawLineTo(event.clientX, event.clientY);});- Highlight a menu when the pointer enters the whole menu, without flicker over child icons
- Draw with a mouse, finger, or pen
- Move cards between lists with the browser’s built-in drop feedback
- Open a custom right-click menu
- Make a brush thicker when a stylus presses harder
- Let a card carry an id to a drop zone
- Show a tooltip once when entering a button that contains an SVG
- Detect the mouse wheel button opening a special action
Sort each product requirement by the event family it needs most.
Common misconceptions
- “Pointer events are CSS
pointer-events.” No. The JavaScript API is events such aspointerdown; the CSS property controls hit-testing. - “
buttonandbuttonsare the same.” No. One changed; the other is a held-button bitmask. - “
mouseenterbubbles likemouseover.” It does not. Usemouseoverfor delegation. - “Pointer capture captures every pointer.” It captures one
pointerIdfor one element. - “HTML drag and drop works the same on every touch device.” It does not. Hedge, test, and consider pointer events for custom touch dragging.
- “Calling
dropis enough.” The browser normally requirespreventDefault()duringdragoverbefore a drop target accepts the drop.
Practice exercises
5 EXERCISESconst button = 2;
const buttons = 3;
console.log(button);
console.log(buttons);2
3The first log prints button, which is 2. The second prints the held-button bitmask, which is 3.
Use mouseenter for mouse-only code or pointerenter for pointer-based widgets. Both avoid child-to-child bubbling flicker.
preventDefault() in dragover? Predict this simplified output.let canDrop = false;
const event = { preventDefault() { canDrop = true; } };
event.preventDefault();
console.log(canDrop);let canDrop = false;
const event = { preventDefault() { canDrop = true; } };
event.preventDefault();
console.log(canDrop);Calling preventDefault() during dragover tells the browser this target accepts a drop. The simplified program prints true.
thumb.setPointerCapture(event.pointerId);Capture retargets later pointer events for that pointer to the thumb, so dragging remains reliable.
clientX 175 on a track that starts at 100 and is 200 pixels wide.function percent(clientX, left, width) {
const raw = ((clientX - left) / width) * 100;
return Math.max(0, Math.min(100, Math.round(raw)));
}
console.log(percent(175, 100, 200));function percent(clientX, left, width) {
const raw = ((clientX - left) / width) * 100;
return Math.max(0, Math.min(100, Math.round(raw)));
}
console.log(percent(175, 100, 200));The pointer is 75px into a 200px track, which is 37.5%. Math.round makes it 38.
Check your understanding
7 QUESTIONSQuestion 1 of 7Which pair is correct for a normal primary mouse click in most browsers?
Choose an answer to see the explanation.
Question 2 of 7What does this print?
Read the code, then predictconst event = { button: 1, buttons: 4 }; console.log(event.button); console.log(event.buttons);Choose an answer to see the explanation.
Question 3 of 7Why can
mouseoverfire several times while you are still inside one card?Choose an answer to see the explanation.
Question 4 of 7What should you set on a custom touch slider so the browser does not take over the drag for scrolling?
Choose an answer to see the explanation.
Question 5 of 7What does this dragover-style program print?
Read the code, then predictlet allowed = false; const event = { preventDefault() { allowed = true; } }; event.preventDefault(); console.log(allowed);Choose an answer to see the explanation.
Question 6 of 7When can
dataTransfer.getData()reliably read custom drag data?Choose an answer to see the explanation.
Question 7 of 7Which event should you listen for to build a custom right-click menu?
Choose an answer to see the explanation.
Key takeaways
- Mouse events still matter for click, hover, auxiliary click, and context-menu behavior.
buttonsays what changed;buttonssays what is held.mouseover/mouseoutbubble;mouseenter/mouseleavedo not.- Pointer events are the default choice for custom controls that should work with mouse, pen, and touch.
- Pointer capture keeps one pointer stream going to one element during a drag.
- HTML drag and drop needs
dragoverpluspreventDefault(), and touch behavior varies.
Final definition: Pointer events are browser events that describe one mouse, pen, or touch input point with a shared event vocabulary.
Up next: Keyboard & focus.