/* superscrollorama - the jquery plugin for doing scroll animations by john polacek (@johnpolacek) powered by the greensock tweening platform http://www.greensock.com greensock license info at http://www.greensock.com/licensing/ dual licensed under mit and gpl. thanks to jan paepke (@janpaepke) for making many nice improvements */ (function($) { $.superscrollorama = function(options) { var superscrollorama = this; var defaults = { isvertical:true, // are we scrolling vertically or horizontally? triggeratcenter: true, // the animation triggers when the respective element's origin is in the center of the scrollarea. this can be changed here to be at the edge (-> false) playoutanimations: true, // when scrolling past the animation should they be played out (true) or just be jumped to the respective last frame (false)? does not affect animations where duration = 0 reverse: true // make reverse configurable so you don't have to pass it in for every tween to reverse globally }; superscrollorama.settings = $.extend({}, defaults, options); var $window = $(window); // private vars var animobjects = [], pinnedobjects = [], scrollcontaineroffset = {x: 0, y: 0}, doupdateonnexttick = false, targetoffset, i; // private functions function init() { // set event handlers $window.scroll(function() { doupdateonnexttick = true; }); tweenlite.ticker.addeventlistener("tick", tickhandler); } function cssnumericposition ($elem) { // return 0 when value is auto var obj = { top: parsefloat($elem.css("top")), left: parsefloat($elem.css("left")) }; if (isnan(obj.top)) { obj.top = 0; } if (isnan(obj.left)) { obj.left = 0; } return obj; } function tickhandler() { if (doupdateonnexttick) { checkscrollanim(); doupdateonnexttick = false; } } // reset a pin object function resetpinobj (pinobj) { pinobj.el.css('position', pinobj.origpositioning.pos); pinobj.el.css('top', pinobj.origpositioning.top); pinobj.el.css('left', pinobj.origpositioning.left); } // set a tween progress (use totalprogress for tweenmax and timelinemax to include repeats) function settweenprogress(tween, progress) { if (tween) { if (tween.totalprogress) { tween.totalprogress(progress).pause(); } else { tween.progress(progress).pause(); } } } function checkscrollanim() { var currscrollpoint = superscrollorama.settings.isvertical ? $window.scrolltop() + scrollcontaineroffset.y : $window.scrollleft() + scrollcontaineroffset.x; var offsetadjust = superscrollorama.settings.triggeratcenter ? (superscrollorama.settings.isvertical ? - $window.height()/2 : - $window.width()/2) : 0; var i, startpoint, endpoint; // check all animobjects var numanim = animobjects.length; for (i=0; i startpoint && currscrollpoint < endpoint) && animobj.state !== 'tweening') { // if it should be tweening and isn't.. animobj.state = 'tweening'; animobj.start = startpoint; animobj.end = endpoint; } if (currscrollpoint < startpoint && animobj.state !== 'before' && animobj.reverse) { // if it should be at the before tween state and isn't.. if (superscrollorama.settings.playoutanimations || animobj.dur === 0) { animobj.tween.reverse(); } else { settweenprogress(animobj.tween, 0); } animobj.state = 'before'; } else if (currscrollpoint > endpoint && animobj.state !== 'after') { // if it should be at the after tween state and isn't.. if (superscrollorama.settings.playoutanimations || animobj.dur === 0) { animobj.tween.play(); } else { settweenprogress(animobj.tween, 1); } animobj.state = 'after'; } else if (animobj.state === 'tweening') { // if it is tweening.. var repeatindefinitely = false; if (animobj.tween.repeat) { // does the tween have the repeat option (tweenmax / timelinemax) repeatindefinitely = (animobj.tween.repeat() === -1); } if (repeatindefinitely) { // if the animation loops indefinitely it will just play for the time of the duration var playheadposition = animobj.tween.totalprogress(); // there is no "isplaying" value so we need to save the playhead to determine whether the animation is running if (animobj.playeadlastposition === null || playheadposition === animobj.playeadlastposition) { if (playheadposition === 1) { if (animobj.tween.yoyo()) { // reverse playback with infinitely looped tweens only works with yoyo true animobj.tween.reverse(); } else { animobj.tween.totalprogress(0).play(); } } else { animobj.tween.play(); } } animobj.playeadlastposition = playheadposition; } else { settweenprogress(animobj.tween, (currscrollpoint - animobj.start)/(animobj.end - animobj.start)); } } } // check all pinned elements var numpinned = pinnedobjects.length; for (i=0; i endpoint && pinobj.state === 'before') || (currscrollpoint < startpoint && pinobj.state === 'after')); // if we jumped past a pinarea (i.e. when refreshing or using a function) we need to temporarily pin the element so it gets positioned to start or end respectively var inpinara = (currscrollpoint > startpoint && currscrollpoint < endpoint); if (inpinara || jumpedpast) { // set original position values for unpinning if (pinobj.pushfollowers && el.css('position') === "static") { // this can't be. if we want to pass following elements we need to at least allow relative positioning el.css('position', "relative"); } // save original positioning pinobj.origpositioning = { pos: el.css('position'), top: pinobj.spacer.css('top'), left: pinobj.spacer.css('left') }; // change to fixed position pinobj.fixedpositioning = { top: superscrollorama.settings.isvertical ? -pinobj.offset : pinobjspaceroffset.top, left: superscrollorama.settings.isvertical ? pinobjspaceroffset.left : -pinobj.offset }; el.css('position','fixed'); el.css('top', pinobj.fixedpositioning.top); el.css('left', pinobj.fixedpositioning.left); // save values pinobj.pinstart = startpoint; pinobj.pinend = endpoint; // if we want to push down following items we need a spacer to do it, while and after our element is fixed. if (pinobj.pushfollowers) { if (superscrollorama.settings.isvertical) { pinobj.spacer.height(pinobj.dur + el.outerheight(true)); } else { pinobj.spacer.width(pinobj.dur + el.outerwidth(true)); } } else { if (pinobj.origpositioning.pos === "absolute") { // no spacer pinobj.spacer.width(0); pinobj.spacer.height(0); } else { // spacer needs to reserve the elements space, while pinned if (superscrollorama.settings.isvertical) { pinobj.spacer.height(el.outerheight(true)); } else { pinobj.spacer.width(el.outerwidth(true)); } } } if (pinobj.state === "update") { if (pinobj.anim) { settweenprogress(pinobj.anim, 0); // reset the progress, otherwise the animation won't be updated to the new position } } else if (pinobj.onpin) { pinobj.onpin(pinobj.state === "after"); } // pin it! pinobj.state = 'pinned'; } } // if state changed to pinned (or already was) we need to position the element if (pinobj.state === 'pinned') { // check to see if object should be unpinned if (currscrollpoint < pinobj.pinstart || currscrollpoint > pinobj.pinend) { // unpin it var before = currscrollpoint < pinobj.pinstart; pinobj.state = before ? 'before' : 'after'; // set animation to end or beginning settweenprogress(pinobj.anim, before ? 0 : 1); var spacersize = before ? 0 : pinobj.dur; if (superscrollorama.settings.isvertical) { pinobj.spacer.height(pinobj.pushfollowers ? spacersize : 0); } else { pinobj.spacer.width(pinobj.pushfollowers ? spacersize : 0); } // correct values if pin object was moved (animated) during pin (pinobj.el.css values will never be auto as they are set by the class) var deltay = pinobj.fixedpositioning.top - cssnumericposition(pinobj.el).top; var deltax = pinobj.fixedpositioning.left - cssnumericposition(pinobj.el).left; // first revert to start values resetpinobj(pinobj); // position element correctly if (!pinobj.pushfollowers || pinobj.origpositioning.pos === "absolute") { var pinoffset; if (pinobj.origpositioning.pos === "relative") { // position relative and pushfollowers = false pinoffset = superscrollorama.settings.isvertical ? parsefloat(pinobj.origpositioning.top) : parsefloat(pinobj.origpositioning.left); if (isnan(pinoffset)) { // if position was "auto" parsefloat will result in nan pinoffset = 0; } } else { pinoffset = superscrollorama.settings.isvertical ? pinobj.spacer.position().top : pinobj.spacer.position().left; } var direction = superscrollorama.settings.isvertical ? "top" : "left"; pinobj.el.css(direction, pinoffset + spacersize); } // if position relative and pushfollowers is true the element remains untouched. // now correct values if they have been changed during pin if (deltay !== 0) { pinobj.el.css("top", cssnumericposition(pinobj.el).top - deltay); } if (deltax !== 0) { pinobj.el.css("left", cssnumericposition(pinobj.el).left - deltax); } if (pinobj.onunpin) { pinobj.onunpin(!before); } } else if (pinobj.anim) { // do animation settweenprogress(pinobj.anim, (currscrollpoint - pinobj.pinstart)/(pinobj.pinend - pinobj.pinstart)); } } } } // public functions superscrollorama.addtween = function(target, tween, dur, offset, reverse) { tween.pause(); animobjects.push({ target:target, tween: tween, offset: offset || 0, dur: dur || 0, reverse: (typeof reverse !== "undefined") ? reverse : superscrollorama.settings.reverse, // determine if reverse animation has been disabled state:'before' }); return superscrollorama; }; superscrollorama.pin = function(el, dur, vars) { if (typeof(el) === 'string') { el = $(el); } var defaults = { offset: 0, pushfollowers: true // if true following elements will be "pushed" down, if false the pinned element will just scroll past them }; vars = $.extend({}, defaults, vars); if (vars.anim) { vars.anim.pause(); } var spacer = $('
'); spacer.css("position", "relative"); spacer.css("top", el.css("top")); spacer.css("left", el.css("left")); el.before(spacer); pinnedobjects.push({ el:el, state:'before', dur:dur, offset: vars.offset, anim:vars.anim, pushfollowers:vars.pushfollowers, spacer:spacer, onpin:vars.onpin, onunpin:vars.onunpin }); return superscrollorama; }; superscrollorama.updatepin = function (el, dur, vars) { // update a pinned object. dur and vars are optional to only change vars and keep dur just pass null for dur if (typeof(el) === 'string') { el = $(el); } if (vars.anim) { vars.anim.pause(); } var numpinned = pinnedobjects.length; for (i=0; i