It’s very easy to create an , but many developers stop right there. Making the JavaScript progressive (built in such a way that even if it is blocked or fails, functionality is not lost) and accessible (usable by everyone, regardless of their ability) takes only a little extra effort, as this article demonstrates.

In this example, I have three images: three thumbnails, linked to the large version of each. It all starts with markup:

<section>
	<div id="javascript-gallery" tabindex="0">
		<a href="gorbea-natural-park-spain.jpg">
			<img src="gorbea-natural-park-spain-thumb.jpg" alt="Gorbea Natural Park, Spain">
		</a>
		<a href="otzarreta-forest-bizkaia-spain.jpg">
			<img src="otzarreta-forest-bizkaia-spain-thumb.jpg" alt="Otzarreta Forest, Bizkaia, Spain">
		</a>
		<a href="saint-maria-de-rio-seco-monastary.jpg">
			<img src="saint-maria-de-rio-seco-monastary-thumb.jpg" alt="Saint Maria de Rio Seco-monastery">
		</a>
	<figure id="fullimagecontainer">
	</figure>
</section>

Note the <figure> element at the end, which will contain the large version of the image, generated by JavaScript. Even if that script never loads, clicks on the thumbnail images will bring up the large versions. The tabindex attribute ensures that the list of links is keyboard navigable by pressing the TAB key, with a link activated by pressing Enter.

I’ll arrange the images using , although there are many other layout possibilities:

section {
	max-width: 800px; margin: 0 auto;
	font-size: 0; background: #000;
}
div#javascript-gallery { display: flex; }
section figure { margin: 0;  }
section figure img {
	width: 100%; height: auto;
}
div#javascript-gallery a {
	flex: 1 1 1%; opacity: .3; transition: .3s;
}
div#javascript-gallery a:hover,
div#javascript-gallery a:focus { 
	opacity: 1; 
}

Each thumbnail is dimmed by default, with a mouse hover or a keyboard focus on a link creating the same visual result.

The script goes at the end of the page. It starts by identifying the elements that we’re going to work with, creates the large image, and appends it inside the <figure> element:

var jgallery = document.getElementById("javascript-gallery"),
largeimagecontainer = document.getElementById("fullimagecontainer"),
links = jgallery.getElementsByTagName('a'),
largeimage = document.createElement("img");
largeimage.setAttribute("id", "fullimage");
largeimagecontainer.appendChild(largeimage);

Next, we add a function call to each of the links:

for (var i=0; i<links.length; i++) {
	links[i].onclick = handler;
}

That function prevents the links from working. Instead, it uses the href value from the activated link, and the alt from the image within it, to set the attributes of the large image:

function handler(e) {
	e.preventDefault();
	largeimage.setAttribute("src", this.getAttribute("href"));
	largeimage.setAttribute("alt", this.querySelector("img").getAttribute("alt"));
	largeimage.animate([{ opacity: '0'}, { opacity: '1'}], { duration: 500 });
}

Traditionally it’s been very difficult to stop, reset and restart CSS animations. The new Web Animations API addresses that, aligning CSS, JavaScript and animations into one syntax. Right now the Web Animations API is only supported in the latest versions of Chrome, Opera and the Android browser, although work in other browsers is progressing fast: in those browsers, no transition will take place, but the large image will still appear.

The last thing we need to do is bring up a large image by default: I’ll assume it will be the image associated with the first thumbnail.

links[0].focus();
links[0].click();

The first of these two lines grabs focus; the second acts as a surrogate click, as if the user has activated the first link. Thereafter, they can use mouse or keyboard navigation to choose whatever image they wish.

Conclusion

There are two downsides to this approach:

  1. The thumbnail image and its large version contain the same alt information, which by its nature is somewhat limited.
  2. There may be a delay in presenting the large images in bandwidth-limited devices, such as mobile. Pre-loading the large images will eliminate that problem entirely.

In the next article, I’ll show how to get around both issues, and improve production workflow, by preloading images and reading metadata with JavaScript.

Photographs by Jesús Ignacio Bravo Soler, licensed under Creative Commons.

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/XJWqgz