How to use onpointermove properly to draw in HTML5 Canvas

Viewed 1304

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.

1 Answers

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>

Related