Pure JavaScript Zoom and Pan, taken from http://phrogz.net/tmp/canvas_zoom_to_cursor.html
A Pen by TechSlides on CodePen.
Pure JavaScript Zoom and Pan, taken from http://phrogz.net/tmp/canvas_zoom_to_cursor.html
A Pen by TechSlides on CodePen.
<p>Showing how to use transform methods on the HTML5 Canvas Context to selectively zoom in and out. Drag to pan. Click to zoom at that location. Shift-click to zoom out. Mousewheel up/down over the canvas to zoom in to/out from that location.</p> | |
<canvas></canvas> |
var canvas = document.getElementsByTagName('canvas')[0]; | |
canvas.width = 800; | |
canvas.height = 600; | |
var gkhead = new Image; | |
window.onload = function(){ | |
var ctx = canvas.getContext('2d'); | |
trackTransforms(ctx); | |
function redraw(){ | |
// Clear the entire canvas | |
var p1 = ctx.transformedPoint(0,0); | |
var p2 = ctx.transformedPoint(canvas.width,canvas.height); | |
ctx.clearRect(p1.x,p1.y,p2.x-p1.x,p2.y-p1.y); | |
ctx.save(); | |
ctx.setTransform(1,0,0,1,0,0); | |
ctx.clearRect(0,0,canvas.width,canvas.height); | |
ctx.restore(); | |
ctx.drawImage(gkhead,0,0); | |
} | |
redraw(); | |
var lastX=canvas.width/2, lastY=canvas.height/2; | |
var dragStart,dragged; | |
canvas.addEventListener('mousedown',function(evt){ | |
document.body.style.mozUserSelect = document.body.style.webkitUserSelect = document.body.style.userSelect = 'none'; | |
lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft); | |
lastY = evt.offsetY || (evt.pageY - canvas.offsetTop); | |
dragStart = ctx.transformedPoint(lastX,lastY); | |
dragged = false; | |
},false); | |
canvas.addEventListener('mousemove',function(evt){ | |
lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft); | |
lastY = evt.offsetY || (evt.pageY - canvas.offsetTop); | |
dragged = true; | |
if (dragStart){ | |
var pt = ctx.transformedPoint(lastX,lastY); | |
ctx.translate(pt.x-dragStart.x,pt.y-dragStart.y); | |
redraw(); | |
} | |
},false); | |
canvas.addEventListener('mouseup',function(evt){ | |
dragStart = null; | |
if (!dragged) zoom(evt.shiftKey ? -1 : 1 ); | |
},false); | |
var scaleFactor = 1.1; | |
var zoom = function(clicks){ | |
var pt = ctx.transformedPoint(lastX,lastY); | |
ctx.translate(pt.x,pt.y); | |
var factor = Math.pow(scaleFactor,clicks); | |
ctx.scale(factor,factor); | |
ctx.translate(-pt.x,-pt.y); | |
redraw(); | |
} | |
var handleScroll = function(evt){ | |
var delta = evt.wheelDelta ? evt.wheelDelta/40 : evt.detail ? -evt.detail : 0; | |
if (delta) zoom(delta); | |
return evt.preventDefault() && false; | |
}; | |
canvas.addEventListener('DOMMouseScroll',handleScroll,false); | |
canvas.addEventListener('mousewheel',handleScroll,false); | |
}; | |
gkhead.src = 'http://phrogz.net/tmp/gkhead.jpg'; | |
// Adds ctx.getTransform() - returns an SVGMatrix | |
// Adds ctx.transformedPoint(x,y) - returns an SVGPoint | |
function trackTransforms(ctx){ | |
var svg = document.createElementNS("http://www.w3.org/2000/svg",'svg'); | |
var xform = svg.createSVGMatrix(); | |
ctx.getTransform = function(){ return xform; }; | |
var savedTransforms = []; | |
var save = ctx.save; | |
ctx.save = function(){ | |
savedTransforms.push(xform.translate(0,0)); | |
return save.call(ctx); | |
}; | |
var restore = ctx.restore; | |
ctx.restore = function(){ | |
xform = savedTransforms.pop(); | |
return restore.call(ctx); | |
}; | |
var scale = ctx.scale; | |
ctx.scale = function(sx,sy){ | |
xform = xform.scaleNonUniform(sx,sy); | |
return scale.call(ctx,sx,sy); | |
}; | |
var rotate = ctx.rotate; | |
ctx.rotate = function(radians){ | |
xform = xform.rotate(radians*180/Math.PI); | |
return rotate.call(ctx,radians); | |
}; | |
var translate = ctx.translate; | |
ctx.translate = function(dx,dy){ | |
xform = xform.translate(dx,dy); | |
return translate.call(ctx,dx,dy); | |
}; | |
var transform = ctx.transform; | |
ctx.transform = function(a,b,c,d,e,f){ | |
var m2 = svg.createSVGMatrix(); | |
m2.a=a; m2.b=b; m2.c=c; m2.d=d; m2.e=e; m2.f=f; | |
xform = xform.multiply(m2); | |
return transform.call(ctx,a,b,c,d,e,f); | |
}; | |
var setTransform = ctx.setTransform; | |
ctx.setTransform = function(a,b,c,d,e,f){ | |
xform.a = a; | |
xform.b = b; | |
xform.c = c; | |
xform.d = d; | |
xform.e = e; | |
xform.f = f; | |
return setTransform.call(ctx,a,b,c,d,e,f); | |
}; | |
var pt = svg.createSVGPoint(); | |
ctx.transformedPoint = function(x,y){ | |
pt.x=x; pt.y=y; | |
return pt.matrixTransform(xform.inverse()); | |
} | |
} |
body { background:#eee; margin:1em; text-align:center; } | |
canvas { display:block; margin:1em auto; background:#fff; border:1px solid #ccc } |
In the zoom function clicks are passed which basically define the zoom level. You could count clicks in the global variable and prevent further zooming, e.g.:
var curZoomIn = 0;
var zoom = function (clicks) {
var newZoomIn = curZoomIn + clicks;
if (newZoomIn < 0 || newZoomIn > 5) {
return;
}
curZoomIn += clicks;
...
}
This would limit zooming between the original size and 5x.
This is awesome code. I have modified the zoom function to allow a minimum and maximum zoom level (also I have an array of layered canvas objects that I need to zoom in and out of):
let layer = [];
layer[0] = document.getElementById("canvas-layer-001");
layer[1] = document.getElementById("canvas-layer-002");
layer[2] = document.getElementById("canvas-layer-top");
let context = [];
context[0] = layer[0].getContext("2d");
context[1] = layer[1].getContext("2d");
context[2] = layer[2].getContext("2d");
const MIN_ZOOM_LEVEL = 0;
const MAX_ZOOM_LEVEL = 10;
let scaleFactor = 1.1;
let accumClicks = 0;
let zoom = function(clicks){
if( accumClicks + clicks < MIN_ZOOM_LEVEL ||
accumClicks + clicks > MAX_ZOOM_LEVEL ) { return; }
let pt = context[i].transformedPoint(lastX,lastY);
let factor = Math.pow(scaleFactor,clicks);
for( let i = 0 ; i < context.length ; ++i ) {
context[i].translate(pt.x,pt.y);
context[i].scale(factor,factor);
context[i].translate(-pt.x,-pt.y);
}
accumClicks += clicks;
draw();
}
let resetZoom = function() {
for( let i = 0 ; i < context.length ; ++i ){
context[i].setTransform(1, 0, 0, 1, 0, 0);
}
}
Issue I have is I do not want the zoom out to show white space (my application shows an image that takes up the entire canvas). Does anyone know how to do this?
Hey! Thank you so much for providing this code, it worked perfecly for my application! I was just wondering if there is a way to limit the zoom in/out? I can't seem to figure out how to do it...