Tuesday, April 19, 2011

Vertical Parallax Effect for the Entire Page

I found a game site (Elder Scrolls) that has a really cool vertical scrolling effect that I wanted to duplicate, but it only works in webkit browsers. I didn't make it into a plugin, but instead made this tutorial, because if you understand the basics, you can add more background images or use CSS3 to add multiple background images.



This is a screen shot of the web page while it is at the top. Note the location of the building tower top is near the top of the logo that is under "Generic".


This screen shot shows how the top of "Box 1" is now above the building tower. Blocks in the foreground move slightly faster than the background image.


Here is the final demo of the vertical scrolling effect (see it as a full page).

How it's done

The Images
This effect uses two background images. The top image (bg1.jpg, 1920x1080 pixels) fades to solid black on its bottom edge.

It's made to be a bit blurry, but you can duplicate the game site's background image by keeping the foreground sharp and the background blurry... it's a cooler effect ;)
 

The second background image (bgtile.jpg, 963x1641 pixels) is added as a background image that appears under the above top image. The css is adjusted so that the top of this image appears below the top image so that you don't see a straight black line cut through the top.

This image is blurry and mostly black, it fades to solid black at the edges, with a rock-like texture. It makes the vertical parallax scroll very subtle, so you may want to adjust it as desired.


The other two images that are used are the title/logo image (title.png, 400x300 pixels).



And the box background (bg-black-55.png, 20x20 pixels) which is a png file of solid black with a 55% opacity.


The Markup
This is very basic HTML markup. The body contains the repeating background tile image (bgtile.jpg). A wrapper is immediately inside the body which contains the top background image (bg1.jpg). These two images could be combined in the body tag if you use css3; then you could use the wrapper for additional images. The Header contains the logo (title.png) and each block of content has the box background image (bg-black-55.png) applied. Fill in the "div.content" block with whatever you want.
<body> <!-- contains repeated background image -->
<div id="wrapper"> <!-- contains top image -->

<div id="header"></div> <!-- contains the page title image -->

 <div class="block"> <!-- contains 55% opacity background image -->
  <h3>Block 1</h3>
  <div class="content">Content 1.</div>
 </div>

 <div class="block"> <!-- contains 55% opacity background image -->
  <h3>Block 2</h3>
  <div class="content">Content 2.</div>
 </div>

 <div class="block"> <!-- contains 55% opacity background image -->
  <h3>Block 3</h3>
  <div class="content">Content 3.</div>
 </div>

 <div class="block"> <!-- contains 55% opacity background image -->
  <h3>Block 4</h3>
  <div class="content">Content 4.</div>
 </div>

 <div class="block"> <!-- contains 55% opacity background image -->
  <h3>Block 5</h3>
  <div class="content">Content 5.</div>
 </div>

</div>

</body>

The CSS
The CSS below doesn't have a lot of comments, but the important parts to focus on are the main background image positions.
body {background: #333; color: #ddd;}
a:link { color: #ddd; }
a:hover { text-decoration: underline; }
a:visited,a:active { color: #999; }

/* Tiled background image */
body {
 margin: 0;
 padding: 0;
 /* Use height of header image for top position */
 background: #000 url(https://2.bp.blogspot.com/-yoacGtVJQ4o/Ta2M6iJiLVI/AAAAAAAAACw/XuY_7_Yhh-A/s200/bgtile.jpg) left 1080px repeat-y;
}
/* Top background image (1920x1200) */
#wrapper {
 position: relative;
 top: 0;
 left: 0;
 height: 100%;
 width: 100%;
 background: url(https://2.bp.blogspot.com/-6vIxC2INCc0/Ta2M5xmVREI/AAAAAAAAACo/T-dk_UmEdB4/s200/bg1.jpg) center top repeat-x;
 z-index: 100;
}
/* Page Title image */
#header {
 height: 350px;
 background: url(https://1.bp.blogspot.com/-9_howS6OiMI/Ta2M7EuxbWI/AAAAAAAAAC0/8C9-4BQrVSg/s200/title.png) center 40px no-repeat;
}
/* Content Block with 55% opacity background image */
.block {
 width: 400px;
 height: 500px;
 margin: 20px auto;
 border: #333 1px solid;
 padding: 20px;
 background: url(https://1.bp.blogspot.com/-eywohnk1LMk/Ta2M6St6EPI/AAAAAAAAACs/7RYel72y0Tw/s1600/bg-black-55.png);
}
.block h3 {
 font-family: 'Arial Black', Gadget, sans-serif;
 font-size: 130%;
}

The Script
jQuery(document).ready(function(){
  // saved values
  var st, 
   // get window and body elements
   win = jQuery(window)[0],
   body = jQuery('body')[0],
   wrap = jQuery('#wrapper')[0],
   // make sure we target the correct document (in IE)
   doc = (jQuery.support.boxModel) ? document.documentElement : document.body,
  
   // Set top background image height here, in pixels
   imgH = 1080;

  // vertical parallax scroll code
  jQuery(win).scroll(function(){
   // get the page scroll top (IE uses doc.scrollTop)
   st = (win.pageYOffset || doc.scrollTop );

   // Limit moving top image (in the "wrap") only when in view (st < imgH)
   // Doing this reduces the amount of DOM activity so it doesn't slow down the scrolling.
   // Here is where the background position of the top image is moved by 1/4 (st/4) of the total amount scrolled
   // Increasing the fraction (making it closer to one) will speed up the background parallax scroll
   // Decreasing the fraction will slow down the parallax scroll
   if (st < imgH) { wrap.style.backgroundPosition = 'center ' + (st/4) + 'px'; }

   // This line limits the tiled background image in the "body"
   body.style.backgroundPosition = 'left ' + ( imgH + st/4) + 'px';
  });
});
Code breakdown
  • The first and last line wrap the code which is run when the document is ready. This is a jQuery method and can be written several different ways. Basically, any code inside is run when the basic structure and code of the page has completed loading. Images may or may not have completed loading at this time.
    jQuery(document).ready(function(){
      // code to run
    });
  • The next few lines save the variables that are repeatedly used in the script. Saving them reduces the amount of time taken to run code inside the scroll function. See the optimization section for more details.
    // saved values
    var st, 
     // get window and body elements
     win = jQuery(window)[0],
     body = jQuery('body')[0],
     wrap = jQuery('#wrapper')[0],
     // make sure we target the correct document (in IE)
     doc = (jQuery.support.boxModel) ? document.documentElement : document.body,
    
    *Note that each line ends with a comma - it is defining multiple variables with one "var".

  • The next saved variable saves the top background image height (bg1.jpg). This is needed so the script can figure out where to start the tiling of the repeated background image (bgtile.jpg). This ensures that the tiled background image isn't overlapped by the top background image making the user see a sharp black edge at the overlap. The script also uses this height to stop the parallax scroll of the top background image when it isn't in view. The reason for this is discussed in the optimization section below.
    // Set top background image height here, in pixels
    imgH = 1080;
  • Now we bind to the window scroll event. This event is fired everytime the browser window is scrolled. So it can be called hundreds of times while scrolling down the page. This is the reason the code inside this function is kept as minimal as possible.
    // vertical parallax scroll code
    jQuery(win).scroll(function(){
    
      // code run each time the page is scrolled
    
    }); // second to last line in the completed script
  • Get the scroll top of the page. Check standard browsers variable, and if not defined (IE) then get the document scroll top.
    // get the page scroll top (IE uses doc.scrollTop)
    st = (win.pageYOffset || doc.scrollTop );
  • Apply the parallax to the top background image. 
    // Here is where the background position of the top image is moved by 1/4 (st/4) of the total amount scrolled
    // Increasing the fraction (making it closer to one) will speed up the background parallax scroll
    // Decreasing the fraction will slow down the parallax scroll
    if (st < imgH) { wrap.style.backgroundPosition = 'center ' + (st/4) + 'px'; }
    • The first thing we do is check if the scroll top (st) is less than the top background image height. If it is, then use parallax on the top background image.
    • "wrap" is one of our saved variables that points to the page wrapper. Here we set the background position using direct DOM manipulation. See optimization section for more details.
    • The background position has two parts - "X Y" positions. We care centering the image from left to right (the X-position), then taking one fourth (1/4) of the scroll top position and making that the background's Y-position. This is basically how the parallax scroll works. It scroll 1/4 less than the actual page. If you add another layer (say wrapper 2), it should be scrolled slightly more (a number closer to one, like 2/3).
    • So here the top background image is scrolled 1/4 less (1/4 * st) than the page itself. Make this number closer to one (i.e. 3/4 * st) to make the scroll more like the page scroll. Or make the number further from one (i.e. 1/8 * st) to make the scroll a tiny bit. Making the number too small may make the background image appear fixed, so don't go too crazy ;)
  • Apply the parallax to the tiled background image.
    // This line limits the tiled background image in the "body"
    body.style.backgroundPosition = 'left ' + ( imgH + st/4) + 'px';
    • Now we are applying the background position to the body of the page, where the tiled background image (bgtile.jpg) is seen.
    • Here we are place the background image on the left edge (centering should be fine too) and again 1/4 of the scroll top of the page. In addition we add the top image height (imgH) to keep the tiled background image aligned to the bottom of the top background image. So we make sure not to see that black image edge.
    • Since the body background image is behind the wrapper background image, you could make this parallax scroll even slower to give more of a feel of it being further off in the distance. This would look especially good with multiple background images available when using css3.
Optimizations
  • This script was optimized so that a minimal amount of code is run inside the scroll function.
  • This ensures that the code doesn't slow down the page scroll, because the scroll function is called constantly. It's probably not a big deal is modern browsers, but is very noticeable in older browsers. 
  • This was done, in part, by saving variables. This limits the amount of look ups that need to be done by the script. Using $(window) or $(document) needs to call several functions to get a value, so saving it in a variable saves time.
  • To set the background position, the script sets the background position directly in the DOM. Again, this was done to minimize function calls. I didn't have any trouble with cross browser compatibility in any browsers, including Opera and IE7+.