devroom.io/content/posts/2017-03-20-image-zoom-with-plain-javascript-and-css.md

186 lines
5.8 KiB
Markdown
Raw Normal View History

2017-03-20 22:59:43 +00:00
+++
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"
+++
2017-11-13 21:46:01 +00:00
_Note: I've moved away from the solution outlined in this post. Mainly because I did not want
to write an entire gallery from scratch. I've since opted to use [lightgallery.js](https://sachinchoolur.github.io/lightgallery.js/)
which is a pure JS image gallery solution with some nice goodies._
2017-03-20 22:59:43 +00:00
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!