Using CSS3 techniques a modal box can be created without JavaScript or images. With a bit of animation, transition and transform, it can be made that little bit more special.
In this experiment, clicking an ‘open’ link pops up a dialogue with a smooth hardware accelerated bounce (where supported). When open all other elements on the page are non-clickable. Closing the modal is also animated, with a minimise effect. I’ve marked up the modal using <aside>, but depending on the purpose of yours, <nav> or probably <details> might be more appropriate.
Of course, using images and JS will only make the modal better, and something like hitting ESC to close will never be reproduced in CSS. Pure CSS is rarely the best production-ready solution.
How to
The :target pseudo-selector changes the style of a targeted element. Combining a link pointing to an element with :target and altering visibility/display/opacity gives a hide/show mechanism. To facilitate the animations, which were jerky when using display:none, I’ve used a combination of :target, opacity and pointer events:
.modal {
opacity: 0;
pointer-events: none;
}
.modal:target {
opacity: 1;
pointer-events: auto;
}
The modal is two parts, one part container, one part content. Ideally the container would be generated using a pseudo-element, but I haven’t got that working yet. The container spreads across the whole page and dims the background with rgba. A high z-index puts the modal on top.
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
z-index: 10000;
…
}
The content is positioned roughly in the middle and is prettified with a sprinkling of text shadow, border radius, box shadow and gradient.
There are two animations, one named “bounce” (scale to slightly larger than normal, then fall back) and another, “minimise”, which act on the content part. These combine with a separate opacity transition on the container.
The simple opacity transition:
.modal {
…
-webkit-transition: opacity 500ms ease-in;
-moz-transition: opacity 500ms ease-in;
transition: opacity 500ms ease-in;
}
The scaling animations, although only 2D, uses scale3d for hardware acceleration. To make the bounce more realistic box shadow is also animated, which comes with a performance hit. Showing only the webkit version for brevity:
@-webkit-keyframes bounce {
0% {
-webkit-transform: scale3d(0.1,0.1,1);
box-shadow: 0 3px 20px rgba(0,0,0,0.9);
}
55% {
-webkit-transform: scale3d(1.08,1.08,1);
box-shadow: 0 10px 20px rgba(0,0,0,0);
}
75% {
-webkit-transform: scale3d(0.95,0.95,1);
-box-shadow: 0 0 20px rgba(0,0,0,0.9);
}
100% {
-webkit-transform: scale3d(1,1,1);
box-shadow: 0 3px 20px rgba(0,0,0,0.9);
}
}
@-webkit-keyframes minimise {
0% {
-webkit-transform: scale3d(1,1,1);
}
100% {
-webkit-transform: scale3d(0.1,0.1,1);
}
}
To change the animation on open we can use the cascade and override the default animation with a more specific one, using :target again:
.modal > div {
…
-webkit-animation: minimise 500ms linear;
}
.modal:target > div {
-webkit-animation-name: bounce;
}
The close button is a hidden close link with a styled ::after pseudo-element that scales on hover/focus/active. As we’re hiding the original close link, there are some hoops to jump through to make the :focus state change on the pseudo-element. The traditional clip, text indent or visibility hidden methods all fail, and I’ve resorted to color:transparent and some specific focus styles to override the confused native ones.
.modal a[href="#close"] {
…
color: transparent;
}
.modal a[href="#close"]:after {
content: 'X';
display: block;
…
}
.modal a[href="#close"]:focus:after,
.modal a[href="#close"]:hover:after {
-webkit-transform: scale(1.1,1.1);
-moz-transform: scale(1.1,1.1);
}
.modal a[href="#close"]:focus:after {
outline: 1px solid #000;
}
Caveats
It won’t work in IE8 and below, there’s no pointer-event support and opacity is poorly implemented. IE9 supports :target but no pointer-events. Some IE specific styles could easily switch the opacity toggle to a display or visibility one.
Users will still be able to tab through the links in the background and activate them. This only becomes an issue if you ignore the focus state…
As I alluded to at the start, some JS hooks for keyboard interaction wouldn’t go amiss, ESC to close and something to pull focus to the current modal and then back again on close.
The close button would probably look better with an image and the markup uses an extra containing element, which is always undesirable.
Animations can be great, in small quantities, but something that animates too much and gets in the way of functionality is a major drawback for users. It would be quite easy to go overboard with this.
Discussion
Thanks for posting this up with such a detailed description. I’ve only this week come across :target. Lots of potential uses but the usage of it seems a little awkward.
Pointer events are something I’ve never heard of before this. I’ll definitely be looking into them
Love this, great work.
For anybody that looks at this and scoffs at the limited browser support, consider some slight alterations. Use JavaScript for the triggering it open and closed (instead of :target and pointer-events). Then you are still leveraging CSS for the design and funzies, but get way deeper browser support.
A note that pointer-events property is not supported yet in HTML for Opera, however, it does work in SVG.
Really impressive, Paul. Probably going to recreate this over the weekend.
Keep up the good work.
This is a cool experiment. One other caveat I didn’t see mentioned is how clicking the back button after closing a modal re-triggers the appearance of the modals.
So sad, so many nifty CSS only solutions tutorials around the web these days. Until there is better IE support, I just don’t have time to check them all out as much as I’d like to.
Thanks for the post. Simple solution. wish it could be standard.
This is wonderful, very well done!
Like Nicolas Gallagher noted, I’d say the major drawback right now is the back button reopening the modal.
Very neat, I experimented with the same technique awhile ago for a pure css lightwindow type photo gallery.
But as others have mentioned, the way :target/fragments works with browser history (and back/forward buttons) makes this solution untenable.
Andy.
awesome man! seriously awesome.
Awesome! Until you press back
I agree with Chris Coyler. This is a great experiment but you should certainly not discard js altogether in favor of a pure CSS solution. JS/jQuery+CSS3 (throw HTML5 in there too) are a match made in heaven and could stand up against Flash anyday.
Looks good after the animation is completed, but when animating it’s very jaggy and doesn’t look good…
@Cristy: It’s works very smooth in Safari… Looks great and @Jake, when you press back you get in the state before. Works perfectly.
Great demo, thanks! Hardware graphics will change the look of web very soon i believe. (At least for modern browser users)
Awesome…but there is a small problem with Chrome 11 Beta.
A great demo, thanks. After years of such static CSS, I find this :target stuff deceptively tricky and your simple guide is appreciated. Nice one.
Awesome man.
This is a great experiment thanks!
nice work dude. simple and beautiful
This = awesome.
Thank heavens for CSS innovators like yourself!
I was checking out some modal scripts, and thought to myself that this should be done with CSS3 really; I seached, and of course here it is!
Hat-tip to you Mr. Hayes.
CSS3 never fails to amaze. Thank you so much for making it all so accessible. Pity some people are using the shortcomings of IE as an excuse not to take advantage of the rich offerings of CSS3.
ps Love last.fm
This is super cool!!
Overlay without extra markup…
I’ve also been looking for a way to do a semi-opaque overlay behind the modal using generated content (:before pseudo-element).
The trouble seems to be putting the modal in front; when using z-index on the modal the overlay (which must have a negative z-index) jumps in front for some reason, and without z-index on the modal it goes other elements in the page. One could put the entire page minus the modal in a container with a z-index of –2, but this is hardly a pretty solution.
Anyway, another way of doing a semi-opaque background would be adding an extreme box-shadow like this:
0 0 0 100000px rgba(0,0,0,0.3)
As much as this huge pixel value hurts my eyes, my browser doesn’t seem to care at all, not using any more resources resizing or doing whatever with this behemoth in the stylesheet.
It makes me wonder: How bad are monster values in CSS with regards to performance? How wrong is it to expect the user’s screen to be less than 141,421 pixels wide (supposed the modal has a border-radius)…?
love this idea
thanks this is awesome
Great work!
What is best way to open a modal with javascript? Adding a class with
opacity: 1;pointer-events: auto;
opens the modal, but without the bounce effect…
You solved a problem I was having with my own modal in this ty!
Keep using the newest tags that’s the way the future is going I love it!
vhdsjfdkc,klvcndsbjnkldsf
Web Side !
ta guowle
Comment