2

I'm using onpointerdown, onpointerup and onpointermove for handling HTML5 Canvas Freehand drawing and it works perfectly with mouse. However, when I use a mobile it doesn't work, I have to add ontouchmove as shown in my working example.

How can I get rid of ontouchmove and use just PointerEvents.

const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')

// Same window dimensions
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// We need to track mouse/finger position and down/up
let x, y, down

// Start
canvas.onpointerdown = e => {
  const { pageX, pageY } = e
  down = true
  x = pageX
  y = pageY
}

// End
canvas.onpointerup = () => down = false

// Move
canvas.onpointermove = canvas.ontouchmove = e => {
  // Return if we havent finish yet
  if (!down) return
  const { pageX, pageY } = (e.touches && e.touches[0]) || e
  // Draw line
  ctx.beginPath()
  ctx.moveTo(x, y)
  ctx.lineTo(pageX, pageY)
  ctx.lineWidth = 1
  ctx.stroke()
  // Update
  x = pageX
  y = pageY
}
body {
  margin: 0;
  padding: 0;
  background-color: #bbb;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
<canvas></canvas>

Updated

I'm using a Samsung Galaxy A5 (2017) with Android 8 and Chrome 80.

Check this demo without the ontouchmove in a mobile. This is a screenshot from my device.

Chumpocomon
  • 661
  • 1
  • 6
  • 12
  • What kind of mobile do you use? Works perfectly for me on both Chrome and Firefox for Android. Safari doesn't support PointerEvents yet, so it's possible that no ios browser can handle it either. – Kaiido Mar 18 '20 at 01:27
  • Thank you so much for your reply Kaiido ^^, I'm using a Samsung Galaxy A5 (2017) with Android 8 and the latest Chrome version. Check this [demo](https://jsfiddle.net/ehrtkzu6/embedded/result/) without the `ontouchmove` in your mobile, I have updated the question as well. – Chumpocomon Mar 23 '20 at 13:00

1 Answers1

1

You can't: as there are legacy touch-based devices that does not support this API (for example, iOS 12 and below), you need to bind events for ontouchdown, ontouchmove, and ontouchend separately. You can check browser support for the pointer event API here.

You can use window.PointerEvent to detect browser support for this API, and then return the correct event name you want to bind to:

const hasPointerEvent = !!window.PointerEvent;
const pointerEventNames = {
  start: window.PointerEvent ? 'pointerdown' : 'touchstart',
  move: window.PointerEvent ? 'pointermove' : 'touchmove',
  end: window.PointerEvent ? 'pointerup' : 'touchend',
};

If you want to support IE10, you will need to add an additional check for window.MSPointerEvent, because IE10 only supports events prefixed with MS (and they are also PascalCased):

function getPointerEventNames() {
  const o = {
    start: 'pointerdown',
    move: 'pointermove',
    end: 'pointerup'
  };

  if (!window.PointerEvent) {
    o.start = 'touchstart';
    o.move = 'touchmove';
    o.end = 'touchend';
  } else if (!!window.MSPointerEvent) {
    o.start = 'MSPointerDown';
    o.move = 'MSPointerMove';
    o.end = 'MSPointerUp';
  }
}
const pointerEventNames = getPointerEventNames();

Instead of doing canvas.onpointerdown = ... and then repeating the same logic for canvas.ontouchstart, you can use canvas.addEventListener instead. The advantage of this is that you can then leverage on the dictionary pointerEventNames above, which will return the supported event names for the device you are on:

// Define common handler
const onDown = (e) => {
  const { pageX, pageY } = e
  down = true
  x = pageX
  y = pageY
}
canvas.addEventListener(pointerEventNames.start, onDown);

And you repeat these for the other two events... see a proof of concept below:

const hasPointerEvent = !!window.PointerEvent;
const pointerEventNames = {
  start: window.PointerEvent ? 'pointerdown' : 'touchstart',
  move: window.PointerEvent ? 'pointermove' : 'touchmove',
  end: window.PointerEvent ? 'pointerup' : 'touchend',
};

const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')

// Same window dimensions
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// We need to track mouse/finger position and down/up
let x, y, down

// Start
const onStart = (e) => {
  const {
    pageX,
    pageY
  } = e
  down = true
  x = pageX
  y = pageY
}
canvas.addEventListener(pointerEventNames.start, onStart);

// End
const onEnd = () => down = false;
canvas.addEventListener(pointerEventNames.end, onEnd);

// Move
const onMove = (e) => {
  // Return if we havent finish yet
  if (!down) return
  const {
    pageX,
    pageY
  } = (e.touches && e.touches[0]) || e
  // Draw line
  ctx.beginPath()
  ctx.moveTo(x, y)
  ctx.lineTo(pageX, pageY)
  ctx.lineWidth = 1
  ctx.stroke()
  // Update
  x = pageX
  y = pageY
}
canvas.addEventListener(pointerEventNames.move, onMove);
body {
  margin: 0;
  padding: 0;
  background-color: #bbb;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
<canvas></canvas>
Yuval.R
  • 1,182
  • 4
  • 15
Terry
  • 63,248
  • 15
  • 96
  • 118
  • @Kaiido Perhaps read up the specs before hand? iOS Safari <=12 do not support pointer events. Only iOS 13+ does. – Terry Mar 18 '20 at 08:11
  • 1
    I'll clarify that in an update, thanks. Meanwhile, it is known that older versions of iOS Safari indeed does not support `onpointer` events. – Terry Mar 18 '20 at 08:18
  • Better; Now would even be better if you did handle the browsers that do support it and the ones that don't separately. (`"PointerEvent" in window` should be enough) – Kaiido Mar 18 '20 at 08:19
  • Terry, thank you so much for your tremendous detailed answer, I really appreciate that. However, I'm trying to figure out why on latest browsers (post date) that support `onpointermove` I cannot get rid of `onmousetouch` completely, actually Chromium 80. I have updated the question with an example in JSFiddle and my screenshot trying to draw long lines (they are cut) – Chumpocomon Mar 23 '20 at 13:43