1

I'm trying to come up with a cross-device code that handles pointer events. I'm running this code successfully on Chrome/Android (using USB debugging), but the Chrome desktop just acts up and keeps firing pointermove after the mouse has been released.

(Another problem is that the moves are not as smooth as on the mobile)

Playable demo

These SO posts don't solve my problem:

user776686
  • 7,933
  • 14
  • 71
  • 124

1 Answers1

0

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>
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • not sure about the last one: `transform` keeps the element taking up its original space, and just draws it somewhere else, unlike setting top/left which actually moves the element (just use css variables instead of setting literal style values directly: `.dot { --x: 50%; --y: 50%; left: var(--x); top: var(--y); ... }` and then `element.style.setProperty('--x', whatever value you need)` on the JS side) – Mike 'Pomax' Kamermans Nov 03 '22 at 18:42
  • 1
    @Mike'Pomax'Kamermans you're absolutely right. Removed the `transform translate` part. Thank you for the extra pair of eyes. – Roko C. Buljan Nov 03 '22 at 20:44
  • @Mike'Pomax'Kamermans Not sure I understand your concern regarding the transform. The snippet works with it just as intended. Would that css props method improve performance? – user776686 Nov 12 '22 at 10:13
  • @RokoC.Buljan Thanks for the answer. Indeed, it was a silly mistake to listen for the other events at the target level. – user776686 Nov 12 '22 at 10:14
  • Mostly: anything that's a variable should use a CSS variable. That's what they're for. You _can_ directly manipulate an element's style rules, in the same way that you _can_ use document.write, but modern JS and modern CSS and modern HTML is best written using modern JS, modern CSS, and modern HTML. – Mike 'Pomax' Kamermans Nov 12 '22 at 16:26
  • @Mike'Pomax'Kamermans You mean JS +CSS --vars [like this](https://stackoverflow.com/a/73058171/383904)? I'm not sure if it's wise to use `style.setProperty` for multiple pointers and pointermove [performance-wise](https://stackoverflow.com/a/73703673/383904). I'll investigate. – Roko C. Buljan Nov 12 '22 at 18:10
  • unless you're updating thousands of values per frame, all of which result in cascade updates, there is no point in benchmarking performance: all you care about is that it runs fast enough. Browsers are extremely good at handing CSS class and variable changes. – Mike 'Pomax' Kamermans Nov 12 '22 at 18:19
  • @Mike'Pomax'Kamermans fair enough, I gave it a go and edited. Thx. – Roko C. Buljan Nov 13 '22 at 12:09