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!

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

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