CJ McAllister
CJ McAllister

Reputation: 331

Asynchronous canvas drawing with Web Workers

So, I'm making a relatively trivial HTML5 Canvas drawing web app. Basically, you can select your color, and then draw on a 500x500 canvas. It's going to be themed as a "graffiti" wall, so I am attempting to create a graffiti effect to the drawing, much like the Spray tool in MS Paint of yore.
Feel free to take a look at it here.

In order to facilitate this effect, I'm making use of web workers to callback on mouse events and asynchronously draw to the canvas. The naive implementation I have now is that on any mouse event, 5 pixels are randomly drawn around the coords of the event.

What I would like to do though, is to have those pixels drawn continuously from the mousedown event until the mouseup event, while updating the coords on mousemove events. From my limited knowledge of JavaScript, I imagine that this could involve a setTimeout(), but I'm not sure how to manipulate this to achieve what I want.

DISCLAIMER: This is part of a school project, and as such I am trying to avoid JQuery, Ajax, and other such frameworks; my goal here is to make an as-pure-as-possible JavaScript/HTML5 web app.

Thanks in advance.

Upvotes: 2

Views: 3478

Answers (3)

Jay
Jay

Reputation: 2069

Drawing an SVG in a web worker to pass it to its parent and append the SVG to the DOM is possible.

Example

/**
 * Web Worker
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
var workerURL = URL.createObjectURL(new Blob(['(',
  function() {
    onmessage = function( event ) {
      if (event.data === 'generateSVG') {
        // Generate the SVG content
        const svgContent = '<svg width="200" height="200"><circle cx="100" cy="100" r="50" fill="red" /></svg>';
        // Send the SVG content back to the parent page
        postMessage(svgContent);
      }
    };
  }.toString(), ')()' ], { type: 'application/javascript' }));


/**
 * Main Script
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 */
// Init Worker
worker = new Worker( workerURL );

// Listen for Worker
worker.addEventListener('message', function(event) {
  if(event.data !== undefined) {
    console.log( 'Worker Response: ', event.data );
  }
}, false);

// Listen for messages from the web worker
worker.onmessage = function (event) {
  const svgData = event.data;
  // Display the SVG in the HTML page
  document.getElementById('svg-container').innerHTML = svgData;
};

// Start the web worker
worker.postMessage('generateSVG');
<html>
  <body>
    <div id="svg-container"></div>
  </body>
</html>

Upvotes: 0

Bergi
Bergi

Reputation: 665574

If you want to use a WebWorker (for example for more complex drawing algorithms), I could think of the following setup:

  • onmousedown, spawn a new worker and register a handler on it that paints objects on the canvas
  • onmouseup (and -leave etc), terminate the worker
  • onmousemove, if a worker exists determine mouse coordinates and send them into the worker

In the worker

  • listen to new mouse coordinates
  • start an timeout interval which constantly fires draw-events (depending on current coordinates and a clever algorithm)

Yet I think that a Worker is too much overhead for a simple graffiti tool. Use the simple solution without a Worker like @Esailija demonstrated.

If you had a more complex application which could make good use of Workers, you wouldn't really spawn them onmousedown and terminate them. Instead, you maybe instantiated single Workers for single kinds of tools, and fired start-processing and end-processing events to them.

Upvotes: 2

Esailija
Esailija

Reputation: 140244

Using a timer (no worker required):

var mouseX = 0,
    mouseY = 0,
    mouseDown = false;

    function ev_canvas( ev ) {
        if (ev.offsetX || ev.offsetX == 0) { //opera
            mouseX = ev.offsetX;
            mouxeY = ev.offsetY;
        } else if (ev.layerX || ev.layerX == 0) { //firefox
            var canvasOffset = document.getElementById("graffiti_wall").getBoundingClientRect();
            mouseX = ev.layerX - canvasOffset.left;
            mouseY = ev.layerY - canvasOffset.top;
        }

        if ( ev.type == 'mousedown' ) {
            mouseDown = true;
        }
        else if ( ev.type == 'mouseup' ) {
            mouseDown = false;
        }
    }

    function draw_spray() {
        if( !mouseDown ) {
            //Don't do anything since the mouse is not pressed down
            return;
        }
        //Draw something at the last known location
        context.strokeRect( mouseX, mouseY, 1, 1 );
    }

    //Call draw_spray function continuously every 16 milliseconds
    window.setInterval( draw_spray, 16 );

Upvotes: 3

Related Questions