“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
Pro CSS3 Animation, Apress, 2013
Massive Head Canon
The New Defaults
CSSslidy