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%;
	}
}

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 );

Tagged with:

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 up to date with the latest web design and development news and relevant updates from Codrops.