+++ 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 ESC 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 ESC 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 ESC 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 ESC 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 ``` 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!