See the Pen 3D Paper Dolls UI by Dudley Storey (@dudleystorey) on CodePen.

For some time I’ve wanted to create a successor to my Origami UI that included sequentially unfolding panels. Unlike the paper dolls you made in elementary school, these components are folded inside each other, to make for a more interesting dynamic animation as they unfold.

Slightly expanded but still folded up, the elements would appear something like the first illustration. For most photographs in the UI, <figcaption> elements are used to create the appearance of paper backings.

Because each group of images needs to fold out in sequence, the markup is more complex:

<figure id="dolls" onclick="unwrap()">
	<img src="brazillian-indian-child.jpg" alt>
		<figure id="dolls-inner1">
			<img src="burmese-child-monk.jpg" alt>
			<figcaption>Faces Around The World</figcaption>
				<figure id="dolls-inner2">
				<img src="egyptian-girl.jpg" alt>
				<figcaption></figcaption>
					<figure id="dolls-inner3">
						<img src="lesotho-girl.jpg" alt>
						<figcaption></figcaption>
					</figure>
				</figure>
		</figure>
</figure>

The first three <figure> elements are diagrammed in illustration 2:

Illustration 2: First three <figure> elements

The containing <div> is given a reasonable perspective value, and each <figure> “hinged” from its left-hand side:

* { 
	box-sizing: border-box;
}
#dolls { 
	width: 100%;
	font-size: 0;
	perspective: 1500px;
}
#dolls:hover {
	cursor: pointer;
}
#dolls figure {
	margin: 0;
	display: inline-block;
	transform-origin: left center;
	transform-style: preserve-3d;
	position: relative;
}
#dolls > img { width: 25%;  }
#dolls figure {
	transition-duration: 1s;
}
#dolls-inner1 > figcaption { 
	border: 2px solid #333;
}
#dolls-inner1 figcaption span {
	display: block;
	font-size: 1.2rem;
}

The initial image is set to ¼th the overall width of the container, and every <figure> given a transition duration of one second:

#dolls > img { width: 25%;  }
#dolls figure { transition-duration: 1s; }

The <figcaption> elements are used to create the impression of each photograph having a “back” surface by moving them along the Z axis by one pixel.

#dolls-inner1 > figcaption {
	border: 2px solid #333;
}
#dolls figcaption {
	width: 100%;
	height: 100%;
	position: absolute;
	top: 0; left: 0;
	transform: translateZ(-1px);
	background-image: url(paper-background.jpg);
	background-size: contain;
	font-size: 1.4rem;
	text-align: center;
	padding-top: .5rem;
}

There’s a .rewrap class that will be applied to the container <div>, used to roll back the images once they are unwrapped.

.rewrap #dolls-inner1 {
	transition-delay: 2s;
}
.rewrap #dolls-inner2 {
	transition-delay: 1s;
}
.rewrap #dolls-inner3 {
	transition-delay: 0s;
}

If the browser window is at least 751 pixels wide, the unwrapped images will be displayed side-by-side. The deeply nested elements creates an interesting effect on the images inside them, which have to be scaled to compensate:

@media screen and (min-width: 751px) {
	#dolls-inner1 {
		width: 75%;
		transform: rotateY(-175deg);
	}
	#dolls-inner1 figcaption {
		transform: translateZ(-2px) rotateY(180deg);
	}
	#dolls-inner1 > * {
		width: 33.33%;
	}
	#dolls-inner2 {
		width: 50%;
		transform: rotateY(-175deg); 
	}
	#dolls-inner2 > * {
		width: 66.66%;
	}
	#dolls-inner3 {
		width: 25%;
		transform: rotateY(-180deg);
	}
	#dolls-inner3 > * {
		width: 266%;
	}
}

When they unwrap, each image group is transitioned until it becomes almost flat:

.unwrap #dolls-inner1,
	.unwrap #dolls-inner2,
	.unwrap #dolls-inner3 {
		transform: rotateY(-1deg);
}
.unwrap #dolls-inner2 {
	transition-delay: 1s; 
}
.unwrap #dolls-inner3 {
	transition-delay: 2s; 
}

If browser is less than 750px wide, the images appear stacked on top of each other:

@media screen and (max-width: 750px) {
	#dolls {
		width: 80%;
		margin: 2rem auto;
	}
	#dolls figure {
		transform-origin: center top;
		width: 100%; display: block; 
	}
	#dolls-inner1, #dolls > img,
		#dolls-inner1 > *, #dolls-inner2 > *,
		#dolls-inner3 > *  {
			width: 100%;
		}
	#dolls-inner1 {
		transform: rotateX(175deg); 
	}
	#dolls-inner1 > figcaption {
		transform: translateZ(-1px) rotateX(180deg);
	}
	#dolls-inner2 {
		transform: rotateX(177deg);
	}
	#dolls-inner3 {
		transform: rotateX(178deg);
	}
	#dolls-inner1 > figcaption {
		height: 33%;
	}
	#dolls-inner2 > figcaption {
		height: 50%;
	}
	#dolls-inner3 > figcaption {
		height: 100%;
	}
	.unwrap #dolls-inner1,
		.unwrap #dolls-inner2,
		.unwrap #dolls-inner3 {
			transform: rotateX(-1deg);
	}
	unwrap #dolls-inner2 {
		transition-delay: 1s;
	}
	.unwrap #dolls-inner3 {
		transition-delay: 2s;
	}
}

Finally, JavaScript is used to initiate the unwrapping and rewrapping of the <figure> elements:

function unwrap(){
	if (dolls.classList.length == 0 || dolls.classList.contains("rewrap")) {
		dolls.classList.remove("rewrap");
		dolls.classList.add("unwrap");
		} else {
		dolls.classList.remove("unwrap");
		dolls.classList.add("rewrap");
		}
}

I am certain that there are more effective ways of achieving this result, which I will be exploring in future: I look forward to your comments and feedback!

Photographs by David Lazar

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/JLtvD