Intriguing animate-on-scroll effect – jQuery tutorial

I suppose that I was not the only one to become speechless in front of the Nizo for iPhone website.
In this tutorial we try to recreate the intriguing scrolling effect. We’ll go for simplicity : 5 objects to animate and linear movement, no easing. The basis that opens the door for further elaboration.

Click here to see the demo.
My herb garden preview

Download the source files

My herb garden
.zip 0.4MB

We’ll start with the html structure :

<!DOCTYPE html>
<meta charset="UTF-8" />
<title>My herb garden</title>
<div id="wrap_out">
  <div id='wrap'>
      <h1>My <em>herb</em> garden</h1>
      <p>Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vestibulum id ligula porta felis euismod semper.</p>
    <div id="separation">
      <div>Scroll <em>down</em></div>
      <img src="images/herbes.jpg" width="592" height="350" alt="Herbes"> </div>
      <div  id="coriander" class="element">
        <img src="images/coriander1.jpg" width="286" height="232" alt="Coriander">
        <p>Cras mattis consectetur purus sit amet fermentum. Cras mattis consectetur purus sit amet fermentum. Cras mattis consectetur purus sit amet fermentum.</p>
      <div  id="rosemary" class="element">...</div>
      <div  id="lemonbalm" class="element">...</div>
      <div  id="chives" class="element">...</div>
      <div  id="basil" class="element">...</div>
    <footer> <a href=""> by <em>PeHaa</em> 2011</a> </footer>

The document is composed of 4 elements header, #separation, section and footer, contained within two divs : #wrap_out and #wrap.
The stylesheet begins with a simple css reset and the @font-face declaration

body, div, h1, h2, form, fieldset, input, textarea, footer, p, img {
	margin: 0; padding: 0; border: 0; outline: none;
a { text-decoration:none; color: #fff;}

 @font-face {
 font-family: 'DeibiRegular';
 src: url('deibi-webfont.eot');
 src: url('deibi-webfont.eot?#iefix') format('embedded-opentype'),  url('deibi-webfont.woff') format('woff'),  url('deibi-webfont.ttf') format('truetype'),  url('deibi-webfont.svg#DeibiRegular') format('svg');
 font-weight: normal;
 font-style: normal;

Right now, the important point is to set width : 100% and overflow-x: hidden for the #wrap_out. The #wrap element centers the content of the page.
section is positioned relatively and the animated herbs (class="element") are positioned absolutely.
Let’s continue with our stylesheet :

body { 
	font-family:'DeibiRegular', Helvetica, Arial, sans-serif;
	background:#f2f2f2 url('images/bg.jpg');
	color: #fff; width:100%;
#wrap_out {width:100%; overflow-x:hidden;}
#wrap {width: 960px; margin: 0 auto; z-index:1;position:relative;}

header {display:block;z-index:2 position:relative;}
header p {display:block; font-size:30px; padding: 70px 60px 0px;}
h1 {text-align:center;font-size:130px; margin-top: 30px; text-shadow: 0 0 60px #889473;}
h1 em {color: #f5f187;font-style: normal;}

#separation {margin: 0 auto;width: 592px;z-index:-1;position:relative;}

section {position:relative; margin-top: 120px;display:block}

.element { position:absolute; display:block;}
.element#coriander {top:0px; left:30px; width: 286px; height: 272px;}
.element#rosemary {top:40px; left: 335px; width: 370px; height: 183px;}
.element#chives {top : 0px; right:30px; width: 197px; height: 514px;}
.element#lemonbalm {left: 30px; top:280px; width:265px; height: 233px;}
.element#basil {right: 255px; top:250px; width:368px; height: 263px;}

footer {margin-bottom: 10px;text-align:center;font-size: 30px; display:block}

The idea is to encourage the visitor to scroll down by positioning the #separation element at the bottom of the page. We want the #separation to stay there when the window is resized but to move up when we start to scroll down. A possible way to do that is to make the height of the header element dependent on the height of the window browser :

$(document).ready(function () {
    var myHeight;

    function init() {
        myHeight = $(window).height();
        $('header').css('height', myHeight - 300);
    $(window).resize(function () {


To animate the .element we’ll go simple and use a linear function to move it from its initial position to its target position. We assign four parameters (its initial and final coordinates) to each element :

$('.element').each(function(i) {
			var myElement =$(this);
				switch (i)
				{ case 0 :'params', {top0 : -1300, x0 : -2600, top1: $(this).css('top'), x1: $(this).css('left')}); 
				case 1 :'params', {top0 : 0, x0 : -930, top1: $(this).css('top'), x1: $(this).css('left')}); 
				case 2 :'params', {top0 : 280, x0 : -1030, top1: $(this).css('top'), x1: $(this).css('left')});
				 case 3 :'params', {top0 : -1200, x0 : -2330, top1: $(this).css('top'), x1: $(this).css('right')}); 
				case 4 :'params', {top0 : 250, x0 : -530, top1: $(this).css('top'), x1: $(this).css('right')}); 

What happens when we scroll ? The top property of each element and the left (for coriander, rosemary and lemon bald) or right (for chives and basil) changes as a function of the scroll position :

$(window).scroll(function () {
var s_max = myHeight / 2 + 500;
    function move(p0, p1, s) {
        return Math.min((-p0 + p1) / s_max * s + p0, p1);
    var scrollTop = parseInt($(window).scrollTop());
    $('.element').each(function (i) {

        var myX = move($(this).data('params').x0, parseInt($(this).data('params').x1), scrollTop),
            myY = move($(this).data('params').top0, parseInt($(this).data('params').top1), scrollTop);
if (i < 3) {
                left: myX + 'px',
                top: myY + 'px'
        } else {
                right: myX + 'px',
                top: myY + 'px'

move is a function of the scroll value and has two parameters : initial (p0) and final (p1) position of an element.
Before we start scrolling, s = 0, move is equeal to p0. When scroll reaches s_max, the value of move is p1. The min operator prevents further movement.
As I said this is one of the simplest possible solutions.
What is s_max ? I wanted the animation to finish once the elements are verticaly centered in the middle of the window (I’m not pixel perfect here).
Where is that point ? Imagine that we don’t animate our elements and that they are in their final positions. Notice that we have to scroll down 500px (see the scheme below), to see the top of the second row of our herbs. If we scroll down a half height of a window more we will see them in the middle.
My herb garden preview
We want to be sure that the page is high enough so that the elements could reach their final positions. To assure that we will make the property height of section dependent on the height of the browser window, in this case we need to scroll $(window).height/2 + 500. We have 240px above the section (120px + 120px). The minimum height of section is then $(window).height/2 + 260. We add a line to the init function

function init() {
    myHeight = $(window).height();
    $('header').css('height', myHeight - 300);
    $('section').css('min-height', Math.max(myHeight / 2 + 260, 600));

It was tested in Safari, Firefox, Chrome, Opera, IE7 and IE8. It works in the last two if you don’t forget to add

<!--[if IE]>
	<script src=""></script>

I hope you find it useful. Please bookmark and share.
Let me know what you think – I’m looking forward to your comments. Thanks.

24 thoughts on “Intriguing animate-on-scroll effect – jQuery tutorial”

  1. Hi there

    Great tutorial. I have been looking for this exact effect for weeks.

    I first saw this effect on this themify wordpress template

    The effect is the same, but the elements also fade in as they animate into the screen

    I have been trying to get the same effect working, but to no avail.

    If you have time it would be a great update to this tutorial, adding fade in fade out

    Keep up the good work

  2. I’m not super familiar with javascript, is there a way to easily reverse this so that the objects are together when the page loads and then move off the screen as you scroll down?

Leave a Reply

Your email address will not be published. Required fields are marked *