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.

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:

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

👾 Hey! Looking for the latest in frontend? Twice a week, we'll deliver the freshest frontend news, website inspo, cool code demos, videos and UI animations right to your inbox.

Zero fluff, all quality, to make your Mondays and Thursdays more creative!

Feedback 54

Comments are closed.
  1. Great Job Mary Lou, THANKS A LOOOOT.

    i was wondering if there is any way to add navigation (next, previous) to go from one panel to another

    • I agree! Adding navigation to this would make it more usable than it is currently. However, it’s still a great job! Thumbs up Mary Lou!

  2. Hey, first thing, this effect is amazing, have a way to doing with mysql this effect?

    • Yes. You can do this with mysql . I’m waiting for the guys at the open source community to give the go ahead then I’d show you.

  3. Amazing!!!!!

    Mary Lou, i’ve a question: I’m trying put some text in “” but the same is not presented, only the “dummy” effect is presented, can you help me?

  4. It would be awesome if someone could turn this into a WordPress Plugin. Simply Amazing! That’s why I love Codrops!!

  5. I cannot seem to get different content to load for different tiles… What am I not doing?

  6. I am trying to use this great effect on a one page design website but it just works for the first section and none of the later sections. I have no idea why but i love it anyway! So elegant and beautiful.

  7. Tried to implement this but it didn’t work. I checked console and got this:
    TypeError: this.contentEl is null on grid3d.js:37
    please help!!!

  8. Hello,

    I’m using this 3D grids system, and i’m thankfull to you.

    But i have a problem with it, it works in chrome, but it doesn’t work in mozilla. Can you help me ?

    Actually i’m working on my website : nicolashofer.ch


  9. Hey!

    I need to use this thrice in one page. Can you help me out please!

    • Duplicate <section id="grid3d">...</section> and give it another unique id eg. <section id="grid3d-xyz">...</section>
      Add this script before </body>
      <script> new grid3D( document.getElementById( 'grid3d' ) ); new grid3D( document.getElementById( 'grid3d-xyz' ) ); </script>
      This works for me.
      Just a two cents from a code monkey.

  10. I wish someone can give me a direction how to make it works for ajax content.

  11. Hello, how i can load a SWF file in this 3d flip gallery? Embed and object don’t works 🙁

  12. When the content opens, the image is not responsive. Is there any way to make it full screen and responsive?

  13. thanks for this amazing demo.

    i have a question.
    how do i set the image in the clicked grid as a responsive image?
    i didn’t manage to succeed.

  14. How would I go about adding previous/next buttons to the javascript?
    Can’t seem to be able to get the position of the currently expanded item :/ (noob!)

  15. Hello
    Thank you for this, the animation , it looks good.
    As it’s not ready yet for every webbrowers, i would like to re-use the responsive grid and the content apparition system.
    Is it possible to use only the simple fallback ?
    I suppose i have to change some things in grid3d.js, but i don’t know what.
    i would like to keep the content apparation without the rotation and loader effects. Is this possible ?

    Thank you

  16. Love it, only it’s not depricating well on mobile devices (testing on Android; didn’t go past that one since it didn’t work there): Can’t close the destination screen; actually, you can, however you need to scroll to the end first, and even then it’s glitchy. But definitely something to keep in mind as technology catches up to your creativity! Would love to use it if anyone has a more full proof fallback to suggest.

  17. Hi Mary Lou! Appreciate your work !
    Just simple question:
    How can I change an images after I click on the tile ( dummy_img) that they gonna be different for each tile?

  18. How can i edit the display text, when user click on the pic ?
    The text shown is dummy and can’t able to identify, from where it would be edited .

    Any help ll be highly appreciated.


  19. Any knows how to remove the ‘All’ from the filter. I don’t want it there and currently trying to remove it from the elastic_grid.js and also the within the HTMl script but no joy.
    Any suggestions would be appreciated. Thanks in advance

  20. Hi Mary Lou,

    Great tutorial. I have tried testing it out on a test page but it’s not working. When I checked my chrome console it says,

    Uncaught TypeError: Cannot read property ‘children’ of null
    grid3D._init @grid3d.js:37
    grid3D @grid3d.js:19
    (anonymous function) @test.html:298 – This line is from my test file that has the script “new grid3D( document.getElementById( ‘grid3d’ ) );”

    Every scripts and code is include and triple checked. I didn’t make any changes. I didn’t include all the images, just 2 for simple testing but it’s just not working. I looked at your code in the console but there was no error like mine.

    Do you know what am doing wrong? Your help would be greatly appreciated

  21. Hi,
    Thanks for nice script with demo

    I have one question.
    Effect not working after the ajax resposive, as per my knowlege we need to reinislize javascript,

    also i added this code “new grid3D( document.getElementById( ‘grid3d’ ) );” at ajax response but not working..
    any one have idea then let me know.
    Yuvraj R K

  22. Hello Mary,

    Wonder full script i have ever used , but there is problem with multiple figure images, i am using a dynamic grid of more than 25 images, so that classie.js is given problem “Uncaught TypeError: Cannot read property ‘classList’ of undefined” , i ahve search a lot but no solution for that, please help me !!!
    Please reply as soon as possible.