Skip to content
Advertisement

How to keep drawing on canvas when scrolling?

I’m wanting to implement canvas as background for my website so users can use their cursors to paint on the webpage like this codepen: https://codepen.io/cocotx/pen/PoGRdxQ?editors=1010 (this is an example code from http://www.dgp.toronto.edu/~clwen/test/canvas-paint-tutorial/)

if(window.addEventListener) {
window.addEventListener('load', function () {
  var canvas, context;

  // Initialization sequence.
  function init () {
    // Find the canvas element.
    canvas = document.getElementById('imageView');
    if (!canvas) {
      alert('Error: I cannot find the canvas element!');
      return;
    }

    if (!canvas.getContext) {
      alert('Error: no canvas.getContext!');
      return;
    }

    // Get the 2D canvas context.
    context = canvas.getContext('2d');
    if (!context) {
      alert('Error: failed to getContext!');
      return;
    }

    // Attach the mousemove event handler.
    canvas.addEventListener('mousemove', ev_mousemove, false);
  }

  // The mousemove event handler.
  var started = false;
  function ev_mousemove (ev) {
    var x, y;

    // Get the mouse position relative to the canvas element.
    if (ev.layerX || ev.layerX == 0) { // Firefox
      x = ev.layerX;
      y = ev.layerY;
    } else if (ev.offsetX || ev.offsetX == 0) { // Opera
      x = ev.offsetX;
      y = ev.offsetY;
    }

    // The event handler works like a drawing pencil which tracks the mouse 
    // movements. We start drawing a path made up of lines.
    if (!started) {
      context.beginPath();
      context.moveTo(x, y);
      started = true;
    } else {
      context.lineTo(x, y);
      context.stroke();
    }
  }

  init();
}, false); }

The problem is that the cursor stops painting when I scroll until I move my mouse again. Any idea on how to keep cursor painting even when I scroll?

Thanks in advance! Much appreciated!

Advertisement

Answer

You will have to store the last mouse event and fire a new fake one in the scroll event.

Luckily, the MouseEvent constructor accepts an mouseEventInit object on which we can set the clientX and clientY values of our new event, so we just need to store these values from the previous event and dispatch it in the scroll event.

Now, I couldn’t help but rewrite almost everything from your code.
It had a lot of checks for old browsers (like very old ones which should never face the web again anyway), you may want to add it again if you wish.
It wasn’t clearing the context, meaning that every time it drew a new line, it also did draw again the previous lines over themselves, leading in fatter lines, with a lot of noise at the beginning, and smoother lines at the end.
This could be fixed in many ways, the less intrusive one was to just clear the context at each frame. To get the relative mouse position, it now uses the clientX and clientY properties of the event.

And the rest of the changes is commented in the snippet.

window.addEventListener('load', function () {
  const canvas = document.getElementById('imageView');
  context = canvas.getContext("2d");
  let last_event; // we will store our mouseevents here
  
  // we now listen to the mousemove event on the document,
  // not only on the canvas
  document.addEventListener('mousemove', ev_mousemove);
  document.addEventListener('scroll', fireLastMouseEvent, { capture: true } );
  // to get the initial position of the cursor
  // even if the mouse never moves
  // we listen to a single mouseenter event on the document's root element
  // unfortunately this seems to not work in Chrome
  document.documentElement.addEventListener( "mouseenter", ev_mousemove, { once: true } );

  // called in scroll event
  function fireLastMouseEvent() {
    if( last_event ) {
      // fire a new event on the document using the same clientX and clientY values
      document.dispatchEvent( new MouseEvent( "mousemove", last_event ) );
    }
  }
  
  // mousemove event handler.
  function ev_mousemove (ev) {
    const previous_evt = last_event || {};
    const was_offscreen = previous_evt.offscreen;
    
    // only for "true" mouse event
    if( ev.isTrusted ) {
      // store the clientX and clientY props in an object
      const { clientX, clientY } = ev;
      last_event = { clientX, clientY };
    }
    
    // get the relative x and y positions from the mouse event
    const point = getRelativePointFromEvent( ev, canvas );
    
    // check if we are out of the canvas viewPort
    if( point.x < 0 || point.y < 0 || point.x > canvas.width || point.y > canvas.height ) {
      // remember we were
      last_event.offscreen = true;
      // if we were already, don't draw
      if( was_offscreen ) { return; }
    }
    // we come from out-of-screen to in-screen
    else if( was_offscreen ) { 
      // move to the previous point recorded as out-of-screen
      const previous_point = getRelativePointFromEvent( previous_evt, canvas );
      context.moveTo( previous_point.x, previous_point.y );
    }
    
    // add the new point to the context's sub-path definition
    context.lineTo( point.x, point.y );

    // clear the previous drawings
    context.clearRect( 0, 0, canvas.width, canvas.height );
    // draw everything again
    context.stroke();

  }

  function getRelativePointFromEvent( ev, elem ) {
    // first find the bounding rect of the element
    const bbox = elem.getBoundingClientRect();
    // subtract the bounding rect from the client coords
    const x = ev.clientX - bbox.left;
    const y = ev.clientY - bbox.top;

    return { x, y };
  }
});
#container {
  width: 400px;
  height: 200px;
  overflow: auto;
  border: 1px solid;
}
#imageView { border: 1px solid #000; }
canvas {
  margin: 100px;
}
<div id="container">
  <canvas id="imageView" width="400" height="300"></canvas>
</div>
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement