3D Folding Panel

3D Folding Panel

A secondary content panel that folds flat, powered by CSS Transformations and jQuery.

Nucleo icons

Sponsored by Nucleo, a free application to collect, customize and export all your icons as icon font and SVG symbols. Made by the CodyHouse folks!

All the resources available on CodyHouse are released under the MIT license. You can support our project with a Paypal donation 🙌

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.

Join our newsletter

Get our monthly recap with the latest CodyHouse news

We use cookies to give you the best possible website experience. By using CodyHouse, you agree to our Privacy Policy.