Javascript canvas drag-and-zoom library

Created: 2012-07-01 — modified: 2021-04-11 — tags: javascript

For those cases when you need to implement something GoogleMaps-like

Working on some project I had to implement a intuitive navigation on the canvas. This library provides exactly that. Use it like this:

<!DOCTYPE html><html><body>
<canvas id="canvas" width="500" height="500" style="border:1px solid #d3d3d3;"></canvas>

<script src="scroll.js"></script>
<script>

    function draw(options){
            options.ctx.beginPath();
            options.ctx.arc(95, 50, 40, 0, 2 * Math.PI);
            options.ctx.stroke();
        }

    new CanvasDragZoom(document.getElementById('canvas'), draw);

</script></body></html>

In other words:

  • Add <canvas> element to HTML

  • Include scroll.js script

  • define a draw function

  • create new CanvasDragZoom object, passing it the <canvas> element and draw function.

The draw function should accept a single options argument, which is a JSON object with various key-value pairs, most important of which is a ctx key, which is this canvas'es 2d context. In other words, the thing that you draw on. And you will get something like this:

Options

Other options in the object passed to the draw function are:

  • minx, miny, maxx, maxy - maximum and minimum values of x and y that fall within visible area of canvas. You can use them to limit what area to draw (what you draw outside of this area will be discarded by canvas, at the cost of performance penalty). These change when you drag the image around.

  • width, height - maxx - minx and maxy - miny, respectively. These change when you zoom in and out.

  • width_px, height_px - dimensions of actual <canvas> element. You can use them to draw images per-pixel (see next example). They don't normally change.

  • scale - zoom ratio of screen_pixels/canvas_units, or how many screen pixels are used to draw one canvas units

Perlin noise example

Example showing usage of these parameters explores Perlin noise generated by this nice Joseph Gentle library. Here is the code:

<!DOCTYPE html><html><body>
<canvas id="canvas" width="500" height="500" style="border:1px solid #d3d3d3;"></canvas>

<script src="scroll.js"></script>
<script src="perlin.js"></script>
<script>

    var image, data;

    function draw(options){
        if(!data){
            // reuse this temporary imageData object
            image = options.ctx.createImageData(options.width_px, options.height_px);
            data = image.data;
        }

        for (var x_px = 0; x_px < options.width_px; x_px++) {
            // convert from "screen pixel" to "canvas units" for x
            var x = x_px/options.scale + options.minx;
            for (var y_px = 0; y_px < options.height_px; y_px++) {
                // convert from "screen pixel" to "canvas units" for y
                var y = y_px/options.scale + options.miny;

                // calculate noise value at point (x,y)
                var value = noise.perlin2(x / 200, y / 200)/2+0.5;
                value *= 256;

                // put value onto image. Note that imageData object uses "screen pixel"
                // coordinates, so it's not affected by zoom or transition effects.
                var cell = (x_px + y_px * canvas.width) * 4;
                data[cell] = data[cell + 1] = data[cell + 2] = value;
                data[cell + 3] = 255; // alpha.
            }
        }
        options.ctx.putImageData(image, 0, 0);
    }

    new CanvasDragZoom(document.getElementById('canvas'), draw);

</script></body></html>

And that's how it looks:

Triangulation example

Last example lets you explore infinite delaunator triangulation. Sorry, the code is too big to fit in this blog post - so view the source of the frame below if you're interested!

Acknowledgements

Thanks to this SO answer for original implementation.