On Scroll Effect Layout

An on scroll effect template that animates the sides of sections once they are in the viewport.

Blueprint ScrollEffect

This Blueprint is a template for a on scroll effect layout. We’ve had some requests on how to achieve this effect of animating elements once they are in the viewport and decided to create a Blueprint for it. It works with adding a class for animating the two sides of a section. There is an example effect defined and also some media queries for dealing with smaller screens.

On devices that support touch, we will not apply any effects due to the scrolling logic on touch devices. Please read more about this issue in this article by TJ VanToll: onscroll Event Issues on Mobile Browsers.

Resource credit: iPad PSD Flat Mockup by Pixeden.

The HTML

<div id="cbp-so-scroller" class="cbp-so-scroller">

	<section class="cbp-so-section">
		<article class="cbp-so-side cbp-so-side-left">
			<h2>Lemon drops</h2>
			<p>Fruitcake toffee jujubes. Topping biscuit sesame snaps jelly caramels jujubes tiramisu fruitcake. Marzipan tart lemon drops chocolate sesame snaps jelly beans.</p>
		</article>
		<figure class="cbp-so-side cbp-so-side-right">
			<img src="images/1.png" alt="img01">
		</figure>
	</section>

	<section class="cbp-so-section">
		<figure class="cbp-so-side cbp-so-side-left">
			<img src="images/2.png" alt="img01">
		</figure>
		<article class="cbp-so-side cbp-so-side-right">
			<h2>Plum caramels</h2>
			<p>Lollipop powder danish sugar plum caramels liquorice sweet cookie. Gummi bears caramels gummi bears candy canes cheesecake sweet roll icing dragée. Gummies jelly-o tart. Cheesecake unerdwear.com candy canes apple pie halvah chocolate tiramisu.</p>
		</article>
	</section>

	<section>
		<!-- ... -->
	</section>
	<!-- ... -->
	
</div>

The CSS

.cbp-so-scroller {
	margin-top: 50px;
	overflow: hidden;
}

.cbp-so-section {
	margin-bottom: 15em;
	position: relative;
}

/* Clear floats of children */
.cbp-so-section:before,
.cbp-so-section:after {
	content: " ";
	display: table;
}

.cbp-so-section:after {
	clear: both;
}

/* Text styling */
.cbp-so-section h2 {
	font-size: 5em;
	font-weight: 300;
	line-height: 1;
}

.cbp-so-section p {
	font-size: 2em;
	font-weight: 300;
}

/* Sides */
.cbp-so-side {
	width: 50%;
	float: left;
	margin: 0;
	padding: 3em 4%;
	overflow: hidden;
	min-height: 12em;
}

/* Clear floats of children */
.cbp-so-side:before,
.cbp-so-side:after {
	content: " ";
	display: table;
}

.cbp-so-side:after {
	clear: both;
}

.cbp-so-side-right {
	text-align: left;
}

.cbp-so-side-left {
	text-align: right;
}

.cbp-so-side-right img {
	float: left;
}

.cbp-so-side-left img {
	float: right;
}

/* Initial state (hidden or anything else) */
.cbp-so-init .cbp-so-side {
	opacity: 0;
	-webkit-transition: none;
	-moz-transition: none;
	transition: none;
}

.cbp-so-init .cbp-so-side-left {
	-webkit-transform: translateX(-80px);
	-moz-transform: translateX(-80px);
	transform: translateX(-80px);
}

.cbp-so-init .cbp-so-side-right {
	-webkit-transform: translateX(80px);
	-moz-transform: translateX(80px);
	transform: translateX(80px);
}

/* Animated state */
/* add you final states (transition) or your effects (animations) for each side */
.cbp-so-section.cbp-so-animate .cbp-so-side-left,
.cbp-so-section.cbp-so-animate .cbp-so-side-right {
	-webkit-transition: -webkit-transform 0.5s, opacity 0.5s;
	-moz-transition: -moz-transform 0.5s, opacity 0.5s;
	transition: transform 0.5s, opacity 0.5s;
	-webkit-transform: translateX(0px);
	-moz-transform: translateX(0px);
	transform: translateX(0px);
	opacity: 1;
}

/* For example, add a delay for the right side:
.cbp-so-section.cbp-so-animate .cbp-so-side-right {
	-webkit-transition-delay: 0.2s;
	-moz-transition-delay: 0.2s;
	transition-delay: 0.2s;
}
*/

/* Example media queries */

@media screen and (max-width: 73.5em) {
	.cbp-so-scroller {
		font-size: 65%;
	}

	.cbp-so-section h2 {
		margin: 0;
	}

	.cbp-so-side img {
		max-width: 120%;
	}
}

@media screen and (max-width: 41.125em) {
	.cbp-so-side {
		float: none;
		width: 100%;
	}

	.cbp-so-side img {
		max-width: 100%;
	}
}

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

The JavaScript

/**
 * cbpScroller.js v1.0.0
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Copyright 2013, Codrops
 * http://www.codrops.com
 */
;( function( window ) {
	
	'use strict';

	var docElem = window.document.documentElement;

	function getViewportH() {
		var client = docElem['clientHeight'],
			inner = window['innerHeight'];
		
		if( client < inner )
			return inner;
		else
			return client;
	}

	function scrollY() {
		return window.pageYOffset || docElem.scrollTop;
	}

	// http://stackoverflow.com/a/5598797/989439
	function getOffset( el ) {
		var offsetTop = 0, offsetLeft = 0;
		do {
			if ( !isNaN( el.offsetTop ) ) {
				offsetTop += el.offsetTop;
			}
			if ( !isNaN( el.offsetLeft ) ) {
				offsetLeft += el.offsetLeft;
			}
		} while( el = el.offsetParent )

		return {
			top : offsetTop,
			left : offsetLeft
		}
	}

	function inViewport( el, h ) {
		var elH = el.offsetHeight,
			scrolled = scrollY(),
			viewed = scrolled + getViewportH(),
			elTop = getOffset(el).top,
			elBottom = elTop + elH,
			// if 0, the element is considered in the viewport as soon as it enters.
			// if 1, the element is considered in the viewport only when it's fully inside
			// value in percentage (1 >= h >= 0)
			h = h || 0;

		return (elTop + elH * h) <= viewed && (elBottom) >= scrolled;
	}

	function extend( a, b ) {
		for( var key in b ) { 
			if( b.hasOwnProperty( key ) ) {
				a[key] = b[key];
			}
		}
		return a;
	}

	function cbpScroller( el, options ) {	
		this.el = el;
		this.options = extend( this.defaults, options );
		this._init();
	}

	cbpScroller.prototype = {
		defaults : {
			// The viewportFactor defines how much of the appearing item has to be visible in order to trigger the animation
			// if we'd use a value of 0, this would mean that it would add the animation class as soon as the item is in the viewport. 
			// If we were to use the value of 1, the animation would only be triggered when we see all of the item in the viewport (100% of it)
			viewportFactor : 0.2
		},
		_init : function() {
			if( Modernizr.touch ) return;
			this.sections = Array.prototype.slice.call( this.el.querySelectorAll( '.cbp-so-section' ) );
			this.didScroll = false;

			var self = this;
			// the sections already shown...
			this.sections.forEach( function( el, i ) {
				if( !inViewport( el ) ) {
					classie.add( el, 'cbp-so-init' );
				}
			} );

			var scrollHandler = function() {
					if( !self.didScroll ) {
						self.didScroll = true;
						setTimeout( function() { self._scrollPage(); }, 60 );
					}
				},
				resizeHandler = function() {
					function delayed() {
						self._scrollPage();
						self.resizeTimeout = null;
					}
					if ( self.resizeTimeout ) {
						clearTimeout( self.resizeTimeout );
					}
					self.resizeTimeout = setTimeout( delayed, 200 );
				};

			window.addEventListener( 'scroll', scrollHandler, false );
			window.addEventListener( 'resize', resizeHandler, false );
		},
		_scrollPage : function() {
			var self = this;

			this.sections.forEach( function( el, i ) {
				if( inViewport( el, self.options.viewportFactor ) ) {
					classie.add( el, 'cbp-so-animate' );
				}
				else {
					// this add class init if it doesn´t have it. This will ensure that the items initially in the viewport will also animate on scroll
					classie.add( el, 'cbp-so-init' );
					
					classie.remove( el, 'cbp-so-animate' );
				}
			});
			this.didScroll = false;
		}
	}

	// add to global namespace
	window.cbpScroller = cbpScroller;

} )( window );

Manoela Ilic

Manoela is the main tinkerer at Codrops. With a background in coding and passion for all things design, she creates web experiments and keeps frontend professionals informed about the latest trends.

Stay in the loop: Get your dose of frontend twice a week

Fresh news, inspo, code demos, and UI animations—zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 60

Comments are closed.
  1. Thanks Mary. This is a really nice article. I’ve wanted to know more about this effect since I first saw it but never had the time. I can’t wait to play with your demo and see what effects I can add to it.

  2. What a great timing! 🙂 I just finished my wireframe with this effect, thank you!

  3. Wow, great! Only a thing, in Chrome (28.0.1500.71) the document flicker when you scroll down. It’s all ok in Safari…

    • I’m seeing this flicker in Chrome as well. I’d be interested to see the underlying cause and how to fix that up.

  4. Excelente articulo MARY LOU, justo lo que necesita para el rediseño de mi web.

  5. I’m so surprised to see this blueprint working properly in IE10 and not working at all in Chrome… what’s wrong with my browsers ?!!

    Anyway, Mary, another great work from you, thanks!

  6. Lovely like always Mary.

    I have one small question that I hope you can answer for me.
    In all of your tutorials on codrops, your javascript is wrapped with this:
    ;( function( window ) { ... } )( window );

    What does this do exactly? Is it a DOM ready function? Or are you passing the window object?

    I am trying to move away from my reliance on jQuery towards a lighter JavaScript skill-set.

    Cheers
    Dean

    • The “;” on the first line prevents some nasty errors that can occur while minimizing/concatenating js files.
      ( function (window) { ... })(window);
      This creates an annonymous and self-executing function to create an isolated scope, so you won’t pollute global namespace with your variables. It takes one argument — the window object. It is just a convenient way to create an alias for window object if you use it a lot. I usually see a slightly more advanced example of such a function:
      ( function (w, d, undefined) { ... })(window, document);
      This aliases window as w, and document as d, and also makes sure undefined is undefined (that’s older js legacy, probably useless now).

  7. I hope you don’t mind such an ignorant question. I am taking online classes to improve my understanding of css, html and java, I just haven’t gotten to this particular part yet and online research has proved fruitless.

    In WordPress, where specifically would you add the the html and javascript? I understand the css would go into the stylesheet/CSS, correct? I just haven’t been able to figure out the html and javascript part.

    Again, I apologize for asking such a primary question. I guess I’m not working the search inquiries correctly to get the answer.

    Your site has been a great resource in learning the basics of coding and seeing it work in an actual code situation. Thank you.

    • Hye,

      The javascript can be written in footer.php i guess (since its included in every page).. And for HTML one of the relevant php files, though you should be careful while editing them.

      Hope it helps.

  8. Excellent Mary Lou!

    As a friendly suggestion, consider doing the blueprint for a three column adaptive – responsive website (see the new Mashable) next.

  9. I am trying to get this to fit my desired animation. I changed this.sections = Array.prototype.slice.call( this.el.querySelectorAll( '.cbp-so-section' ) ); to this.sections = Array.prototype.slice.call( this.el.querySelectorAll( '.featured-icon' ) ); hoping that it would target all of my divs with the class “featured-icon” but it returned this.el is null. Any tips?

  10. I’ve been trying to stop the effects when scrolling back up but didn’t succeed so far. This must be something very easy to achieve. Does anyone know how to do it.
    Thank you!

  11. It doesn’t work in IE. Even the demo URL fails in rendering the effect in IE8 giving an error “Webpage error details – Message: JScript object expected
    Line: 87 Char: 4 Code: 0 ” in the js file. Any comments ?

  12. Beautiful work, I love it. Can anyone help me implement this concept on a joomla site, or has anyone done it successfully. I am trying but not there yet.

    • Create a custom html module for the html. Upload the css and js files to e.g. /templates/yourtemplate/css/ or …/js/ respectively and load them by adding references to the head of your template default layout (probably templates/layouts/default.php or theme.php or whatever. Then create a new empty article. Create a menu item to your article. Publish the custom html module to the mainbody position of your article (many templates have the option to override the main content output publishing a module to the mainbody position if yours doesn’t, just put the html in an article body). Bob’s yer uncle.

  13. Is there a way to add a scroll to bottom that works with the slide in animation? I have been trying to implement a smooth scroll to bottom on this page, but it just jumps straight to the bottom. It seems that the slide in animation is over ridding anything I add. Thanks!

  14. Hi Mary Lou,
    I love your work – absolutely fantastic stuff!
    One thing though, the demo texts are making me extremely hungry 🙂

    All the best,

    Jared

  15. I am getting some strange issue, which inturn breaks all my other jquery plugins.

    I have slightly changed the HTML markup to be articles instead of figures, I do not want to use images but rather all text. I have not touched the JS code.

    Website Design

    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry’s standard dummy text ever since the 1500s

    My JS console gives me this Uncaught TypeError: Cannot call method ‘querySelectorAll’ of null on line 87 of cbpScroller.js

    Has anyone come across this issue?

  16. I’m trying everything on the site is behind him because the menu is fixed and does not know where I move this code to move backward.
    Can someone help me

  17. I am seeing the Error come up in IE8, is there any way to work around this, so at least when it doesn’t work in the browser the error doesn’t come up?

  18. Thanks a lot!
    Not all understand, but works very well.
    And i fix a little moment, animate element once, it’s better, as for me.

  19. This doesn’t work on mobile devices. Well It works there is just no animation.

  20. Hello! I have a doubt. When i try to create new sections (like copy from to ) the cbp-so-scroller only animates the first section, the original one. How can i fix it, in order to duplicate several sections and only change the id to point to other menu item? Thank you so much, B.R. Rodrigo Maia.

    The HTML:

    Quisque gravida felis
    Praesent

    • I had the same problem .. just replace
      this.sections = Array.prototype.slice.call( this.el.querySelectorAll( '.cbp-so-section' ) );
      with
      this.sections = Array.prototype.slice.call( document.querySelectorAll( '.cbp-so-section' ) );

  21. Hello, somebody know how to use this awesome javascript code in the mobile and touch phones, i saw the article at the start, but i can’t modify the code, if somebody could help me, i will be gratefully, Thanks.

  22. I wonder if this scroll effect will work with the page split to make a very cool effect.

  23. Hello, would there be any possible way to resize images to fit screen size? I tried this with a 1920×1080 resolution and it works perfectly, but if I change my resolution to 1280×1024 , the images will be like cropped.

  24. Does this affect work with Chrome? When I click View Demo and then scroll the page, nothing happens. What am I suppose to see?

  25. Same issue… strange !!!! I confirm, it is working on IE and Edge but does not work on Chrome….