182 lines
5.5 KiB
Markdown
182 lines
5.5 KiB
Markdown
|
+++
|
||
|
date = "2017-03-20"
|
||
|
title = "Image Zoom with plain JavaScript and CSS"
|
||
|
tags = ["javascript", "css"]
|
||
|
description = "I'm a back-end developer and rarely dabble in the fine art of writing JavaScript. This site still uses jQuery for handling image zooming. No more!"
|
||
|
slug = "image-zoom-with-plain-javascript-and-css"
|
||
|
+++
|
||
|
|
||
|
The premise is simple. A post may contain images. These images are restricted in rendered size to
|
||
|
keep the flow of the page in tact. Clicking an image allows you to zoom in. Here's an example:
|
||
|
|
||
|
_Go ahead, click that bunny!_
|
||
|
|
||
|
![Sample zoomable image](/img/bunny.jpg)
|
||
|
|
||
|
## The CSS
|
||
|
|
||
|
Let's get the CSS out of the way first. The selector used is `article img`, which means
|
||
|
any image in the post. By default I limit it to a maximum width of its parent container.
|
||
|
Also, I change the cursor to a pointer, to indicate you can click on the image, like a link.
|
||
|
|
||
|
``` css
|
||
|
article img {
|
||
|
max-width: 100%;
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Then there are images with the `zoomed` class. This is still the same image element,
|
||
|
but with an additional class:
|
||
|
|
||
|
``` css
|
||
|
article img.zoomed {
|
||
|
position: fixed;
|
||
|
|
||
|
top: 5vh;
|
||
|
bottom: 5vh;
|
||
|
left: 5vw;
|
||
|
right: 5vw;
|
||
|
|
||
|
max-width: 90vw;
|
||
|
max-height: 90vh;
|
||
|
|
||
|
margin: auto;
|
||
|
|
||
|
border: 4px solid #000
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Okay, that's a bit more CSS, but this basically overlays the image on to the page and adds some
|
||
|
whitespace around it.
|
||
|
|
||
|
The trick to zooming is adding the `zoomed` class to the `img` element. Zooming out means
|
||
|
removing that `zoomed` class again.
|
||
|
|
||
|
Now, on to the JavaScript...
|
||
|
|
||
|
## The jQuery solution
|
||
|
|
||
|
For years now jQuery has been my go-to tool for anything JavaScript, mainly because it
|
||
|
comes bundled with Rails. (Yes, I used prototype as well in the old days.)
|
||
|
|
||
|
The jQuery solution is as you rather straight forward. Wait for the DOM to be loaded,
|
||
|
and handle `click` events on all `article img` elements. When clicked, toggle the `zoomed`
|
||
|
class.
|
||
|
|
||
|
``` javascript
|
||
|
$(function() {
|
||
|
$(document).on('click', 'article img', function() {
|
||
|
$(this).toggleClass('zoomed');
|
||
|
});
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Because I'm a keyboard user (Vim, not Emacs, thank you), I prefer to map <kbd>ESC</kbd> to
|
||
|
also close any zoomed imaged.
|
||
|
|
||
|
``` javascript
|
||
|
$(function() {
|
||
|
$(document).keyup(function(e) {
|
||
|
if (e.keyCode == 27) {
|
||
|
$('img.zoomed').each(function(idx) {
|
||
|
$(this).toggleClass('zoomed');
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Again, hook into the `keyup` event, check if <kbd>ESC</kbd> was pressed and toggle
|
||
|
the `zoomed` class for all zoomed in images.
|
||
|
|
||
|
But, using jQuery means adding an extra dependency: 1 extra HTTP request and 85kB download for you.
|
||
|
Also, the few friends I have who practice JavaScript tell me that _pure_ JavaScript is the way to
|
||
|
go these days. So, let's try!
|
||
|
|
||
|
## The JavaScript solution
|
||
|
|
||
|
With some help from the [You Might Not Need jQuery](http://youmightnotneedjquery.com/) website,
|
||
|
I managed to drop the 85kB big jQuery dependency and rewrite the above functionality in plain old
|
||
|
JavaScript.
|
||
|
|
||
|
First, let's write a function that waits for the DOM to load.
|
||
|
|
||
|
``` javascript
|
||
|
function ready(fn) {
|
||
|
if (document.readyState != 'loading') {
|
||
|
fn();
|
||
|
} else {
|
||
|
document.addEventListener('DOMContentLoaded', fn);
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
This was taken straight from You Might Not Need jQuery to wrap any functions you want to run
|
||
|
when the document has loaded fully.
|
||
|
|
||
|
Next I wrote a function to handle toggling the `zoomed` CSS class on the images:
|
||
|
|
||
|
``` javascript
|
||
|
function imageClick(e) {
|
||
|
e.preventDefault();
|
||
|
this.classList.toggle('zoomed');
|
||
|
}
|
||
|
```
|
||
|
|
||
|
It turns out that JavaScript is more than capable of toggling classes on its own.
|
||
|
|
||
|
While we're at it, let's also write the function that handles the <kbd>ESC</kbd> presses.
|
||
|
|
||
|
``` javascript
|
||
|
function handleEsc(e) {
|
||
|
if (e.keyCode == 27) {
|
||
|
var zoomedImages = document.querySelectorAll('img.zoomed');
|
||
|
Array.prototype.forEach.call(zoomedImages, function(el, i) {
|
||
|
el.classList.toggle('zoomed');
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
This is a bit more involed. I still check for the proper `keyCode`, and then proceed to
|
||
|
find all zoomed images using `document.querySelectorAll`. It's really that easy.
|
||
|
|
||
|
Next I use the `Array` prototype to map a function to each zoomed image. That function simply
|
||
|
toggles the `zoomed` class, just like `imageClick` does.
|
||
|
|
||
|
What remains is nothing more than some glue to put the above fuctions together.
|
||
|
|
||
|
``` javascript
|
||
|
ready(function() {
|
||
|
var images = document.querySelectorAll('article img');
|
||
|
Array.prototype.forEach.call(images, function(el, i) {
|
||
|
el.addEventListener('click', imageClick);
|
||
|
});
|
||
|
|
||
|
document.addEventListener('keyup', handleEsc);
|
||
|
});
|
||
|
```
|
||
|
|
||
|
Here I use the `ready` function I wrote. Just like `handleEsc`, I find all
|
||
|
`article img` elements and add the event listener for clicks. Then I also
|
||
|
add an event listener for the <kbd>ESC</kbd> key.
|
||
|
|
||
|
## Conclusion
|
||
|
|
||
|
Rewriting a trivial piece of jQuery code to plain JavaScript appears to be more
|
||
|
than worth the while. Besides the warm fuzzy feeling of dumping jQuery, it saves
|
||
|
quite a few kilobytes from each page on devroom.io. Especially for mobile users
|
||
|
this matters.
|
||
|
|
||
|
`git` says 11 deletions (bye, jQuery) and 27 additions (hello, JavaScript). This does
|
||
|
not tell the full story, as one of these deleted lines is this one:
|
||
|
|
||
|
``` html
|
||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||
|
```
|
||
|
|
||
|
I can highly recommend you take a closer look at what your jQuery code is _actually doing_
|
||
|
and consider moving away from unnecessary dependencies. Yay for lean and mean web pages!
|
||
|
|