Javascript for Zooming and Panning in a Canvas

In 2012, I had a need for a simple zooming and panning capability for an animation project in Javascript.
With much help from examples on the net, I came up with the solution illustrated at here. There must be better solutions available now.

Here is zoom.js:

/**********************************************************************
 * zoom.js
 *
 * Zooming and panning by resetting context transform each time displayed.
 * Viewing parameters are:
 *   xleftView: x coordinate of left side of view
 *   ytopView:  y coordinate of top side of view
 *   widthView:  width of view
 *   heightView: height of view
 * Used as follows before drawing:
 *   ctx.setTransform(1,0,0,1,0,0);
 *   ctx.scale(widthCanvas/widthView, heightCanvas/heightView);
 *   ctx.translate(-xleftView,-ytopView);
 *
 * Chuck Anderson, 2012, with lots of help from examples by others on the net.
 *
 * Licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.
 * See http://creativecommons.org/licenses/by-nc/4.0/
 *
 * Example use in HTML file:
 *
 * <!doctype html>
 * <html lang='en'>
 *   <head>
 *     <meta charset='utf-8'>
 *     <title> Zoom </title>
 *     <script type='text/javascript' src='zoom.js'></script>
 *     <style> canvas {border: red 2px solid;
 *                     float: left;}
 *                     width: 200;
 *                     height: 200;}
 *     </style>
 *   </head>
 * 
 *   <body>;
 *     <canvas id='canvas' width='200' height='200'></canvas>
 * 
 *     <div>
 *       Click left mouse button and hold down to pan.  

 *       Double click left mouse button to zoom in to point. (With Shift to zoom out.) 

 *       Spin mouse wheel to zoom in and out of center of view. 

 *       

 *       Circle stays at center of view.
 *     </div>
 * 
 *   </body>
 * </html>
 * **********************************************************************/

var canvas;
var ctx;
var widthCanvas;
var heightCanvas;

// View parameters
var xleftView = 0;
var ytopView = 0;
var widthViewOriginal = 1.0;           //actual width and height of zoomed and panned display
var heightViewOriginal = 1.0;
var widthView = widthViewOriginal;           //actual width and height of zoomed and panned display
var heightView = heightViewOriginal;

window.addEventListener(&quot;load&quot;,setup,false);

function setup() {
    canvas = document.getElementById(&quot;canvas&quot;);
    ctx = canvas.getContext(&quot;2d&quot;);

    widthCanvas = canvas.width;
    heightCanvas = canvas.height;

    canvas.addEventListener(&quot;dblclick&quot;, handleDblClick, false);  // dblclick to zoom in at point, shift dblclick to zoom out.
    canvas.addEventListener(&quot;mousedown&quot;, handleMouseDown, false); // click and hold to pan
    canvas.addEventListener(&quot;mousemove&quot;, handleMouseMove, false);
    canvas.addEventListener(&quot;mouseup&quot;, handleMouseUp, false);
    canvas.addEventListener(&quot;mousewheel&quot;, handleMouseWheel, false); // mousewheel duplicates dblclick function
    canvas.addEventListener(&quot;DOMMouseScroll&quot;, handleMouseWheel, false); // for Firefox

    draw();
}

function draw() {
    ctx.setTransform(1,0,0,1,0,0);
    ctx.scale(widthCanvas/widthView, heightCanvas/heightView);
    ctx.translate(-xleftView,-ytopView);

    ctx.fillStyle = &quot;yellow&quot;;
    ctx.fillRect(xleftView,ytopView, widthView,heightView);
    ctx.fillStyle = &quot;blue&quot;;
    ctx.fillRect(0.1,0.5,0.1,0.1);
    ctx.fillStyle = &quot;red&quot;;
    ctx.fillRect(0.3,0.2,0.4,0.2);
    ctx.fillStyle=&quot;green&quot;;
    ctx.beginPath();
    ctx.arc(widthView/2+xleftView,heightView/2+ytopView,0.05,0,360,false);
    ctx.fill();
}

function handleDblClick(event) {
    var X = event.clientX - this.offsetLeft - this.clientLeft + this.scrollLeft; //Canvas coordinates
    var Y = event.clientY - this.offsetTop - this.clientTop + this.scrollTop;
    var x = X/widthCanvas * widthView + xleftView;  // View coordinates
    var y = Y/heightCanvas * heightView + ytopView;

    var scale = event.shiftKey == 1 ? 1.5 : 0.5; // shrink (1.5) if shift key pressed
    widthView *= scale;
    heightView *= scale;

    if (widthView &gt; widthViewOriginal || heightView &gt; heightViewOriginal) {
    widthView = widthViewOriginal;
    heightView = heightViewOriginal;
    x = widthView/2;
    y = heightView/2;
    }

    xleftView = x - widthView/2;
    ytopView = y - heightView/2;

    draw();
}

var mouseDown = false;

function handleMouseDown(event) {
    mouseDown = true;
}

function handleMouseUp(event) {
    mouseDown = false;
}

var lastX = 0;
var lastY = 0;
function handleMouseMove(event) {

    var X = event.clientX - this.offsetLeft - this.clientLeft + this.scrollLeft;
    var Y = event.clientY - this.offsetTop - this.clientTop + this.scrollTop;

    if (mouseDown) {
        var dx = (X - lastX) / widthCanvas * widthView;
        var dy = (Y - lastY)/ heightCanvas * heightView;
    xleftView -= dx;
    ytopView -= dy;
    }
    lastX = X;
    lastY = Y;

    draw();
}

function handleMouseWheel(event) {
    var x = widthView/2 + xleftView;  // View coordinates
    var y = heightView/2 + ytopView;

    var scale = (event.wheelDelta &lt; 0 || event.detail &gt; 0) ? 1.1 : 0.9;
    widthView *= scale;
    heightView *= scale;

    if (widthView &gt; widthViewOriginal || heightView &gt; heightViewOriginal) {
    widthView = widthViewOriginal;
    heightView = heightViewOriginal;
    x = widthView/2;
    y = heightView/2;
    }

    // scale about center of view, rather than mouse position. This is different than dblclick behavior.
    xleftView = x - widthView/2;
    ytopView = y - heightView/2;

    draw();
}

Leave a Comment