3D Grid Effect

A proof-of-concept effect recreation of the animation seen in a prototype app by Marcus Eckert. The idea is to flip a grid item in 3D, expand it to fullscreen and reveal some associated content. We’ve created two demos with a vertical and a horizontal rotation.

From our sponsor: Learn from healthcare leaders in Northwestern’s Online MS in Healthcare Administration.

Today we’d like to share a little animation concept with you. It’s the recreation of an effect we spotted in this fantastic prototype app by Marcus Eckert. The idea is to rotate a grid item in 3D, expand it into fullscreen and reveal some content. For our attempt to imitate the app behavior, we created two demos. In the first one we rotate the grid item vertically and in the second one horizontally.

Please note that this is just a proof-of-concept and that we are using several CSS properties that might not work in every browser (pointer-events, 3D Transforms, CSS Transitions). It is highly experimental and for browsers not supporting either one of those properties, we provide a simple fallback that just shows and hides the content.

The amazing illustrations featured in the demos are by Adam Quest. View all images from the Selected editorials 2014 Vol.1 on Behance and visit his Facebook page.

The way we simulate the effect seen in the prototype app video, is by creating a placeholder element that will have a front and a back face. The front will contain a copy of the grid item content and the back will be white. When clicking on a grid item, we add the absolutely positioned placeholder and position it in the clicked item’s position. Then we animate its width and height to the window’s width and height and set the top and left to negative values, making it move outside of the grid into the top left corner of the page. While we are doing that, we’ll also rotate the element in 3D, revealing the white back side of the placeholder. Once it fills the screen, we’ll fade in the content which we stored in a different division.

The illusion, that it’s actually the grid item that flies out and rotates, is completed by fading out the clicked grid item so that we can only see the placeholder being moved out of the grid.

In addition to the placeholder moving towards the viewer, we also move the grid away by translating it on the Z-axis.

Please note, that this is just a proof-of-concept. You might want to load your content dynamically (see the dummy loading effect) or show something else as the main content, i.e. a fullscreen image. The fallback is also very simple and general.

Let’s take a look at the markup and some important styles to understand the concept of how the effect is done.

We have a main section element which contains a division for the grid and one for the content:

<section class="grid3d vertical" id="grid3d">
	<div class="grid-wrap">
		<div class="grid">
			<figure><img src="img/1.jpg" alt="img01"/></figure>
			<figure><img src="img/2.jpg" alt="img02"/></figure>
			<figure><img src="img/3.jpg" alt="img03"/></figure>
			<!-- ... -->
	</div><!-- /grid-wrap -->
	<div class="content">
			<div class="dummy-img"></div>
			<p class="dummy-text">Some text</p>
			<p class="dummy-text">Some more text</p>
			<!-- ... -->
		<!-- ... -->
		<span class="loading"></span>
		<span class="icon close-content"></span>

When we click on a grid item, we’ll go by order to choose the matching content division. The content division also contains two spans, one for the activity indicator to simulate some loading, and one for the closing cross.

The placeholder for our rotation and expansion effect will be added dynamically to the grid and the structure is the following:

<div class="placeholder">
	<div class="front"><!-- content of clicked grid item --></div>
	<div class="back"></div>

Let’s have a look at some crucial styles.

The grid-wrap division will be the element with perspective:

.grid-wrap {
	margin: 10px auto 0;
	max-width: 1090px;
	width: 100%;
	padding: 0;
	perspective: 1500px;

In addition to the perspective value, we also set the perspective origin. But that we do dynamically depending on the view. If you, for example, view this grid on a smaller device where the grid height becomes very large, we don’t want the default perspective origin of 50% 50% because then an item that is far from the center of the whole grid will rotate away from the viewport (down or up). We’ll set the origin to be central to the viewer instead.

The grid will have a transition and we need to assign a preserve-3d transform-style to it because we want to be able to rotate its children in 3D:

.grid {
	position: relative;
	transition: all 0.5s cubic-bezier(0,0,0.25,1);
	transform-style: preserve-3d;

When we click on a grid item, we add the class view-full to the grid which will make the grid move away from us:

.view-full .grid {
	transform: translateZ(-1500px);

When we click on a grid item, we’ll make it fade out (our placeholder will be put in place):

.grid figure.active {
	opacity: 0;

The placeholder will have its pointer-events set to none and it will be positioned absolutely, unlike the other figures in the grid. It also needs the preserve-3d transform-style because we want to flip its backside:

.grid .placeholder {
	pointer-events: none;
	position: absolute;
	transform-style: preserve-3d;
	transition: all 0.5s ease-out;

The front and back side of the placeholder follow the classic 3D “card” style:

.placeholder > div {
	display: block;
	position: absolute;
	width: 100%;
	height: 100%;
	backface-visibility: hidden;

.placeholder .front img {
	width: 100%;
	height: 100%;

.placeholder .back {
	background: white;
	transform: rotateY(180deg);

The cloned image that we add to the front face of the placeholder is stretched to the full width and height to fill all the element.

Once we open a grid item we want the transition to have a bit of a delay and a custom timing-function:

.view-full .placeholder {
	transition: all 0.5s 0.1s cubic-bezier(0,0,0.25,1);

Depending on which kind of rotation we want, we set the according class to the main wrapper and set the following transform:

.vertical .view-full .placeholder {
	transform: translateZ(1500px) rotateX(-179.9deg);

.horizontal .view-full .placeholder {
	transform: translateZ(1500px) rotateY(-179.9deg);

This should of course be -180 degrees! But because browsers seem to have the liberty to choose which direction they rotate in this case, we need to set it to this awkward number (Firefox and Chrome choose opposing directions with -180 degrees).

The content division has fixed positioning and it will be fullscreen. We also want its inner content divisions to be scrollable, so we set the overflow-y to scroll and add -webkit-overflow-scrolling: touch for better mobile scrolling behavior:

.content {
	/* ... */
	overflow-y: scroll;
	height: 0;
	background: #fff;
	visibility: hidden;
	z-index: 400;
	-webkit-overflow-scrolling: touch;

When we want to open a specific content division, we first show the main content container:

.content.show {
	height: auto;
	pointer-events: auto;
	visibility: visible;

Each content division is placed absolutely and we set the initial opacity value to 0. We also need to set the height to 0 so that other invisible divisions don’t define the height of the whole thing.
To show a specific division, we need to set the following style:

.content > div.show {
	height: auto;
	opacity: 1;	
	transition: opacity 0.6s;

For the dummy content we define some transitions for the opacity and the transform. Depending on which rotation we choose, we set the initial translation of the dummy items to translate on the Y or the X-axis. Then, when we add the class “show” to their parent, we animate the translation to 0.

Take a look at the commented JavaScript to see how the functionality is implemented. As mentioned before, the fallback for browsers that don’t support some of the CSS properties in use, we provide a very basic fallback.

We hope you enjoy this little effect and find it inspiring!

Tagged with:

Stay up to date with the latest web design and development news and relevant updates from Codrops.