2
\$\begingroup\$

I'm experimenting in canvas and trying to create pan effect. I've managed to pan succesfully, but I'm curious if I'm doing it correctly. Since I've come up with the following code myself, I don't know if there is a better way to accomplish the task.

JSFiddle

var canvas    = document.getElementById("canvas");
var context   = canvas.getContext("2d");
canvas.width  = 600;
canvas.height = 400;

var global = {
	scale	: 1,
	offset	: {
		x : 0,
		y : 0,
	},
};
var pan = {
	start : {
		x : null,
		y : null,
	},
	offset : {
		x : 0,
		y : 0,
	},
};

function draw() {
	context.setTransform(1, 0, 0, 1, 0, 0);
	context.clearRect(0, 0, canvas.width, canvas.height);
	
	context.translate(pan.offset.x, pan.offset.y);
	
	context.beginPath();
	context.rect(50, 50, 100, 100);
	context.fillStyle = 'skyblue';
	context.fill();
	
	context.beginPath();
	context.arc(350, 250, 50, 0, 2 * Math.PI, false);
	context.fillStyle = 'green';
	context.fill();
}

draw();

canvas.addEventListener("mousedown", startPan);
canvas.addEventListener("mouseleave", endPan);
canvas.addEventListener("mouseup", endPan);

function startPan(e) {
	canvas.addEventListener("mousemove", trackMouse);
	canvas.addEventListener("mousemove", draw);
	pan.start.x = e.clientX;
	pan.start.y = e.clientY;
}

function endPan(e) {
	canvas.removeEventListener("mousemove", trackMouse);
	canvas.removeEventListener("mousemove", draw);
	pan.start.x = null;
	pan.start.y = null;
	global.offset.x = pan.offset.x;
	global.offset.y = pan.offset.y;
}

function trackMouse(e) {
	var offsetX  = e.clientX - pan.start.x;
	var offsetY  = e.clientY - pan.start.y;
	pan.offset.x = global.offset.x + offsetX;
	pan.offset.y = global.offset.y + offsetY;
}
body {
	background: gainsboro;
	margin: 0;
}
canvas {
	background: white;
	box-shadow: 1px 1px 1px rgba(0,0,0,.2);
}
<canvas id="canvas"></canvas>


Updated version of the pan (using the tips Flambino provided):

JSFiddle

var canvas    = document.getElementById("canvas");
var context   = canvas.getContext("2d");
canvas.width  = 600;
canvas.height = 400;

var global = {
	scale	: 1,
	offset	: {
		x : 0,
		y : 0,
	},
};
var panning = {
	start : {
		x : null,
		y : null,
	},
	offset : {
		x : 0,
		y : 0,
	},
};

function draw() {
	context.beginPath();
	context.rect(50, 50, 100, 100);
	context.fillStyle = 'skyblue';
	context.fill();
	
	context.beginPath();
	context.arc(350, 250, 50, 0, 2 * Math.PI, false);
	context.fillStyle = 'green';
	context.fill();
}

draw();

canvas.addEventListener("mousedown", startPan);

function pan() {
	context.setTransform(1, 0, 0, 1, 0, 0);
	context.clearRect(0, 0, canvas.width, canvas.height);
	context.translate(panning.offset.x, panning.offset.y);
	draw();
}

function startPan(e) {
	window.addEventListener("mousemove", trackMouse);
	window.addEventListener("mousemove", pan);
	window.addEventListener("mouseup", endPan);
	panning.start.x = e.clientX;
	panning.start.y = e.clientY;
}

function endPan(e) {
	window.removeEventListener("mousemove", trackMouse);
	window.removeEventListener("mousemove", pan);
	window.removeEventListener("mouseup", endPan);
	panning.start.x = null;
	panning.start.y = null;
	global.offset.x = panning.offset.x;
	global.offset.y = panning.offset.y;
}

function trackMouse(e) {
	var offsetX	 = e.clientX - panning.start.x;
	var offsetY	 = e.clientY - panning.start.y;
	panning.offset.x = global.offset.x + offsetX;
	panning.offset.y = global.offset.y + offsetY;
}
body {
	background: gainsboro;
	margin: 0;
}
canvas {
	background: white;
	box-shadow: 1px 1px 1px rgba(0,0,0,.2);
}
<canvas id="canvas"></canvas>

\$\endgroup\$
1
\$\begingroup\$

Seems fine to me!

The only things I would be add would be to set up the endPan event handlers in mousedown (and remove them in endPan) the same way you do with mousemove.

I'd also separate the draw function from the translate function. Those two tasks are separate: One handles the panning, regardless of what's to be drawn, and the other draws, regardless of the panning. So having that in one function is lumping too many things into one.

You could instead have a pan function that sets the translation, and then calls draw(). That'll keep things apart.

From a UX standpoint, I'd add the mousemove and mouseup listeners to window, and skip the mouseleave listener. It's jarring when the panning stops just because you moved outside the canvas. It feels more natural for the panning to just keep following your mouse as long as hold the button down.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the tips. Implemented them right away! Now, off to creating zoom effect :) \$\endgroup\$ – akinuri Aug 7 '16 at 0:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.