The "pointerup" Event is assigned to the #canvas, but such event will never occur because the mouse is actually above the generated DIV circle.
Since your circles are just visual helpers, set in CSS
.dot {
/* ... */
pointer-events: none;
}
Also, make sure to use Event.preventDefault()
on "pointerdown"
.
Regarding the other strategies for a seamless experience, both on desktop and on mobile (touch):
- assign only the
"pointerdown"
Event to a desired Element (canvas
in your case)
- use the
window
object for all the other events
Edited example:
const canvas = document.getElementById('canvas');
function startTouch(ev) {
ev.preventDefault();
const dot = document.createElement('div');
dot.classList.add('dot');
dot.id = ev.pointerId;
dot.style.left = `${ev.pageX}px`;
dot.style.top = `${ev.pageY}px`;
document.body.append(dot);
}
function moveTouch(ev) {
const dot = document.getElementById(ev.pointerId);
if (!dot) return;
dot.style.left = `${ev.pageX}px`;
dot.style.top = `${ev.pageY}px`;
}
function endTouch(ev) {
const dot = document.getElementById(ev.pointerId);
if (!dot) return;
removeDot(dot);
}
function removeDot(dot) {
dot.remove();
}
canvas.addEventListener('pointerdown', startTouch);
addEventListener('pointermove', moveTouch);
addEventListener('pointerup', endTouch);
addEventListener('pointercancel', endTouch);
.dot {
background-color: deeppink;
width: 2rem;
height: 2rem;
border-radius: 50%;
transform: translate(-50%, -50%);
position: absolute;
pointer-events: none; /* ADD THIS! */
}
#canvas {
height: 50vh;
background-color: black;
touch-action: none;
}
body {
margin: 0;
}
<div id="canvas"></div>
The code needs also this improvement:
- Don't query the DOM inside a pointermove event
Using CSS vars
As per the comments section here's a viable solution that uses custom properties CSS variables and JS's CSSStyleDeclaration.setProperty() method.
Basically the --x
and --y
CSS properties values are updated from the pointerdown/move event handlers to reflect the current clientX
and clientY
values:
const el = (sel, par) => (par || document).querySelector(sel);
const elNew = (tag, prop) => Object.assign(document.createElement(tag), prop);
const canvas = document.getElementById('canvas');
const pointersDots = (parent) => {
const elParent = typeof parent === "string" ? el(parent) : parent;
const dots = new Map();
const moveDot = (elDot, {clientX: x,clientY: y}) => {
elDot.style.setProperty("--x", x);
elDot.style.setProperty("--y", y);
};
const onDown = (ev) => {
ev.preventDefault();
const elDot = elNew("div", { className: "dot" });
moveDot(elDot, ev);
elParent.append(elDot);
dots.set(ev.pointerId, elDot);
};
const onMove = (ev) => {
if (dots.size === 0) return;
const elDot = dots.get(ev.pointerId);
moveDot(elDot, ev);
};
const onUp = (ev) => {
if (dots.size === 0) return;
const elDot = dots.get(ev.pointerId);
elDot.remove();
dots.delete(ev.pointerId);
};
canvas.addEventListener('pointerdown', onDown);
addEventListener('pointermove', onMove);
addEventListener('pointerup', onUp);
addEventListener('pointercancel', onUp);
};
// Init: Pointers helpers
pointersDots("#canvas");
* {
margin: 0;
}
.dot {
--x: 0;
--y: 0;
pointer-events: none;
position: absolute;
width: 2rem;
height: 2rem;
border-radius: 50%;
background-color: deeppink;
transform: translate(-50%, -50%);
left: calc(var(--x) * 1px);
top: calc(var(--y) * 1px);
}
#canvas {
margin: 10vh;
height: 80vh;
background-color: black;
touch-action: none;
}
<div id="canvas"></div>