“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 { 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;
position: absolute; max-width: 700px;
}
dialog[open]::backdrop {
animation: fadeToNearBlack 1s forwards;
}
.backdrop {
animation: fadeToNearBlack 1s forwards;
}
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
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/bNdvym