3D Curtain Template

3D Curtain Template

A list of split blocks that reunite on scrolling, simulating a movement along the z-axis with the help of 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 🙌

The 2-blocks design approach is quite common nowadays. On one side the text paragraph, on the other a visual element. With this nugget we tried to “spice-up” the transition between the sections by simulating a movement along the z-axis while the user is scrolling (or using the 2 navigation buttons).

We didn’t use any CSS 3D property though, just the Scale and Translate transformations, controlled through jQuery.

Image credits: Picjumbo

Creating the structure

The HTML structure is quite basic: for each block of content we created a <section> element containing a div.cd-block and 2 div.cd-half-block. The first .cd-half-block is always empty and is used to set the background-image, while the second contains the text paragraph.

<section class="cd-section">
   <div class="cd-block">
      <h1>3D Curtain Template</h1>
   </div>
</section> <!-- .cd-section -->

<section class="cd-section">
   <div class="cd-block">
      <div class="cd-half-block"></div>

      <div class="cd-half-block">
         <p> <!-- Text here --> </p>
      </div>
   </div>
</section> <!-- .cd-section -->

<section class="cd-section">
   <!-- ... -->
</section> <!-- .cd-section -->

Adding style

On small devices we didn't implement the curtain effect so you'll see a basic layout with the list of all the sections (you can give a look at the source code for more details).
On desktop devices (viewport width more than 1170px) we assigned a position: fixed and a top: 0 to the .cd-block elements in order to place them on top of the screen (this way they are one on top of the other). Since their containers - .cd-section - have  a height: 100vh (and position: static) , they still occupy their own space (and that's way we can scroll the page).
Besides, we assigned a translateX to each .cd-half-block element (translateX(-100%) and translateX(100%) alternatively to :first-of-type and :nth-of-type(2)) so that they are moved outside the viewport.

@media only screen and (min-width: 1170px) {
  .cd-section {
    height: 100vh;
  }
  .cd-block {
    position: fixed;
    top: 0;
    left: 0;
  }
  .cd-half-block {
    height: 100vh;
    width: 50%;
    position: absolute;
    top: 0;
  }
  .cd-section:nth-of-type(even) .cd-half-block:first-of-type, 
  .cd-section:nth-of-type(odd) .cd-half-block:nth-of-type(2) {
    left: 0;
    transform: translateX(-100%);
  }
  .cd-section:nth-of-type(odd) .cd-half-block:first-of-type, 
  .cd-section:nth-of-type(even) .cd-half-block:nth-of-type(2) {
    right: 0;
    transform: translateX(100%);
  }
}

Events Handling

Each section animation is basically made up of two phases: in the first one, the 2 .cd-half-block elements are moved back in the viewport (the translateX value varies from 100%/-100% to 0); in the second one the .cd-block is scaled down and its opacity is reduced (to simulate a 3D movement).

To do so, we attached the triggerAnimation() function to the window scroll event. When the user scrolls, for each .cd-section element we evaluate - using the animateSection() function - the translateX and scale value according to the window scrollTop (and the section offset().top).

$(window).on('scroll', function(){
   triggerAnimation();
});

function triggerAnimation(){
   if(MQ == 'desktop') {
      //if on desktop screen - animate sections
      window.requestAnimationFrame(animateSection);
   } // .....
}

function animateSection () {
   $('.cd-section').each(function(){
      var actualBlock = $(this),
          scale,
          translate,
       opacity;

      //evaluate scale/translate value

      scaleBlock(actualBlock.find('.cd-block'), scale, opacity);
      translateBlock(actualBlock.find('.cd-half-block').eq(0), '-'+translate);
      translateBlock(actualBlock.find('.cd-half-block').eq(1), translate);  
   });
}

function translateBlock(elem, value) {
   elem.css({
      //...
      'transform': 'translateX(' + value + ')',
   });
}

function scaleBlock(elem, value, opac) {
   elem.css({
      //...
      'transform': 'scale(' + value + ')',
      'opacity': opac
   });
}

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.