3D fold effects are pretty popular nowadays, mostly because they have been integrated quite often in mobile apps. A beautiful example is the iOS Peek Calendar app. Thanks to CSS3 transformation and transitions, we can create a similar interaction in the browser!

Sometimes these 3D effects feel too strong, unnecessary. It’s a point I can’t argue with.

However, there will be cases when you need to load content, a process that requires time (even if it’s just half a second). In these cases an animation can be a nice way to replace a loading bar, or a loading gif. Besides, with the growth of native apps built on top of web frameworks, learning how to create complex CSS transformations is an ace up your sleeve ;)

Creating the structure

The HTML is structured in 2 main elements: an unordered list, containing the .cd-item blocks and wrapped in a <main> element, and a .cd-folding-panel element, containing the panel content ( .cd-fold-content ) and the 2 folds ( .left-fold and .right-fold ).

<main class="cd-main"> <ul class="cd-gallery"> <li class="cd-item"> <a href="item-1.html"> <div> <h2>Title 1</h2> <p>Lorem ipsum dolor sit amet, consectetur.</p> <b>View More</b> </div> </a> </li> <li class="cd-item"> <!-- content here --> </li> <!-- additional list items here --> </ul> <!-- .cd-gallery --> </main> <!-- .cd-main --> <div class="cd-folding-panel"> <div class="fold-left"></div> <!-- this is the left fold --> <div class="fold-right"></div> <!-- this is the right fold --> <div class="cd-fold-content"> <!-- content will be loaded using javascript --> </div> <a class="cd-close" href="#0"></a> </div> <!-- .cd-folding-panel -->

Adding style

To realise our animation, we used CSS3 Transformations applied to the .left-fold , .right-fold , .cd-main and .cd-item elements.

The 2 folds are created animating the ::after pseudo-elements of the .left-fold and .right-fold .

On mobile, we animate only the right fold ( .left-fold element has a display: none): by default, the .cd-folding-panel (and its child .right-panel ) has a position fixed and covers the entire viewport (but its visibility is set to hidden), while the .right-panel::after is translated to the left and rotated ( translateX(-100%) rotateY(-90deg) , with transform-origin: right center ).

When user clicks one of the .cd-item , the .cd-main content is translated to the right (using the .fold-is-open class), while the .right-fold::after is translated into the viewport and rotated back (using the .is-open class assigned to the .cd-folding-panel ).

.cd-main { overflow-x: hidden; } .cd-main > * { transition: transform 0.5s 0.4s; } .cd-main.fold-is-open > * { /* on mobile - translate .cd-main content to the right when the .cd-folding-panel is open */ transform: translateX(100%); transition: transform 0.5s 0s; } .cd-folding-panel { position: fixed; top: 0; left: 0; width: 100%; height: 100vh; visibility: hidden; overflow: hidden; transition: visibility 0s 0.9s; } .cd-folding-panel .fold-left, .cd-folding-panel .fold-right { /* the :after elements of .fold-left and .fold-right are the 2 fold sides */ width: 100%; height: 100vh; overflow: hidden; /* enable a 3D-space for children elements */ perspective: 2000px; } .cd-folding-panel .fold-right { perspective-origin: 0% 50%; } .cd-folding-panel .fold-left { /* on mobile only the right fold side is visible */ display: none; } .cd-folding-panel .fold-right::after { /* 2 fold sides */ content: ''; position: absolute; top: 0; left: 0; height: 100%; width: 100%; transform-origin: right center; transform: translateX(-100%) rotateY(-90deg); transition: transform 0.5s 0.4s, background-color 0.5s 0.4s; } .cd-folding-panel.is-open { visibility: visible; transition: visibility 0s 0s; } .cd-folding-panel.is-open .fold-right::after { transform: translateX(0); transition: transform 0.5s 0s, background-color 0.5s 0s; }

On desktop devices (viewport width > 1100px), both ::after pseudo-elements are animated: the .cd-folding-panel is now placed in the center of the viewport (width: 800px), the .left-panel and .right-panel have a float: left and width equal to half of their parent (400px). Their ::after pseudo-elements are both rotated (rotateY(-90deg)) and translated to the left ( .left-panel::after - translateX(100%)) or to the right ( .right-panel::after - translateX(-100%)).

When user clicks one of the .cd-item , they are translated to the left ( :nth-of-type(2n+1) ) or to the right ( :nth-of-type(2n) ), while both ::after pseudo-elements are translated and rotated back (rotateY(0) translateX(0)).

@media only screen and (min-width: 1100px) { .cd-item { width: 50%; float: left; transition: transform 0.5s 0.4s; } .fold-is-open .cd-item { transition: transform 0.5s 0s; transform: translateX(-400px); } .fold-is-open .cd-item:nth-of-type(2n) { transform: translateX(400px); } } @media only screen and (min-width: 1100px) { .cd-folding-panel { left: 50%; transform: translateX(-50%); width: 800px; } .cd-folding-panel .fold-left, .cd-folding-panel .fold-right { width: 50%; float: left; height: 100%; } .cd-folding-panel .fold-right { /* change perspective-origin so that the 2 fold sides have the same vanishing point */ perspective-origin: 0% 50%; } .cd-folding-panel .fold-right::after { transform-origin: right center; transform: translateX(-100%) rotateY(-90deg); } .cd-folding-panel .fold-left { display: block; /* change perspective-origin so that the 2 fold sides have the same vanishing point */ perspective-origin: 100% 50%; } .cd-folding-panel .fold-left::after { transform-origin: left center; transform: translateX(100%) rotateY(90deg); } .cd-folding-panel.is-open .fold-right::after, .cd-folding-panel.is-open .fold-left::after { transform: translateX(0); transition: transform 0.5s 0s, background-color 0.5s 0s; } }



One important note: each ::after pseudo-element has, as default vanishing point for its 3D space, the center of its parent (so in our case, the center of the .left-panel and .right-panel ). For the animation to work properly, we changed perspective-origin of these 2 elements in order to have the center of the viewport as vanishing point.

.cd-folding-panel .fold-right { perspective-origin: 0% 50%; } .cd-folding-panel .fold-left { perspective-origin: 100% 50%; }

Events handling

In the index.html file, the .cd-fold-content element is initially empty.

When user selects one of the .cd-item elements , we used the load() function to insert the proper content inside the .cd-fold-content (we created a new html file - item-1.html, item-2.html, ... - for each .cd-item in order to store the new content).

Once the new html content has been inserted, the proper classes are assigned and the animation is triggered.

/* open folding content */ $('.cd-gallery a').on('click', function(event){ event.preventDefault(); openItemInfo($(this).attr('href')); }); function openItemInfo(url) { /* check if mobile or desktop */ var mq = viewportSize(); if( $('.cd-gallery').offset().top > $(window).scrollTop() && mq != 'mobile') { /* if content is visible above the .cd-gallery - scroll before opening the folding panel */ $('body,html').animate({ 'scrollTop': $('.cd-gallery').offset().top }, 100, function(){ toggleContent(url, true); }); } else { toggleContent(url, true); } } function toggleContent(url, bool) { if( bool ) { /* load and show new content */ $('.cd-fold-content').load(url+' .cd-fold-content > *', function(event){ $('body').addClass('overflow-hidden'); $('.cd-folding-panel').addClass('is-open'); $('.cd-main').addClass('fold-is-open'); }); } else { /* close the folding panel */ $('.cd-folding-panel').removeClass('is-open') $('.cd-main').removeClass('fold-is-open'); /* ...*/ } }

Note: we implemented a basic load() function to upload new content, but you may want to replace it with, for example, a proper $.ajax call in order to handle errors, beforeSend request etc. according to your project.