How to Design Animated Sliding Page Elements With CSS

There’s a recurring trend of using animated page elements in web design at the moment — as you scroll down the page, items will naturally animate into view. These animations only happen one time, and they only begin once the element is within the browser viewport.

I’ve explored this concept a bit using jQuery, along with CSS3 transitions. In a nutshell, this script checks for special classes on the page and uses jQuery to append a new class for transition effects. Those elements which have already animated are then removed from the event handler. And once there are no more elements to animate, the event handler is completely removed until you refresh the page. Take a look at my demo example to see exactly what we’re creating, and follow along!

Live DemoDownload Source Code

Basic Page Structure

To start off, I created a simple HTML5 layout including an external stylesheet and a copy of the latest jQuery library. All my custom JavaScript will be written into another external file called scrollview.js.

The animation info can be stored directly in HTML using classes and data attributes. Let’s take a look at the first animated element on my page.

<section class="clearfix">
    <div id="devices" data-position="left" class="notViewed animBlock floatl">
      <img src="img/mac-product-graphics.png" alt="apple devices">
      <!-- http://pixelsdaily.com/resources/photoshop/psds/mac-product-graphics/ -->
    </div>
  
    <div id="devicesTxt" data-position="right" class="notViewed animBlock floatr">
      <h3>The New Product</h3>
      <p>It works on <a href="http://pixelsdaily.com/resources/photoshop/psds/mac-product-graphics/">all devices</a>, I'm so serious. The responsive layout adapts to all screen resolutions.</p>
    </div>
  </section>

This contains two separate animated divs which float next to each other inside a container. The attribute data-position tells us which side the element will be coming from. They also utilize a few classes for various CSS properties.

The .floatr and .floatl classes represent floated elements to the right and left, respectively. .animBlock elements are hidden by default using zero opacity, along with transition properties for the animation.

/** animated boxes **/
.animBlock {
  display: inline-block;
  opacity: 0;
  filter: alpha(opacity=0);
  position: relative;
  -webkit-transition: all .55s ease-in;
  -moz-transition: all .55s ease-in;
  -ms-transition: all .55s ease-in;
  -o-transition: all .55s ease-in;
  transition: all .55s ease-in;
}
.animBlock[data-position="left"] { left: -20%; }
.animBlock[data-position="right"] { right: -20%; }

.animBlock[data-position="left"].viewed {
  left: 0%;
  opacity: 1;
  filter: alpha(opacity=100);
}
.animBlock[data-position="right"].viewed {
  right: 0%;
  opacity: 1;
  filter: alpha(opacity=100);
}

You’ll find this code snippet down in my style sheet. It revolves around the .notViewed and .viewed classes which tell us when an element has (or has not) been animated. Each block initially renders using .notViewed and jQuery checks these animated elements to see which have been viewed and which have not.

Upon adding the .viewed class, these elements move in 20% from the right/left side, and also transition from 0% opacity up to 100%. The techniques in CSS are fairly basic and run properly in all modern browsers. Handling the switch between classes using jQuery is when things get a little complicated.

Binding the Scroll Event

I want to move over into scrollview.js which is separate from the main index file. There are two code blocks with one being the event handler and the other being a pre-built function.

$(function() {
  var $blocks = $('.animBlock.notViewed');
  var $window = $(window);

  $window.on('scroll', function(e){
    $blocks.each(function(i,elem){
      if($(this).hasClass('viewed')) 
        return;
        
      isScrolledIntoView($(this));
    });
  });
});

The first two variables represent cached copies of jQuery objects. $blocks is a jQuery object representing all the animated blocks with .notViewed classes. Basically it should hold all of the possible animated elements allowing us to simply loop through everything. $window is called every time the user scrolls on the page, so using a cached copy is more efficient in regards to processing and memory.

The jQuery .each() method can iterate over objects and arrays. In this case we have an object holding many other objects on the page. On each scroll event this function will process every element in the $blocks objects one-by-one.

The first if{} statement saves time once an element has completed animation. Since the $blocks variable is a cached object it will not auto-update once some classes have been changed. So instead we need to check if each element has already been updated to use the class .viewed, and if so then stop the current operation to move onto the next one.

Otherwise we know the element hasn’t yet been animated into view. This is where the more detailed function isScrolledIntoView() will be run checking a number of specific properties. My code is actually modified from this post on Stack Overflow which provides an excellent starting template.

Animated Elements

I’ll break this function down into segments for easier comprehension. At first it’s pretty basic, setting up variables that are unique to the scroll event and each element passed through $block.

function isScrolledIntoView(elem) {
  var docViewTop = $(window).scrollTop();
  var docViewBottom = docViewTop + $(window).height();
  var elemOffset = 0;
  
  if(elem.data('offset') != undefined) {
    elemOffset = elem.data('offset');
  }

The first two variables docViewTop and docViewBottom will change on every scroll. I also figured sometimes we might want to animate elements before they are completely within the viewport. This is why I created an offset variable which can be applied directly into HTML. You’ll notice I am checking for a single data-offset property – which is optional – and will override the default value of 0. You’ll see this applied onto my 2nd animated element down the page:

<section>
  <div id="iphonePerspective" data-position="right" data-offset="250" class="notViewed animBlock">
    <img src="img/white-iphone-mockup.png" alt="white iphone flat ui">
    <!-- http://www.pixeden.com/psd-mock-up-templates/iphone-5-psd-flat-design-mockup -->
  </div>
</section>

The next piece of JS code checks where the current element is situated on the page. Remember this will loop through everything, so these values change based on the current viewport and which element is currently being passed through the function. Two variables elemTop and elemBottom give us numeric values referencing the full height of the current object.

 var elemTop = $(elem).offset().top;
  var elemBottom = elemTop + $(elem).height();
  
  if(elemOffset != 0) { // custom offset is updated based on scrolling direction
    if(docViewTop - elemTop >= 0) {
      // scrolling up from bottom
      elemTop = $(elem).offset().top + elemOffset;
    } else {
      // scrolling down from top
      elemBottom = elemTop + $(elem).height() - elemOffset
    }
  }

Using 0 offset there is no need to change any of the codes, we only animate when the element is in full view. But when there is an offset then we need to check whether the user is scrolling down from the top of the page, or up from the bottom. I’ve done so by subtracting the current top viewport value from the actual element’s top position.

If the element is located higher-up on the page than the user’s viewport, it will be a smaller number (closer to 0). Subtracting the element’s top value (smaller) from the viewport top value (larger) will always be a positive number or very close to zero. So the user must be lower on the page and thus scrolling up. Adding the offset to the element’s top value makes it a higher number and thus “lower” on the page, even though it doesn’t actually move anywhere. This is all figurative logic to determine when the offset animation should begin.

Otherwise the user should be coming down from the top and we subtract this offset value from the element’s bottom(now making the bottom closer). This concept is a bit confusing at first. Try thinking of these top/bottom values like pixel numbers. The very top of the window represents 0 and the very bottom of the window is the highest possible value we can get.

Switching CSS Classes

The final piece to this function creates the animation effect once the element is within view. Typically the logic statement only runs when the element is within the full viewport. Yet the offset value can allow elements to start the animation process sooner.

 if((elemBottom <= docViewBottom) && (elemTop >= docViewTop)) {
    // once an element is visible exchange the classes
    $(elem).removeClass('notViewed').addClass('viewed');
    
    var animElemsLeft = $('.animBlock.notViewed').length;
    if(animElemsLeft == 0){
      // with no animated elements left debind the scroll event
      $(window).off('scroll');
    }
  }
}

In simpler terms, the logic reads something like this: when the element’s bottom position is within view or just below view and the top position is perfectly aligned or just within view, animate the element. Both conditions must be true before JavaScript will run any code.

It should be somewhat obvious that the only requirement is to remove .notViewed and then add our animating class .viewed. This happens one time per pageload, so once an element has been placed into view it now activates the return command from my very first block of code. It still remains inside the $block object but we’re saving time by not running it through this function again.

So what happens once all of the elements are visible? We don’t want to keep this scroll event attached beyond necessity. $(‘.animBlock.notViewed’).length will return a number of total elements matching that jQuery selector. Once every element has changed to .animBlock.viewed this value will return 0.

Once this reaches 0, we just finished animating the very last element on the page. So when this finally happens we initiate .off() attached to the window’s scroll event. Now the scrolling event handler won’t trigger unless the user refreshes the page again. It makes sense to keep this method within the final logic check because it only runs as many times as there are elements to animate (in my demo that’s 5 total).

Live DemoDownload Source Code

Closing

This technique is geared towards advanced websites not necessarily catering to older legacy browsers. Most Internet users have moved on to the latest versions of IE, Firefox, Chrome, Opera, etc. But you could always check browser versions in JavaScript to force these elements into view when CSS3 transitions are not possible. Feel free to download a copy of my source code and see what else you can build using this effect.