Practical Image Magnification using Canvas and JavaScript

Use Case: So users can easily zoom in on large images

HTML5's Canvas can help you create some pretty beautiful visuals for your website. I'm all for beauty, but I also want my programs and software to be useful as well. In this code example, I put the power of Canvas to use to allow users to simply and efficiently zoom in on large images.

Usually, for performance purposes, you wouldn't want to load an image on your page that was larger than it displayed in terms of dimensions. However, there are times when the image is the main focus of the web page - for example, a product photo - and the user should be able to take in as much of the image as possible.

Set Up the Canvas Element

We'll start by creating an HTML Canvas element and then connecting to it with our JavaScript and setting its initial dimensions based on the image that we want to use. We should also set the offset position of the canvas relative to the page as we'll need this later.

HTML

<canvas></canvas>

JavaScript

var canvas = document.querySelector('canvas')
canvas.width = 500
canvas.height = 325
canvas.top = canvas.offsetTop
canvas.left = canvas.offsetLeft
var c = canvas.getContext('2d')

Create Initial Canvas Image

Now, we want to set up the initial image to display in and fill the container. We'll set up a function called createImage that will be simple to start but we'll edit later to work with our magnification animation.

Then we'll call the function to create our first image.

JavaScript

function createImage() {
	var width = canvas.width
	var height = canvas.height
	var x = 0
	var y = 0

	var img = new Image
	img.onload = () => {
		c.drawImage(img,x,y,width,height)
	}
	img.src = 'https://images.pexels.com/photos/373912/pexels-photo-373912.jpeg'
}
createImage(0, 0, 0)

Great - so now our image displays just like a regular image on the page - at least, as far as our users are concerned. But we didn't use all of this extra code just to put a static image on the page. Let's add some interactivity.

Add Mouse Movement/Position Listener

We're going set it up so that we'll be able to zoom in and out on the image based on the position of the mouse on the canvas. This way, the user will be able to zoom in on a particular section of the image and not just the center (or the top-left - coordinates 0,0).

We'll create a onmousemove event listener on the canvas element itself that will call thecreateImage function we created and send in the x and y coordinates of the mouse on the page as arguments. We also have to accept the arguments in our function and we'll go ahead and set the x and y variables equal to these parameters minus the previously set top and left element positioning variables.

Also, we'll declare the 2 variables outside of our listener function so that we can reference them later.

JavaScript

var y = 0, x = 0
canvas.onmousemove = (e) => {
	x = e.pageX - canvas.left, y = e.pageY - canvas.top
	createImage(x,y)
}

function createImage(posX,posY) {
	...
	var x = posX
	var y = posY
	...

We have a problem though. Hover over the image below to see what it is.

The new images are being created only partially and not filling the entire canvas. We want the images to always take up the entire canvas at the very least, so we need to do a little math to make sure that our coordinates are always - at the very greatest - equal to 0.

JavaScript

function createImage(posX,posY) {
	...
	var x = 0 - (posX / canvas.width)
	var y = 0 - (posY / canvas.width)
	...

And this is what we have now. Not really much going on here but with our next step we'll start to see some improved interactivity.

Add Mouse Wheel Listener

We want our users to be able to zoom in and out on an image via the mouse wheel, or scrolling. We can do that by creating another listener event called onwheel and retrieving the delta values of the wheel action to get the intensity level of the scroll (greater intensity should equal faster scrolling, right?).

JavaScript

var intensity = 0
canvas.onwheel = (e) => {
	if (e.deltaY < 0) {
		intensity = (intensity + e.deltaY > 0) ? intensity + e.deltaY : 0
	} else {
		intensity += e.deltaY
	}
	createImage(intensity, x, y)
}

We'll start by declaring a variable called intensity and placing it outside of our scope so that it can be referenced elsewhere. Then, we'll call our function and determine if the scroll was up or down based on if the event's deltaY value was greater than or less than 0.

If less then 0 (or down) then we want to run a little conditional to calculate the intensity. Since we update the intensity value with each wheel action, if the value of intensity and deltaY is greater than 0, then we'll add the 2 together. If it's less than 0 then we'll just leave it at 0. Else, if the user is scrolling up (zooming in) then we'll keep increasing the intensity based on the deltaY value.

Finally, we'll create another image and pass in the intensity value as an argument. So that this actually has an effect, we'll need to update our createImage function to accept this new parameter.

JavaScript

function createImage(intensity, posX, posY) {
	var width = canvas.width + (intensity * (canvas.width / canvas.height))
	var height = canvas.height + intensity
	...

We want our image to scale based on the intensity value passed to it. So we'll dynamically set the width and height variables. Previously, they were just the width and height of the canvas. Now, we'll add in our intensity value. Since this image is wider than it is tall, well need to multiple the intensity value by how much bigger the image is wide than tall.

Here's what we have now:

Okay, we have some action, but there are 2 glaring things wrong here - the page continues to scroll so we can't focus on the image and the image appears to zoom in only on the top-left corner of the canvas.

Let's add an event listener to keep the window from scrolling when we're hovered over the canvas:

JavaScript

canvas.onmouseover = () => {
	document.querySelector('body').style.overflow = 'hidden'
}

canvas.onmouseleave = () => {
	document.querySelector('body').style.overflow = 'visible'
}

When our canvas is hovered over, we'll set the body tag's overflow to hidden - cutting off the rest of the page temporarily. Then, when we move our mouse off of the canvas we'll return the CSS overflow property to its default of visible.

That fixed our scrolling problem. Now we need to update the coordinates of our image so that when we zoom in with the mouse we'll be able to narrow our focus to the position of the mouse itself and not the top-left corner.

JavaScript

function createImage(intensity, posX, posY) {
	...
	var x = 0 - (posX / canvas.width) * (width - canvas.width)
	var y = 0 - (posY / canvas.height) * (height - canvas.height)
	...

Back to our createImage function, we need to add something to our coordinate values. As the size of the image changes, we want to dynamically change the coordinates based on that size difference. We'll get the size difference by substracting it from the canvas's size, and then multiplying it by the position of the mouse on the canvas.

That gives us this:

Great! We can zoom in and move our mouse around to explore the picture. Now let's just add an icon and some formatting to make it a little more user-friendly ...

Tweet me @tylerewillis

Or send me an email: