I have a map. I want a user to be able to zoom and pan the map. Imagine Google Maps, but instead of being infinitely pannable, the map is a square (it doesn\'t wrap around a
Here's one technique for zooming at a point:
Drawing the map
Simplify things by not using transforms to draw the map (no need for translate,scale!).
All that's needed is the scaling version of context.drawImage
.
What you do is scale the original map to the desired size and then pull it upward and leftward from the scaling point that the user has selected.
context.drawImage(
map,
0,0,map.width,map.height, // start with the map at original (unscaled) size
offsetX,offsetY, // pull the map leftward & upward from the scaling point
scaledWidth,scaledHeight // resize the map to the currently scaled size
Selecting the scaling point (the focal point):
The scaling focal point is actually 2 points!
The first focal point is the mouseX,mouseY where the user clicked to set their desired scaling point. It's important to remember that the mouse coordinate is in scaled space. The map that the user is seeing/clicking is scaled so their mouseX,mouseY is scaled also.
The second focal point is calculated by unscaling the mouse coordinate. This second point is the equivalent mouse position on the original unscaled map.
The second unscaled focal point is used to calculate how much to pull the scaled map leftward and upward from the first focal point.
function setFocus(mx,my){
// mouseX,mouseY is the scaling point in scaled coordinates
focusX=mx;
focusY=my;
// convert the scaled focal point
// to an unscaled focal point
focusX1=parseInt((mx-mapLeft)/scale);
focusY1=parseInt((my-mapTop)/scale);
}
Scaling the map
When the user indicates they want to scale the map larger or smaller:
Code:
function setScale(newScale){
scale=newScale;
// calc the width & height of the newly scaled map
mapWidth=parseInt(iw*scale);
mapHeight=parseInt(ih*scale);
// calc how much to offset the map on the canvas
mapLeft=parseInt(focusX-focusX1*scale);
mapTop =parseInt(focusY-focusY1*scale);
// draw the map
drawMap();
}
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
//
var counter=1;
var PI2=Math.PI*2;
var iw,ih;
var mapLeft,mapTop,mapWidth,mapHeight;
var focusX,focusY,focusX1,focusY1;
var scale;
var map=new Image();
map.onload=start;
map.src="https://dl.dropboxusercontent.com/u/139992952/multple/mapSmall.png";
function start(){
iw=map.width;
ih=map.height;
// initial
mapLeft=0;
mapTop=0;
scale=1.00;
setFocus(iw/2*scale,ih/2*scale);
setScale(scale); // also sets mapWidth,mapHeight
drawMap();
//
$("#canvas").mousedown(function(e){handleMouseDown(e);});
//
canvas.addEventListener('DOMMouseScroll',handleScroll,false);
canvas.addEventListener('mousewheel',handleScroll,false);
}
//
function setScale(newScale){
scale=newScale;
mapWidth=parseInt(iw*scale);
mapHeight=parseInt(ih*scale);
mapLeft=parseInt(focusX-focusX1*scale);
mapTop =parseInt(focusY-focusY1*scale);
drawMap();
}
//
function setFocus(mx,my){
// mouseX,mouseY is the scaling point in scaled coordinates
focusX=mx;
focusY=my;
// convert the scaled focal point
// to an unscaled focal point
focusX1=parseInt((mx-mapLeft)/scale);
focusY1=parseInt((my-mapTop)/scale);
//
drawMap();
}
//
function drawMap(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.drawImage(map,0,0,iw,ih,mapLeft,mapTop,mapWidth,mapHeight);
dot(ctx,focusX,focusY,"red");
ctx.restore();
}
function dot(ctx,x,y,fill){
ctx.beginPath();
ctx.arc(x,y,4,0,PI2);
ctx.closePath();
ctx.fillStyle=fill;
ctx.fill();
ctx.lineWidth=2;
ctx.stroke();
}
//
function handleScroll(e){
e.preventDefault();
e.stopPropagation();
var delta=e.wheelDelta?e.wheelDelta/30:e.detail?-e.detail:0;
if (delta){
counter+=delta;
setScale(1+counter/100);
}
};
//
function handleMouseDown(e){
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
setFocus(mouseX,mouseY);
drawMap();
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Click to set zoom point<br>Use mousewheel to zoom</h4>
<canvas id="canvas" width=600 height=400></canvas><br>