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!

Do you want to include this resource in a product offered for sale? Learn more about our Extended License

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