Autumn, by Lucien Agasse

While Codrops and CodePlayer have shown recent variations of this technique, I felt their solutions were lacking in several regards: the images weren’t mobile-ready, the markup lacked semantic value, and the CSS tended to be overly complex. Thus, this derivation and tutorial…


The HTML5 code is very simple, relying primarily on <figure> and <figcaption> elements:

		<figcaption>Autumn, by Lucien Agasse</figcaption>

The <div> element contains the context of 3D perspective; it will also ensure that user interactions are captured by an element that remains static on the screen.


div {
	perspective: 1000px;
	width: 33%;
	margin: 0 auto;
	margin-top: 3rem;

The <figure> contains a full-width background image that is cut off by a height set in vw units (and thus responds to the width of the browser window).

figure {
	margin: 0;
	width: 100%;
	height: 29.5vw;
	position: relative;
	background-size: 100%;
	background: url("winter-hat.jpg");
	transform-origin: center bottom;
	transform-style: preserve-3d;
	transition: 1s transform;

The transform-origin of the <figure> element is set to “swing” from its lower edge, like a trapdoor. Next, the image caption:

figcaption {
	width: 100%;
	height: 50px;
	position: relative;
	top: 29.5vw;
	background: linear-gradient(rgba(0, 0, 0, 0.4),
		rgba(0, 0, 0, 0.4)),
	background-size: 100%;
	background-position: bottom;
	color: #fff;
	transform-origin: center top;
	transform: rotateX(-89.9deg);
	font-size: 1.2vw;
	text-align: center;
	line-height: 3;

The <figcaption> element is provided with a set height and moved to the exact position where the <figure> is cut off. The element uses the same background-image as the <figure>, only starting from the bottom of the element, rather than the top. This background is overlaid with a “wash” of rgba color to fade it, a technique I’ve discussed before. Then, the <figcaption> is “hinged” from the top and folded back by a little less than 90 degrees.

Creating The Shadow

The shadow is a pseudo-element generated from the <figure>, its size and shape determined by the rule that absolutely positioned elements take their cues from relatively positioned parents:

figure:before {
	content: '';
	position: absolute;
	top: 0; left: 0;
	width: 100%;
	height: 100%;
	box-shadow: 0 0 100px 50px rgba(0, 0, 0, 0.1),
		inset 0 0 250px 250px rgba(0, 0, 0, 0.1);
	transition: 1s;
	transform: rotateX(95deg) translateZ(-80px) scale(0.75);
	transform-origin: inherit;

The shadow is given the appearance of “solidity” by using both a standard outer box-shadow and an inset one. Then, it is rotated back while being moved and scaled down, providing the impression of a “ground”.

Adding The Transition

The movement of the <figure> and its shadow are initiated by hovering over the <div> element:

div:hover figure {
	transform: rotateX(75deg) translateZ(5vw);
div:hover figure:before {
	box-shadow: 0 0 25px 25px rgba(0, 0, 0, 0.5),
		inset 0 0 250px 250px rgba(0, 0, 0, 0.5);
	transform: rotateX(-5deg) translateZ(-80px) scale(1);

The <figure> is rotated back while being moved down, and the box-shadow rotated up to meet it. At the same time, the shadow darkens and focuses (note the altered alpha and blur values). As part of the <figure> (and because we added transform-style: preserve-3d earlier) the <figcaption> moves in synchronicity.

Adding Breakpoints

The image is perfect for large viewports, but requires some intervention at smaller sizes: realistically, we can’t leave the <div> at ⅓rd the browser window width if the screen is only 320 pixels wide. <figcaption> will also need some intervention, as set vw units do not remain appropriate for text across the entire spectrum of screen sizes.

@media screen and (max-width: 800px) {
	div { 
		width: 50%;
	figure {
		height: 45vw;
	figcaption {
		top: 45vw;
		font-size: 2vw;
@media screen and (max-width: 500px) {
	div {
		width: 80%;
		margin-top: 1rem;
	figure {
		height: 70vw;
	figcaption {
		top: 70vw;
		font-size: 3vw;

Adaption For Mobile

Support for :hover is inconsistent across mobile platforms; a quick, albeit imperfect solution is to add an empty onclick action for the <div>:

<div onclick="">

There’s much more that can be done with this interaction pattern, as I’ll show in future articles.

Photograph by Lucien Agasse, licensed under Creative Commons