“Lightbox” effects have been an established UI pattern for a decade, but the vast majority of implementations have been framework-dependent. In previous articles I’ve shown how to create a Lightbox UI using only CSS, although that version lacked controls. By adding the <dialog> element and a dozen lines of vanilla JavaScript, we can recreate a traditional lightbox perfectly:
The Markup
<nav id="thumbs">
<a href="elephant.jpg"><img src="elephant-thumb.jpg" alt></a>
<a href="taj-mahal.jpg"><img src="taj-mahal-thumb.jpg" alt></a>
<a href="wise-man.jpg"><img src="wise-man-thumb.jpg" alt></a>
</nav>
<dialog id="cover">
<button id="closecover">Close</button>
<img src="" alt>
</dialog>
The linked images follow the pattern used in my previous “Accessible Image Gallery with Progressive JavaScript” article; the large image inside the <dialog> element is a placeholder that we’ll change later using JavaScript. (alt values have been left blank for the sake of brevity).
The CSS
@keyframes fadeToNearBlack{
to { background: rgba(0,0,0,0.9); }
}
@keyframes goBig { to { opacity: 1; } }
nav { display: flex; }
nav a { display: block; flex: 1; }
nav a img, dialog img { width: 100%; height: auto; }
dialog { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); border: none; opacity: 0; }
dialog button { border: none; background: none; font-size: 1.2rem; }
dialog[open] {
animation: goBig 1s .4s forwards;
width: 70%; margin: auto;
max-width: 700px;
}
dialog[open]::backdrop { animation: fadeToNearBlack 1s forwards; }
.backdrop { animation: fadeToNearBlack 1s forwards; }
dialog img { width: 100%; height: auto; }
The animations will fade in the <dialog> element; the <nav> links are displayed using flexbox to divide them evenly across the page. When visible, the <dialog> element is positioned using a variation on one of the established methods of centering elements when the height of the parent (the <body>, in this case) is unknown. (Vendor prefixes have been dropped for the purpose of clarity).
The JavaScript
The script goes at the end of the document; other browsers will require an additional polyfill to support the <dialog> element.
function showImage(e) {
e.preventDefault();
coverimage.setAttribute("src", this.getAttribute("href"));
coverimage.setAttribute("alt", this.querySelector("img").getAttribute("alt"));
cover.showModal();
}
document.getElementById("closecover").onclick = function() {
coverimage.setAttribute("src", "");
cover.close();
}
var imglinks = document.getElementById("thumbs").getElementsByTagName('a'),
cover = document.getElementById("cover"),
coverimage = cover.getElementsByTagName("img")[0];
testdialog=document.createElement("dialog");
testdialog.setAttribute("open", "");
if (!testdialog.open) { dialogPolyfill.registerDialog(cover); }
for (var i=0; i<imglinks.length; i++) { imglinks[i].onclick = showImage; }
The script attaches a function to each of the links; when the user clicks on one, it sets the src attribute of the large image in the <dialog> element to the value of the link and shows the modal window; the elements animate due to the CSS we applied earlier. Clicking on the altered <button> element closes the modal. Because the thumbnail images are links, they are also progressive and accessible.
Conclusion & Improvements
There are a few things that could be better; obviously, being able to drop the polyfill once all browsers support the <dialog> element would be good, and the element itself could be animated like the version in my CSS-only example. Centering the element perfectly across different platforms could also be improved: right now, things move around a little more than I would like. I’ll leave that and further enhancements for a future article.
Photographs by Richard Kardhordó, licensed under Creative Commons
Play with the code for this gallery on CodePen