/* 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 = {}; 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('will-change','top'); 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