/** * gridscrollfx.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, support = { animations : modernizr.cssanimations }, animendeventnames = { 'webkitanimation' : 'webkitanimationend', 'oanimation' : 'oanimationend', 'msanimation' : 'msanimationend', 'animation' : 'animationend' }, // animation end event name animendeventname = animendeventnames[ modernizr.prefixed( 'animation' ) ]; 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 - elh * h) >= scrolled; } function extend( a, b ) { for( var key in b ) { if( b.hasownproperty( key ) ) { a[key] = b[key]; } } return a; } function griditem( el ) { this.el = el; this.anchor = el.queryselector( 'a' ) this.image = el.queryselector( 'img' ); this.desc = el.queryselector( 'h3' ); } griditem.prototype.addcurtain = function() { if( !this.image ) return; this.curtain = document.createelement( 'div' ); this.curtain.classname = 'curtain'; var rgb = new colorfinder( function favorhue(r,g,b) { // exclude white //if (r>245 && g>245 && b>245) return 0; return (math.abs(r-g)*math.abs(r-g) + math.abs(r-b)*math.abs(r-b) + math.abs(g-b)*math.abs(g-b))/65535*50+1; } ).getmostprominentcolor( this.image ); if( rgb.r && rgb.g && rgb.b ) { this.curtain.style.background = 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')'; } this.anchor.appendchild( this.curtain ); } griditem.prototype.changeanimationdelay = function( time ) { if( this.curtain ) { this.curtain.style.webkitanimationdelay = time + 'ms'; this.curtain.style.animationdelay = time + 'ms'; } if( this.image ) { this.image.style.webkitanimationdelay = time + 'ms'; this.image.style.animationdelay = time + 'ms'; } if( this.desc ) { this.desc.style.webkitanimationdelay = time + 'ms'; this.desc.style.animationdelay = time + 'ms'; } } function gridscrollfx( el, options ) { this.el = el; this.options = extend( {}, this.options ); extend( this.options, options ); this._init(); } gridscrollfx.prototype.options = { // minimum and maximum delay of the animation (random value is chosen) mindelay : 0, maxdelay : 500, // the viewportfactor defines how much of the appearing item has to be visible in order for the animation to start // 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 } gridscrollfx.prototype._init = function() { var self = this, items = []; [].slice.call( this.el.children ).foreach( function( el, i ) { var item = new griditem( el ); items.push( item ); } ); this.items = items; this.itemscount = this.items.length; this.itemsrenderedcount = 0; this.didscroll = false; imagesloaded( this.el, function() { // show grid classie.add( self.el, 'loaded' ); // initialize masonry new masonry( self.el, { itemselector : 'li', isfitwidth : true, transitionduration : 0 } ); // the items already shown... self.items.foreach( function( item ) { if( inviewport( item.el ) ) { ++self.itemsrenderedcount; classie.add( item.el, 'shown' ); } else { item.addcurtain(); // add random delay item.changeanimationdelay( math.random() * ( self.options.maxdelay - self.options.mindelay ) + self.options.mindelay ); } } ); var onscrollfn = function() { if( !self.didscroll ) { self.didscroll = true; settimeout( function() { self._scrollpage(); }, 200 ); } if( self.itemsrenderedcount === self.itemscount ) { window.removeeventlistener( 'scroll', onscrollfn, false ); } } // animate the items inside the viewport (on scroll) window.addeventlistener( 'scroll', onscrollfn, false ); // check if new items are in the viewport after a resize window.addeventlistener( 'resize', function() { self._resizehandler(); }, false ); }); } gridscrollfx.prototype._scrollpage = function() { var self = this; this.items.foreach( function( item ) { if( !classie.has( item.el, 'shown' ) && !classie.has( item.el, 'animate' ) && inviewport( item.el, self.options.viewportfactor ) ) { ++self.itemsrenderedcount; if( !item.curtain ) { classie.add( item.el, 'shown' ); return; }; classie.add( item.el, 'animate' ); // after animation ends add class shown var onendanimationfn = function( ev ) { if( support.animations ) { this.removeeventlistener( animendeventname, onendanimationfn ); } classie.remove( item.el, 'animate' ); classie.add( item.el, 'shown' ); }; if( support.animations ) { item.curtain.addeventlistener( animendeventname, onendanimationfn ); } else { onendanimationfn(); } } }); this.didscroll = false; } gridscrollfx.prototype._resizehandler = function() { var self = this; function delayed() { self._scrollpage(); self.resizetimeout = null; } if ( this.resizetimeout ) { cleartimeout( this.resizetimeout ); } this.resizetimeout = settimeout( delayed, 1000 ); } // add to global namespace window.gridscrollfx = gridscrollfx; } )( window );