File: /storage/v4513/ctoivenusin/public_html/wp-content/themes/ivenusp2/assets/js/sequence.js
/*
* Sequence.js
*
* The responsive CSS animation framework for creating unique sliders,
* presentations, banners, and other step-based applications.
*
* @link https://github.com/IanLunn/Sequence
* @author IanLunn
* @version 2.0.0
* @license http://sequencejs.com/licenses/
* @copyright Ian Lunn Design Limited 2015
*/
function defineSequence(imagesLoaded, Hammer) {
'use strict';
var instances = [],
instance = 0;
/**
* Sequence function
*
* @param {Object} element - the element Sequence is bound to
* @param {Object} options - this instance's options
* @returns {Object} self - Properties and methods available to this instance
*/
var Sequence = (function (element, options) {
var instanceId = element.getAttribute("data-seq-enabled");
// Prevent multiple instances on the same element. Return the object instead
if (instanceId !== null) {
return instances[instanceId];
}
// The element now has Sequence attached to it
element.setAttribute("data-seq-enabled", instance);
instance++;
/* --- PRIVATE VARIABLES/FUNCTIONS --- */
// Default Sequence settings
var defaults = {
/* --- General --- */
// The first step to show
startingStepId: 1,
// Should the starting step animate in to begin with?
startingStepAnimatesIn: false,
// When the last step is reached, should Sequence cycle back to the start?
cycle: true,
// How long to wait between the current phase animating out, and the next
// animating in.
phaseThreshold: true,
// Should animations be reversed when navigating backwards?
reverseWhenNavigatingBackwards: false,
// Should the active step be given a higher z-index?
moveActiveStepToTop: true,
/* --- Canvas Animation --- */
// Should the canvas automatically animate between steps?
animateCanvas: true,
// Time it should take to animate between steps
animateCanvasDuration: 250,
/* --- autoPlay --- */
// Cause Sequence to automatically navigate between steps
// Specify a number in milliseconds or true (for a default of 5000ms) to
// define the period Sequence should wait between each step before
// navigating to the next step
autoPlay: true,
// How long to wait between each step before navigation occurs again
autoPlayInterval: 3000,
// Amount of time to wait until autoPlay starts again after being stopped
autoPlayDelay: null,
// Direction of navigation when autoPlay is enabled
autoPlayDirection: 1,
// Use an autoPlay button? You can also specify a CSS selector to
// change what element acts as the button. If true, the element uses the
// class of "seq-autoplay"
autoPlayButton: true,
// Pause autoPlay when the Sequence element is hovered over
autoPlayPauseOnHover: true,
/* --- Navigation Skipping --- */
// Allow the user to navigate between steps even if they haven't
// finished animating
navigationSkip: true,
// How long to wait before the user is allowed to skip to another step
navigationSkipThreshold: 250,
// Fade a step when it has been skipped
fadeStepWhenSkipped: true,
// How long the fade should take
fadeStepTime: 500,
// When a step is skipped, the next step will immediately animate-in
// regardless of the phaseThreshold option
ignorePhaseThresholdWhenSkipped: false,
// Don't allow the user to go to a previous step when the current one is
// still active
preventReverseSkipping: false,
/* --- Next/Prev Button --- */
// Use next and previous buttons? You can also specify a CSS selector to
// change what element acts as the button. If true, the element uses
// classes of "seq-next" and "seq-prev"
nextButton: true,
prevButton: true,
/* --- Pagination --- */
// Use pagination? You can also specify a CSS selector to
// change what element acts as pagination. If true, the element uses the
// class of "seq-pagination"
pagination: true,
/* --- Preloader --- */
// You can also specify a CSS selector to
// change what element acts as the preloader. If true, the element uses
// the class of "seq-preloader"
preloader: false,
// Preload all images from specific steps
preloadTheseSteps: [1],
// Preload specified images
preloadTheseImages: [
/**
* Example usage
* "images/catEatingSalad.jpg",
* "images/grandmaDressedAsBatman.png"
*/
],
// Hide Sequence's steps until it has preloaded
hideStepsUntilPreloaded: false,
// (Debugging only) Prevent the preloader from hiding so you can
// test it's styles
pausePreloader: false,
/* --- Keyboard --- */
// Can the user navigate between steps by pressing keyboard buttons?
keyNavigation: false,
// When numeric keys 1 - 9 are pressed, Sequence will navigate to the
// corresponding step
numericKeysGoToSteps: false,
// Events to run when the user presses the left/right keys
keyEvents: {
left: function(sequence) {sequence.prev();},
right: function(sequence) {sequence.next();}
},
/* --- Touch Swipe --- */
// Can the user navigate between steps by swiping on a touch enabled device?
swipeNavigation: true,
// Events to run when the user swipes in a particular direction
swipeEvents: {
left: function(sequence) {sequence.next();},
right: function(sequence) {sequence.prev();},
up: undefined,
down: undefined
},
// Options to supply the third-party Hammer library See: http://hammerjs.github.io/recognizer-swipe/
swipeHammerOptions: {},
/* --- hashTags --- */
// Should the URL update to include a hashTag that relates to the current
// step being shown?
hashTags: false,
// Get the hashTag from an ID or data-seq-hashtag attribute?
hashDataAttribute: false,
// Should the hash change on the first step?
hashChangesOnFirstStep: false,
/* --- Fallback Theme --- */
// Settings to use when the browser doesn't support CSS transitions
fallback: {
// The speed to transition between steps
speed: 500
}
};
// Default value for autoPlay in milliseconds
var autoPlayDefault = 5000;
// See sequence.animation.domDelay() for an explanation of this
var domThreshold = 50;
// Throttle the window resize event
// see self.manageEvents.add.resizeThrottle()
var resizeThreshold = 100;
// Convert browser fixes to CSS strings
var prefixTranslations = {
animation: {
"WebkitAnimation": "-webkit-",
"animation": ""
}
};
/**
*
* This version of Modernizr is for use with Sequence.js and is included
* internally to prevent conflicts with other Modernizr builds.
*
* Modernizr 2.8.3 (Custom Build) | MIT & BSD
* Build: http://modernizr.com/download/#-cssanimations-csstransforms-csstransitions-svg-touch-prefixed-teststyles-testprop-testallprops-prefixes-domprefixes
*/
/* jshint ignore:start */
var Modernizr=function(a,b,c){function z(a){i.cssText=a}function A(a,b){return z(l.join(a+";")+(b||""))}function B(a,b){return typeof a===b}function C(a,b){return!!~(""+a).indexOf(b)}function D(a,b){for(var d in a){var e=a[d];if(!C(e,"-")&&i[e]!==c)return b=="pfx"?e:!0}return!1}function E(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:B(f,"function")?f.bind(d||b):f}return!1}function F(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+n.join(d+" ")+d).split(" ");return B(b,"string")||B(b,"undefined")?D(e,b):(e=(a+" "+o.join(d+" ")+d).split(" "),E(e,b,c))}var d="2.8.3",e={},f=b.documentElement,g="modernizr",h=b.createElement(g),i=h.style,j,k={}.toString,l=" -webkit- -moz- -o- -ms- ".split(" "),m="Webkit Moz O ms",n=m.split(" "),o=m.toLowerCase().split(" "),p={svg:"http://www.w3.org/2000/svg"},q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var h,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:g+(d+1),l.appendChild(j);return h=["­",'<style id="s',g,'">',a,"</style>"].join(""),l.id=g,(m?l:n).innerHTML+=h,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=f.style.overflow,f.style.overflow="hidden",f.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),f.style.overflow=k),!!i},x={}.hasOwnProperty,y;!B(x,"undefined")&&!B(x.call,"undefined")?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:w(["@media (",l.join("touch-enabled),("),g,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},q.cssanimations=function(){return F("animationName")},q.csstransforms=function(){return!!F("transform")},q.csstransitions=function(){return F("transition")},q.svg=function(){return!!b.createElementNS&&!!b.createElementNS(p.svg,"svg").createSVGRect};for(var G in q)y(q,G)&&(v=G.toLowerCase(),e[v]=q[G](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)y(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof enableClasses!="undefined"&&enableClasses&&(f.className+=" "+(b?"":"no-")+a),e[a]=b}return e},z(""),h=j=null,e._version=d,e._prefixes=l,e._domPrefixes=o,e._cssomPrefixes=n,e.testProp=function(a){return D([a])},e.testAllProps=F,e.testStyles=w,e.prefixed=function(a,b,c){return b?F(a,b,c):F(a,"pfx")},e}(window,window.document);
/* jshint ignore:end */
// Add indexOf() support to arrays for Internet Explorer 8
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement, fromIndex) {
if ( this === undefined || this === null ) {
throw new TypeError( '"this" is null or not defined' );
}
// Hack to convert object.length to a UInt32
var length = this.length >>> 0;
fromIndex = +fromIndex || 0;
if (Math.abs(fromIndex) === Infinity) {
fromIndex = 0;
}
if (fromIndex < 0) {
fromIndex += length;
if (fromIndex < 0) {
fromIndex = 0;
}
}
for (;fromIndex < length; fromIndex++) {
if (this[fromIndex] === searchElement) {
return fromIndex;
}
}
return -1;
};
}
/**
* Determine the prefix to use for the pageVisibility API
*/
var hidden,
visibilityChange;
if (typeof document.hidden !== "undefined") {
// Opera 12.10 and Firefox 18 and later support
hidden = "hidden";
visibilityChange = "visibilitychange";
} else if (typeof document.mozHidden !== "undefined") {
hidden = "mozHidden";
visibilityChange = "mozvisibilitychange";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
}
/**
* Is an object an array?
*
* @param {Object} object - The object we want to test
* @api private
*/
function isArray(object) {
if (Object.prototype.toString.call(object) === '[object Array]') {
return true;
}else {
return false;
}
}
/**
* Extend object a with the properties of object b.
* If there's a conflict, object b takes precedence.
*
* @param {Object} a - The first object to merge
* @param {Object} b - The second object to merge (takes precedence)
* @api private
*/
function extend(a, b) {
for (var i in b) {
a[i] = b[i];
}
return a;
}
/**
* Get the values of an element's CSS property
*
* @param {HTMLObject} element - The element to get the value from
* @param {String} property - The CSS property to get the value of
* @returns {String} value - The value from the element's CSS property
*/
function getStyle(element, property) {
var value;
// IE
if (element.currentStyle) {
value = element.currentStyle[property];
}
else if (document.defaultView && document.defaultView.getComputedStyle) {
value = document.defaultView.getComputedStyle(element, "")[property];
}
return value;
}
/**
* Cross Browser helper for addEventListener
*
* @param {HTMLObject} element - The element to attach the event to
* @param {String} eventName - The name of the event; "click" for example
* @param {Function} handler - The function to execute when the event occurs
* @returns {Function} handler - Returns the handler so it can be removed
*/
function addEvent(element, eventName, handler) {
if (element.addEventListener) {
element.addEventListener(eventName, handler, false);
return handler;
}
else if (element.attachEvent) {
// Allows IE to return this keyword
var handlerr = function() {
handler.call(element);
};
element.attachEvent("on" + eventName, handlerr);
return handlerr;
}
}
/**
* Cross Browser helper for removeEventListener
*
* @param {HTMLObject} element - The element to attach the event to
* @param {String} eventName - The name of the event; "click" for example
* @param {Function} handler - The function to execute when the event occurs
*/
function removeEvent(element, eventName, handler) {
if (element.addEventListener) {
element.removeEventListener(eventName, handler, false);
}
else if (element.detachEvent) {
element.detachEvent("on" + eventName, handler);
}
}
/**
* Converts a time value taken from a CSS property, such as "0.5s"
* and converts it to a number in milliseconds, such as 500
*
* @param {String} time - the time in a string
* @returns {Number} convertedTime - the time as a number
* @api private
*/
function convertTimeToMs(time) {
var convertedTime,
fraction;
// Deal with milliseconds and seconds
if (time.indexOf("ms") > -1) {
fraction = 1;
}else {
fraction = 1000;
}
if (time == "0s") {
convertedTime = 0;
}else {
convertedTime = parseFloat(time.replace("s", "")) * fraction;
}
return convertedTime;
}
/**
* Does an element have a particular class?
*
* @param {HTMLElement} element - The element to check
* @param {String} name - The name of the class to check for
* @returns {Boolean}
* @api private
*/
function hasClass(element, name) {
if (element === undefined) {
return;
}
return new RegExp('(\\s|^)' + name + '(\\s|$)').test(element.className);
}
/**
* Add a class to an element
*
* @param {Object} elements - The element(s) to add a class to
* @param {String} name - The class to add
* @api private
*/
function addClass(elements, name) {
var element,
elementsLength,
i;
// If only one element is defined, turn it into a nodelist so it'll pass
// through the for loop
if (isArray(elements) === false) {
elementsLength = 1;
elements = [elements];
}
elementsLength = elements.length;
for (i = 0; i < elementsLength; i++) {
element = elements[i];
if (hasClass(element, name) === false) {
element.className += (element.className ? ' ': '') + name;
}
}
}
/**
* Remove a class from an element
*
* @param {Object} elements - The element to remove a class from
* @param {String} name - The class to remove
* @api private
*/
function removeClass(elements, name) {
var element,
elementsLength,
i;
// If only one element is defined, turn it into a nodelist so it'll pass
// through the for loop
if (isArray(elements) === false) {
elementsLength = 1;
elements = [elements];
}
else {
elementsLength = elements.length;
}
for (i = 0; i < elementsLength; i++) {
element = elements[i];
if (hasClass(element, name) === true) {
element.className = element.className.replace(new RegExp('(\\s|^)' + name + '(\\s|$)'),' ').replace(/^\s+|\s+$/g, '');
}
}
}
/**
* Determine if the cursor is inside the boundaries of an
* element.
*
* @param {Object} element - The element to test
* @param {Object} cursor - The event holding cursor properties
*/
function insideElement(element, cursor) {
// Get the elements boundaries
var rect = element.getBoundingClientRect(),
inside = false;
// Return true if inside the boundaries of the Sequence element
if (cursor.clientX >= rect.left && cursor.clientX <= rect.right && cursor.clientY >= rect.top && cursor.clientY <= rect.bottom) {
inside = true;
}
return inside;
}
/**
* Determine if an element has a specified parent, and if so, return the
* index number for the element.
*
* The index is taken from the top level elements witint a pagination
* element. This function will iterate through each parent until it
* reaches the top level, then get all top level elements and determine
* the index of the chosen top level.
*
* @param {Object} parents - The parent element(s) that the child should be
* within
* @param {Object} target - The child element to test if it has the parent
* @param {Object} previousTarget - The element that was previously checked
* to determine if it was top level
* @api private
*/
function hasParent(parent, target, previousTarget) {
if (target.nodeName === "BODY") {
return false;
}
// We're at the pagination parent
if (parent === target) {
if (previousTarget !== undefined) {
// Get the top level element clicked and all top level elements
var topLevel = previousTarget;
var allTopLevel = parent.getElementsByTagName(topLevel.nodeName);
// Count the number of top level elements
var i = allTopLevel.length;
// Which top level element was clicked?
while (i--) {
if (topLevel === allTopLevel[i]) {
// One-base the index and return it
return i + 1;
}
}
}
}
// Not yet at the pagination parent element, iterate again
else {
previousTarget = target;
return hasParent(parent, target.parentNode, previousTarget);
}
}
/**
* Determine the Hammer direction required based on the swipe directions
* being used
* hammerjs.github.io/api/#directions
*
* @param {Object} swipeEvents - An object holding each swipe direction and
* the handler to execute
* @api private
*/
function getHammerDirection(swipeEvents) {
var swipeDirections = 0,
hammerDirection = Hammer.DIRECTION_NONE;
if (swipeEvents.left !== undefined || swipeEvents.right !== undefined) {
swipeDirections += 1;
}
if (swipeEvents.up !== undefined || swipeEvents.down !== undefined) {
swipeDirections += 2;
}
if (swipeDirections === 1) {
hammerDirection = Hammer.DIRECTION_HORIZONTAL;
} else if (swipeDirections === 2) {
hammerDirection = Hammer.DIRECTION_VERTICAL;
} else if (swipeDirections === 3) {
hammerDirection = Hammer.DIRECTION_ALL;
}
return hammerDirection;
}
/**
* Add classes to the Sequence container that allow for styling based on
* feature support.
*
* @param {HTMLObject} $el - The element to add the classes to
* @param {Object} Modernizr - Sequence's instance of Modernizr
* @api private
*/
function addFeatureSupportClasses($el, Modernizr) {
// TODO: Add support for all features used by Sequence and only manipulate
// the DOM once. Currently this function just adds a class for the touch
// feature. Probably best just to copy how Modernizr does it to implement
// full list of features
var prefix = "seq-",
support = "no-touch";
if (Modernizr.touch === true) {
support = "touch";
}
addClass($el, prefix + support);
}
/* --- PUBLIC PROPERTIES/METHODS --- */
// Expose some of Sequence's private properties
var self = {
modernizr: Modernizr
};
/**
* Manage UI elements such as nextButton, prevButton, and pagination
*/
self.ui = {
// Default UI elements
defaultElements: {
"nextButton": "seq-next",
"prevButton": "seq-prev",
"autoPlayButton": "seq-autoplay",
"pagination": "seq-pagination",
"preloader": "seq-preloader"
},
/**
* Get an UI element
*
* @param {String} type - The type of UI element (nextButton for example)
* @returns {Boolean | HTMLElement} option - True if using the default
* element, else an HTMLElement
*/
getElements: function(type, option) {
var element,
elements,
elementsLength,
relatedElements = [],
rel,
i;
// Get the element being used
if (option === true) {
// Default elements
elements = document.querySelectorAll("." + this.defaultElements[type]);
} else {
// Custom elements
elements = document.querySelectorAll(option);
}
elementsLength = elements.length;
// Does the element control this instance of Sequence? We're looking
// for either a global element or one with a rel attribute the same
// as this instances ID
for (i = 0; i < elementsLength; i++) {
element = elements[i];
rel = element.getAttribute("rel");
if (rel === null || rel === self.$container.getAttribute("id")) {
relatedElements.push(element);
}
}
return relatedElements;
},
/**
* Fade an element in using transitions if they're supported, else use JS
*
* @param {HTMLElement} element - The element to show
* @param {Number} duration - The duration to show the element over
*/
show: function(element, duration) {
if (self.propertySupport.transitions === true) {
element.style[Modernizr.prefixed("transitionDuration")] = duration + "ms";
element.style[Modernizr.prefixed("transitionProperty")] = "opacity, " + Modernizr.prefixed("transform");
element.style.opacity = 1;
}
else {
self.animationFallback.animate(element, "opacity", "", 0, 1, duration);
}
},
/**
* Fade an element out using transitions if they're supported, else use JS
*
* @param {HTMLElement} element - The element to hide
* @param {Number} duration - The duration to hide the element over
* @param {Function} callback - Function to execute when the element is
* hidden
*/
hide: function(element, duration, callback) {
if (self.propertySupport.transitions === true) {
element.style[Modernizr.prefixed("transitionDuration")] = duration + "ms";
element.style[Modernizr.prefixed("transitionProperty")] = "opacity, " + Modernizr.prefixed("transform");
element.style.opacity = 0;
}
else {
self.animationFallback.animate(element, "opacity", "", 1, 0, duration);
}
if (callback !== undefined) {
self.hideTimer = setTimeout(function() {
callback();
}, duration);
}
}
};
/**
* Methods relating to autoPlay
*/
self.autoPlay = {
/**
* Initiate autoPlay
*/
init: function() {
self.isAutoPlayPaused = false;
self.isAutoPlaying = false;
},
/**
* Determine the delay that should be applied before starting autoPlay. A
* custom delay should take precedence. If delay is true then the delay
* should use options.autoPlayDelay where specified. If not
* specified, use the same time as defined in options.autoPlayInterval
*
* @param {Boolean/Number} delay - Whether a delay should be applied before
* starting autoPlay (true = same amount as options.autoPlayInterval,
* false = no interval, number = custom interval period). Applied to
* autoPlay.start()
* @param {Number} startDelay - The delay applied via options.autoPlayDelay
* @param {Number} autoPlayInterval - The delay applied via options.autoPlayInterval
*/
getDelay: function(delay, startDelay, autoPlayInterval) {
switch (delay) {
case true:
delay = (startDelay === null) ? autoPlayInterval: startDelay;
break;
case false:
case undefined:
delay = 0;
break;
}
return delay;
},
/**
* Start autoPlay
*
* @param {Boolean/Number} delay - Whether a delay should be applied before
* starting autoPlay (true = same amount as options.autoPlayInterval,
* false = no delay, number = custom delay period)
* @param {Boolean} continuing - If autoPlay is continuing from a
* previous cycle, the started() callback won't be triggered
* @returns false - When autoPlay is already active and can't be started
* again
*/
start: function(delay, continuing) {
// Only start once
if (self.isAutoPlaying === true || self.isReady === false) {
return false;
}
var options = self.options;
// Which delay should we use?
delay = this.getDelay(delay, options.autoPlayDelay, options.autoPlayInterval);
// Callback (only to be triggered when autoPlay is continuing from a
// previous cycle)
if (continuing === undefined) {
self.started(self);
}
addClass(self.$container, "seq-autoplaying");
addClass(self.$autoPlay, "seq-autoplaying");
// autoPlay is now enabled and active
options.autoPlay = true;
self.isAutoPlaying = true;
// Only start a new autoPlay timer if Sequence isn't already animating.
// If it is, a new one will be started at the end of the animation.
if (self.isAnimating === false) {
// Choose the direction and start autoPlay
self.autoPlayTimer = setTimeout(function() {
if (options.autoPlayDirection === 1) {
self.next();
}else {
self.prev();
}
}, delay);
}
return true;
},
/**
* Stop autoPlay
*/
stop: function() {
if (self.options.autoPlay === true && self.isAutoPlaying === true) {
self.options.autoPlay = false;
self.isAutoPlaying = false;
clearTimeout(self.autoPlayTimer);
removeClass(self.$container, "seq-autoplaying");
removeClass(self.$autoPlay, "seq-autoplaying");
// Callback
self.stopped(self);
} else {
return false;
}
return true;
},
/**
* Unpause autoPlay
*
* autoPlay.pause() and autoPlay.unpause() are used internally to
* temporarily stop autoPlay when hovered over.
*/
unpause: function() {
if (self.isAutoPlayPaused === true) {
self.isAutoPlayPaused = false;
this.start(true);
} else {
return false;
}
return true;
},
/**
* Pause autoPlay
*
* autoPlay.pause() and autoPlay.unpause() are used internally to
* temporarily stop autoPlay when hovered over.
*/
pause: function() {
if (self.options.autoPlay === true) {
self.isAutoPlayPaused = true;
this.stop();
} else {
return false;
}
return true;
}
};
/**
* Controls Sequence's canvas
*/
self.canvas = {
/**
* Setup the canvas, screen, and steps ready to be animated
*/
init: function(id) {
if (self.$screen !== undefined) {
self.$screen.style.height = "100%";
self.$screen.style.width = "100%";
}
// Determine the position of each step and the transform properties
// required for the canvas so it can move to each step
self.canvas.getTransformProperties();
},
/**
* Get Sequence's steps
*
* @param {HTMLElement} canvas - The canvas element
* @returns {Array} steps - The elements that make up Sequence's steps
* @api private
*/
getSteps: function(canvas) {
var steps = [],
stepId,
step,
stepElements = canvas.children,
stepsLength = stepElements.length,
i;
// Where we'll save info about the animation
self.stepProperties = {};
// Get the steps that have a parent with a class of "seq-canvas"
for (i = 0; i < stepsLength; i++) {
step = stepElements[i];
stepId = i + 1;
steps.push(step);
// Add each step to the animation map, where we'll save its transform
// properties
self.stepProperties[stepId] = {};
self.stepProperties[stepId].isActive = false;
}
return steps;
},
/**
* Determine the position of each step and the transform properties
* required for the canvas so it can move to each step
*
*/
getTransformProperties: function() {
var i,
step,
stepId,
canvasTransform;
for (i = 0; i < self.noOfSteps; i++) {
step = self.$steps[i];
stepId = i + 1;
canvasTransform = {
"seqX": 0,
"seqY": 0,
"seqZ": 0
};
// Invert the steps offsetLeft and offsetTop to the canvas will always
// move to show the step
canvasTransform.seqX += step.offsetLeft * -1;
canvasTransform.seqY += step.offsetTop * -1;
self.stepProperties[stepId].canvasTransform = canvasTransform;
}
},
/**
* Move the canvas to show a specific step
*
* @param {Number} id - The ID of the step to move to
* @param {Boolean} animate - Should the canvas animate or snap?
*/
move: function(id, animate) {
if (self.options.animateCanvas === true) {
// Get the canvas element and step element to animate to
var duration = 0,
transforms;
// Should the canvas animate?
if (animate === true && self.firstRun === false) {
duration = self.options.animateCanvasDuration;
}
// Animate the canvas using CSS transitions
if (self.isFallbackMode === false) {
transforms = self.stepProperties[id].canvasTransform;
// Apply the transform CSS to the canvas
self.$canvas.style[Modernizr.prefixed("transitionDuration")] = duration + "ms";
self.$canvas.style[Modernizr.prefixed("transform")] = "translateX(" + transforms.seqX + "px) " + "translateY(" + transforms.seqY + "px) " + "translateZ(" + transforms.seqZ + "px) ";
}
return true;
}
return false;
},
/**
* Remove the no-JS "seq-in" class from a step
*
* @param {Object} self - Properties and methods available to this instance
* @api private
*/
removeNoJsClass: function() {
if (self.isFallbackMode === true) {
return;
}
// Look for the step with the "seq-in" class and remove the class
for (var i = 0; i < self.$steps.length; i++) {
var element = self.$steps[i];
if (hasClass(element, "seq-in") === true) {
var step = i + 1;
self.animation.resetInheritedSpeed(step);
removeClass(element, "seq-in");
}
}
}
};
/**
* Controls Sequence's step animations
*/
self.animation = {
/**
* Get the properties of a phase
*
* @param {Number} stepId - The ID of the step
* @returns {Object} ID and element of the step the phase belongs to,
* array of all child elements that belong to step, array of animated
* elements, timings object containing maxDuration, maxDelay, and
* maxLength
*/
getPhaseProperties: function(stepId) {
var stepElement = self.$steps[stepId - 1],
stepAnimatedChildren = stepElement.querySelectorAll("*[data-seq]"),
stepChildren = stepElement.querySelectorAll("*"),
stepChildrenLength = stepChildren.length,
el,
i,
watchedDurations = [],
watchedDelays = [],
watchedLengths = [],
durations = [],
delays = [],
lengths = [],
duration,
delay;
// Get the animation length of each element (duration + delay) and save
// for comparisson
for (i = 0; i < stepChildrenLength; i++) {
el = stepChildren[i];
duration = convertTimeToMs(getStyle(el, Modernizr.prefixed("transitionDuration")));
delay = convertTimeToMs(getStyle(el, Modernizr.prefixed("transitionDelay")));
// Save this elements animation length for all elements
durations.push(duration);
delays.push(delay);
lengths.push(duration + delay);
// Also save animation lengths but only for watched elements
if (el.getAttribute("data-seq") !== null) {
watchedDurations.push(duration);
watchedDelays.push(delay);
watchedLengths.push(duration + delay);
}
}
// Which were the longest durations and delays?
var maxDuration = Math.max.apply(Math, durations),
maxDelay = Math.max.apply(Math, delays),
maxTotal = Math.max.apply(Math, lengths);
// Which were the longest watched durations and delays?
var watchedMaxDuration = Math.max.apply(Math, watchedDurations),
watchedMaxDelay = Math.max.apply(Math, watchedDelays),
watchedMaxTotal = Math.max.apply(Math, watchedLengths);
return {
stepId: stepId,
stepElement: stepElement,
children: stepChildren,
animatedChildren: stepAnimatedChildren,
watchedTimings: {
maxDuration: watchedMaxDuration,
maxDelay: watchedMaxDelay,
maxTotal: watchedMaxTotal
},
timings: {
maxDuration: maxDuration,
maxDelay: maxDelay,
maxTotal: maxTotal
}
};
},
/**
* How long before the next phase should start?
* Ignore the phaseThreshold (on first run for example)
*/
getPhaseThreshold: function(ignorePhaseThreshold, phaseThresholdOption, isAnimating, currentPhaseDuration) {
var phaseThresholdTime = 0;
// Ignore the phaseThreshold if the developer wishes for this to happen
// if Sequence.js is animating
if (isAnimating === true && self.options.ignorePhaseThresholdWhenSkipped === true) {
ignorePhaseThreshold = true;
}
if (ignorePhaseThreshold === undefined) {
if (phaseThresholdOption === true) {
// The phaseThreshold should be the length of the current phase
// so the next starts immediately after
phaseThresholdTime = currentPhaseDuration;
} else if (phaseThresholdOption !== false) {
// Use the developer defined phaseThreshold
phaseThresholdTime = phaseThresholdOption;
}
}
return phaseThresholdTime;
},
/**
* Do we need to add a delay to account for one phase finishing
* before another?
*
* @param {Number} currentPhaseTotal - Amount of time in milliseconds the
* current phase will animate for
* @param {Number} nextPhaseTotal - Amount of time in milliseconds the
* next phase will animate for
* @param {Boolean} ignorePhaseThresholdWhenSkippedOption - if true,
* don't use a reversePhaseDelay
* @param {Boolean} isAnimating - Whether Sequence is animating
* @returns {Object} - Contains times that should delay the next or
* current phase accordingly
*/
getReversePhaseDelay: function(currentPhaseTotal, nextPhaseTotal, phaseThresholdOption, ignorePhaseThresholdWhenSkippedOption, isAnimating) {
var phaseDifference = 0,
current = 0,
next = 0;
// Only use a reversePhaseDelay if the phaseThreshold option is true or
// a custom time, and Sequence is not animating with the
// ignorePhaseThreshold option on
if (phaseThresholdOption !== true && (ignorePhaseThresholdWhenSkippedOption === false || isAnimating === false)) {
phaseDifference = currentPhaseTotal - nextPhaseTotal;
if (phaseDifference > 0) {
next = phaseDifference;
} else if (phaseDifference < 0) {
current = Math.abs(phaseDifference);
}
}
return {
next: next,
current: current
};
},
/**
* If the moveActiveStepToTop option is being used, move the next step
* to the top and the current step to the bottom via z-index
*
* @param {HTMLElement} currentElement - The current step to be moved off
* the top
* @param {HTMLElement} nextElement - The next step to be moved to the top
*/
moveActiveStepToTop: function(currentElement, nextElement) {
if (self.options.moveActiveStepToTop === true) {
var prevStepElement = self.$steps[self.prevStepId - 1],
lastStepId = self.noOfSteps - 1;
prevStepElement.style.zIndex = 1;
currentElement.style.zIndex = lastStepId;
nextElement.style.zIndex = self.noOfSteps;
}
return null;
},
/**
* If the navigationSkipThreshold option is being used, prevent the use
* of goTo() during the threshold period
*
* @param {Number} id - The ID of the step Sequence is trying to go to
* @param {HTMLObject} nextStepElement - The element for the next step
*/
manageNavigationSkip: function(id, nextStepElement) {
if (self.isFallbackMode === true) {
return;
}
var i,
stepProperties,
stepElement,
stepId,
phaseSkipped;
// Show the next step again
self.ui.show(nextStepElement, 0);
if (self.options.navigationSkip === true) {
// Start the navigation skip threshold
self.navigationSkipThresholdActive = true;
// Are there steps currently animating that need to be faded out?
if (self.phasesAnimating !== 0) {
// If a step is waiting to animate in based on the phaseThreshold,
// cancel it
clearTimeout(self.phaseThresholdTimer);
clearTimeout(self.nextPhaseStartedTimer);
// Fade a step if the user navigates to another prior to its
// animation finishing
if (self.options.fadeStepWhenSkipped === true) {
// Fade all elements that are animating
// (not including the current one)
for (i = 1; i <= self.noOfSteps; i++) {
stepProperties = self.stepProperties[i];
// Deal with the steps that were skipped whilst still animating
if (stepProperties.isActive === true && i !== id) {
stepElement = self.$steps[i - 1];
stepId = i;
phaseSkipped = {};
phaseSkipped.id = stepId;
phaseSkipped.element = stepElement;
// Save the ID of the skipped step so we can deal with it when it has faded out
self.phasesSkipped.push(phaseSkipped);
// Deal with the skipped step
self.animation.stepSkipped(stepElement);
}
}
}
}
// Start the navigationSkipThreshold timer to prevent being able to
// navigate too quickly
self.navigationSkipThresholdTimer = setTimeout(function() {
self.navigationSkipThresholdActive = false;
}, self.options.navigationSkipThreshold);
}
},
/**
* Deal with a step when it has been skipped
*
* @param {HTMLElement} stepElement - The step element that was skipped
*/
stepSkipped: function(stepElement) {
// TODO: Add resetWhenStepSkipped option -
// https://github.com/IanLunn/Sequence/issues/257
// Reset the skipped steps current and next phase ended timers and trigger
// them as soon as the steps have faded out
// Fade the step out
self.ui.hide(stepElement, self.options.fadeStepTime, function() {
});
},
/**
* Change a step's class. Example: go from step1 to step2
*
* @param {Number} id - The ID of the step to change
*/
changeStep: function(id) {
// Get the step to add
var stepToAdd = "seq-step" + id;
// Add the new step and remove the previous
if (self.currentStepId !== undefined) {
var stepToRemove = "seq-step" + self.currentStepId;
addClass(self.$container, stepToAdd);
removeClass(self.$container, stepToRemove);
}else {
addClass(self.$container, stepToAdd);
}
},
/**
* Apply the reversed properties to all animatable elements within a phase
*
* @param {Object} phaseProperties - Properties relating to the active phases
* (seq-in and seq-out)
* @param {Number} phaseDelay - A delay that is added when one phase
* animates longer than the other
* @param {Number} phaseThresholdTime - The amount of time in milliseconds
* before the next step should start animating in
* @param {Boolean} ignorePhaseThreshold - if true, ignore the threshold
* between phases (breaks reversal of animations but may be used when
* skipping navigation etc)
* @returns {Number} maxWatchedTotal - The new total length
* (duration + delay) for watched elements when reversed
*/
reverseProperties: function(phaseProperties, phaseDelay, phaseThresholdTime, ignorePhaseThreshold) {
var animation = this,
phaseElements = phaseProperties.children,
noOfPhaseElements = phaseElements.length,
stepDurations = phaseProperties.timings,
el,
i,
timingFunction,
timingFunctionReversed,
duration,
delay,
total,
maxTotal,
maxWatchedTotal,
totals = [],
watchedTotals = [];
for (i = 0; i < noOfPhaseElements; i++) {
el = phaseElements[i];
// Get each element's duration and delay
duration = convertTimeToMs(getStyle(el, Modernizr.prefixed("transitionDuration")));
delay = convertTimeToMs(getStyle(el, Modernizr.prefixed("transitionDelay")));
// Save the total
total = duration + delay;
// Delay elements in relation to the length of other elements in the
// phase eg. If one element A transitions for 1s and element B 2s
// element A should be delayed by 1s (element B length - element A length).
delay = stepDurations.maxTotal - total;
// Delay this phase's elements so they animate in relation to the
// other phase
if (ignorePhaseThreshold !== true) {
delay += phaseDelay;
}
// Update the total with the new delay
total = duration + delay;
// Save the total of the reversed animation
totals.push(total);
// Save the total of the reversed animation for watched elements only
if (el.getAttribute("data-seq") !== null) {
watchedTotals.push(total);
}
// Get the timing-function and reverse it
timingFunction = getStyle(el, Modernizr.prefixed("transitionTimingFunction"));
timingFunctionReversed = animation.reverseTimingFunction(timingFunction);
// Apply the reversed transition properties to each element
el.style[Modernizr.prefixed("transition")] = duration + "ms " + delay + "ms " + timingFunctionReversed;
}
// Get the longest total and delay of the reversed animations
maxTotal = Math.max.apply(Math, totals);
// Get the longest total and delay of the reversed animations (for
// watched elements only)
maxWatchedTotal = Math.max.apply(Math, watchedTotals);
// Remove the reversed transition properties from each element once it
// has finished animating; allowing for the inherited styles to take
// effect again.
setTimeout(function() {
animation.domDelay(function() {
for (i = 0; i < noOfPhaseElements; i++) {
el = phaseElements[i];
el.style[Modernizr.prefixed("transition")] = "";
}
});
}, maxTotal + phaseThresholdTime);
return maxWatchedTotal;
},
/**
* Go forward to the next step
*
* @param {Number} id - The ID of the next step
* @param {HTMLObject} currentStepElement - The element for the current step
* @param {HTMLObject} nextStepElement - The element for the next step
* @param {Boolean} ignorePhaseThreshold - if true, ignore the
* transitionThreshold setting and immediately go to the specified step
* @param {Boolean} hashTagNav - If navigation is triggered by the hashTag
*/
forward: function(id, currentStepElement, nextStepElement, ignorePhaseThreshold, hashTagNav) {
var animation = this,
currentPhaseProperties,
currentPhaseTotal,
phaseThresholdTime;
if (self.firstRun === false) {
// Callback
animation.currentPhaseStarted(self.currentStepId);
}
// Snap the step to the "animate-start" phase
removeClass(nextStepElement, "seq-out");
animation.domDelay(function() {
// Make the current step transition to "seq-out"
currentPhaseProperties = animation.startAnimateOut(self.currentStepId, currentStepElement, 1);
// Total duration of the current phase (duration + delay)
currentPhaseTotal = currentPhaseProperties.watchedTimings.maxTotal;
// How long before the next phase should start?
phaseThresholdTime = animation.getPhaseThreshold(ignorePhaseThreshold, self.options.phaseThreshold, self.isAnimating, currentPhaseTotal);
// Sequence is now animating
self.isAnimating = true;
// Make the next step transition to "seq-in"
animation.startAnimateIn(id, currentPhaseTotal, nextStepElement, phaseThresholdTime, hashTagNav);
});
},
/**
* Navigate in reverse to the next step
*
* @param {Number} id - The ID of the next step
* @param {HTMLObject} currentStepElement - The element for the current step
* @param {HTMLObject} nextStepElement - The element for the next step
* @param {Boolean} ignorePhaseThreshold - if true, ignore the
* transitionThreshold setting and immediately go to the specified step
* @param {Boolean} hashTagNav - If navigation is triggered by the hashTag
*/
reverse: function(id, currentStepElement, nextStepElement, ignorePhaseThreshold, hashTagNav) {
var animation = this,
currentId,
reversePhaseDelay,
phaseThresholdTime = 0,
currentPhaseProperties,
nextPhaseProperties,
currentPhaseTotal,
nextPhaseTotal;
// Snap the next phase to "animate-out"
addClass(nextStepElement, "seq-out");
animation.domDelay(function() {
// Get the step number, element, its animated elements (child nodes),
// and max timings
currentPhaseProperties = animation.getPhaseProperties(self.currentStepId, "current");
nextPhaseProperties = animation.getPhaseProperties(id, "next");
// Determine the gap between phases based on the phaseThreshold option
phaseThresholdTime = animation.getPhaseThreshold(ignorePhaseThreshold, self.options.phaseThreshold, self.isAnimating, currentPhaseProperties.timings.maxTotal);
// Determine if there should be a delay to account for one phase
// finishing before the other.
// Example: Navigating forward, the next element transitions in over
// 1s (A) and the current element over 2s (B). When reversed, the next
// element (previouly the current element) should wait 1s (B - A)
// before animating back in
reversePhaseDelay = animation.getReversePhaseDelay(currentPhaseProperties.timings.maxTotal, nextPhaseProperties.timings.maxTotal, self.options.phaseThreshold, self.options.ignorePhaseThresholdWhenSkipped, self.isAnimating);
// Reverse properties for all elements in the current and next step
// and add the reversePhaseDelay as a transition-delay where necessary
currentPhaseTotal = animation.reverseProperties(currentPhaseProperties, reversePhaseDelay.current, 0, ignorePhaseThreshold);
nextPhaseTotal = animation.reverseProperties(nextPhaseProperties, reversePhaseDelay.next, phaseThresholdTime, ignorePhaseThreshold);
// Make the current step transition to "animate-start"
animation.startAnimateOut(self.currentStepId, currentStepElement, -1, currentPhaseTotal);
// Sequence is now animating
self.isAnimating = true;
if (self.firstRun === false) {
// Callbacks
animation.currentPhaseStarted(self.currentStepId);
}
// Make the next step transition to "seq-in"
animation.startAnimateIn(id, currentPhaseTotal, nextStepElement, phaseThresholdTime, hashTagNav, nextPhaseTotal);
});
},
/**
* Start the next step's "seq-in" phase
*
* @param {Number} id - The ID of the next step
* @param {Number} currentPhaseTotal - Amount of time in milliseconds the
* current phase will animate for
* @param {HTMLObject} nextStepElement - The element for the next step
* @param {Number} phaseThresholdTime - The amount of time in milliseconds
* before the next step should start animating in
* @param {Boolean} hashTagNav - If navigation is triggered by the hashTag
* @param {Number} nextPhaseTotal - Amount of time in milliseconds the
* next phase will animate for
*/
startAnimateIn: function(id, currentPhaseTotal, nextStepElement, phaseThresholdTime, hashTagNav, nextPhaseTotal) {
var animation = this,
nextPhaseProperties,
stepDuration;
// The next ID is now the current ID
self.prevStepId = self.currentStepId;
self.currentStepId = id;
if (self.firstRun === true) {
// Update pagination
self.pagination.update();
}
// When should the "seq-in" phase start and how long until the step
// completely finishes animating?
if (self.firstRun === false || self.options.startingStepAnimatesIn === true) {
self.stepProperties[id].isActive = true;
// Callback
self.nextPhaseStartedTimer = setTimeout(function() {
animation.nextPhaseStarted(id, hashTagNav);
}, phaseThresholdTime);
// Start the "seq-in" phase
self.phaseThresholdTimer = setTimeout(function() {
// TODO: Change multiple classes via one function
addClass(nextStepElement, "seq-in");
removeClass(nextStepElement, "seq-out");
// When animate-in is reversed, this will have already been passed
// in as an argument so it's only needed for forward animation
if (nextPhaseTotal === undefined) {
nextPhaseProperties = self.animation.getPhaseProperties(id, "next");
nextPhaseTotal = nextPhaseProperties.watchedTimings.maxTotal;
}
// Wait for the next phase to end
animation.phaseEnded(id, "next", nextPhaseTotal, animation.nextPhaseEnded);
// How long will it take for the step to end?
stepDuration = animation.getStepDuration(currentPhaseTotal, nextPhaseTotal, self.options.phaseThreshold);
self.stepEndedTimer = setTimeout(function() {
self.animation.stepEnded(id);
}, stepDuration);
}, phaseThresholdTime);
}
// This is the first run. Snap the first step into place without animation
else {
// Set the first step's speed to 0 to have it immediately snap into place
animation.resetInheritedSpeed(id);
self.phasesAnimating = 0;
self.isAnimating = false;
if (self.options.autoPlay === true) {
self.autoPlay.start(true);
}
addClass(nextStepElement, "seq-in");
removeClass(nextStepElement, "seq-out");
}
self.firstRun = false;
},
/**
* Start the next step's "seq-out" phase
*
* @param {Number} id - The ID of the next step
* @param {HTMLObject} currentStepElement - The element for the current step
* @param {Number} direction - The direction of navigation
* @param {Number} currentPhaseTotal - Amount of time in milliseconds the
* current phase will animate for
*/
startAnimateOut: function(id, currentStepElement, direction, currentPhaseTotal) {
var animation = this,
currentPhaseProperties;
if (direction === 1) {
// TODO: Change multiple classes via one function
// Make the current step transition to "animate-out"
removeClass(currentStepElement, "seq-in");
addClass(currentStepElement, "seq-out");
// Get the step number, element, its animated elements (child nodes), and
// max timings
currentPhaseProperties = animation.getPhaseProperties(id, "current");
currentPhaseTotal = currentPhaseProperties.watchedTimings.maxTotal;
} else {
// Make the current step transition to "animate-start"
removeClass(currentStepElement, "seq-in");
}
if (self.firstRun === false) {
// Add the steps to the list of active steps
self.stepProperties[id].isActive = true;
// Wait for the current phase to end
animation.phaseEnded(id, "current", currentPhaseTotal, animation.currentPhaseEnded);
}
return currentPhaseProperties;
},
/**
* Determine how long a step will animate for
*
* @param {Number} currentPhaseTotal - Amount of time in milliseconds the
* current phase will animate for
* @param {Number} nextPhaseTotal - Amount of time in milliseconds the
* next phase will animate for
* @param {Number|Boolean} phaseThresholdOption - Amount of time between
* phases as defined via the phaseThreshold option
* @returns {Number} stepDuration - Which ever is longest, current or next
* phase duration
*/
getStepDuration: function(currentPhaseTotal, nextPhaseTotal, phaseThresholdOption) {
var stepDuration;
switch (phaseThresholdOption) {
case true:
stepDuration = nextPhaseTotal;
break;
case false:
// The stepDuration is whichever phase is longest
stepDuration = (currentPhaseTotal >= nextPhaseTotal) ? currentPhaseTotal : nextPhaseTotal;
break;
default:
stepDuration = (currentPhaseTotal >= nextPhaseTotal + phaseThresholdOption) ? (currentPhaseTotal) - phaseThresholdOption : nextPhaseTotal;
}
return stepDuration;
},
/**
* When the current phase starts animating
*/
currentPhaseStarted: function(id) {
self.phasesAnimating++;
// Callback
self.currentPhaseStarted(id, self);
},
/**
* When the current phase finishes animating
*/
currentPhaseEnded: function(id) {
// Callback
self.currentPhaseEnded(id, self);
},
/**
* When the next phase starts animating
*
* @param {Boolean} hashTagNav - If navigation is triggered by the hashTag
*/
nextPhaseStarted: function(id, hashTagNav) {
self.phasesAnimating++;
// Update the hashTag if being used
if (hashTagNav === undefined) {
self.hashTags.update();
}
// Update pagination
self.pagination.update();
// Callback
self.nextPhaseStarted(id, self);
},
/**
* When the next phase finishes animating
*/
nextPhaseEnded: function(id) {
// Callback
self.nextPhaseEnded(id, self);
},
/**
* Wait for a phases (seq-in or seq-out) animations to finish
*
* @param {Number} id - The id of the step the phase belongs to
* @param {Number} phaseDuration - The amount of time before the phase
* ends (in milliseconds)
* @param {Function} callback - A function to execute when the phase ends
*/
phaseEnded: function(id, phase, phaseDuration, callback) {
var animation = this,
phaseEnded;
phaseEnded = function(id) {
self.stepProperties[id].isActive = false;
self.phasesAnimating--;
// Phase callback
callback(id);
};
if (phase === "current") {
self.currentPhaseEndedTimer = setTimeout(function() {
phaseEnded(id);
}, phaseDuration);
} else {
self.nextPhaseEndedTimer = setTimeout(function() {
phaseEnded(id);
}, phaseDuration);
}
},
/**
* When a step's animations have completely finished
*
* @param {Number} id - The ID of the step that ended
* @param {Number} stepDuration - The amount of time before the step
* finishes animating
*/
stepEnded: function(id) {
self.isAnimating = false;
self.isAutoPlaying = false;
// Continue with autoPlay if enabled
if (self.options.autoPlay === true) {
self.autoPlay.start(true, true);
}
// Callback
self.animationEnded(id, self);
},
/**
* Change "seq-out" to "seq-in" and vice-versa.
*
* @param {String} phase - The phase to reverse
* @returns {String} - The reversed phase
*/
reversePhase: function(phase) {
var reversePhase = {
"seq-out": "seq-in",
"seq-in": "seq-out"
};
return reversePhase[phase];
},
/**
* Apply a short delay to a function that manipulates the DOM. Allows for
* sequential DOM manipulations.
*
* Why is this needed?
*
* When sequentially manipulating a DOM element (ie, removing a class then
* immediately applying another on the same element), the first manipulation
* appears not to apply. This function puts a small gap between sequential
* manipulations to give the browser a chance visually apply each manipulation.
*
* Some browsers can apply a succession of classes quicker than
* this but 50ms is enough to capture even the slowest of browsers.
*
* @param {Function} callback - a function to run after the delay
*/
domDelay: function(callback) {
setTimeout(function() {
callback();
}, domThreshold);
},
/**
* Reverse a CSS timing function
*
* @param {String} timingFunction - The timing function to reverse
* @returns {String} timingFunction - The reverse timing function
*/
reverseTimingFunction: function(timingFunction) {
if (timingFunction === '' || timingFunction === undefined) {
return timingFunction;
}
// Convert timingFunction keywords to a cubic-bezier function
// This is needed because some browsers return a keyword, others a function
var timingFunctionToCubicBezier = {
"linear" : "cubic-bezier(0.0,0.0,1.0,1.0)",
"ease" : "cubic-bezier(0.25, 0.1, 0.25, 1.0)",
"ease-in" : "cubic-bezier(0.42, 0.0, 1.0, 1.0)",
"ease-in-out": "cubic-bezier(0.42, 0.0, 0.58, 1.0)",
"ease-out" : "cubic-bezier(0.0, 0.0, 0.58, 1.0)"
};
var cubicBezier,
cubicBezierLength,
reversedCubicBezier,
i;
// Convert the timing function to a cubic-bezier if it is a keyword
if (timingFunction.indexOf("cubic-bezier") < 0) {
// TODO: Support multiple timing-functions
// If the timing-function is made up of multiple functions, reduce it
// to one only
timingFunction = timingFunction.split(",")[0];
// Convert
timingFunction = timingFunctionToCubicBezier[timingFunction];
}
// Remove the CSS function and just get the array of points
cubicBezier = timingFunction.replace("cubic-bezier(", "").replace(")", "").split(",");
cubicBezierLength = cubicBezier.length;
// Convert each point into a number (rather than a string)
for (i = 0; i < cubicBezierLength; i++) {
cubicBezier[i] = parseFloat(cubicBezier[i]);
}
// Reverse the cubic bezier
reversedCubicBezier = [
1 - cubicBezier[2],
1 - cubicBezier[3],
1 - cubicBezier[0],
1 - cubicBezier[1]
];
// Add the reversed cubic bezier back into a CSS function
timingFunction = "cubic-bezier(" + reversedCubicBezier + ")";
return timingFunction;
},
/**
* Apply a transition-duration and transition-delay to each element
* then remove these temporary values once the phase is reset.
*
* Can be used to apply 0 to both duration and delay so animates reset
* back into their original places for example.
*
* @param {String} step - The step that the elements we'll reset belong to
*/
resetInheritedSpeed: function(step) {
if (self.isFallbackMode === true) {
return;
}
var animation = this,
el,
i;
// Get the step's elements and count them
var stepElements = self.$steps[step - 1].querySelectorAll("*"),
numberOfStepElements = stepElements.length;
// Temporarily apply a transition-duration and transition-delay to each
// element we need to track.
for (i = 0; i < numberOfStepElements; i++) {
el = stepElements[i];
el.style[Modernizr.prefixed("transition")] = "0ms 0ms";
}
/**
* Note: Synchronously, an element's phase class is added/removed here.
* To save the need for a callback though (and extra code), we instead rely
* on the necessity for the .domDelay() function which doesn't remove the
* inheritedStyles until after a brief delay. What would be the callback
* is instead just placed after the call to .resetInheritedSpeed() and
* from a synchronous point of view, occurs at this point, before the
* following .domDelay();
*/
// Remove the temporary transition-duration and transition-delay from each
// element now it has been manipulated; allowing for the inherited styles
// to take effect again.
self.animation.domDelay(function() {
for (i = 0; i < numberOfStepElements; i++) {
el = stepElements[i];
el.style[Modernizr.prefixed("transition")] = "";
}
});
},
/**
* Get the direction to navigate in based on whether the .goTo() function
* has a defined direction, and if not, what options are being used.
*
* @param {Number} id - The id of the step to go to
* @param {Number} definedDirection - The defined direction 1 or -1
* @returns {Number} direction - The direction 1 or -1
*/
getDirection: function(id, definedDirection, currentStepId, noOfSteps, isFallbackMode, reverseWhenNavigatingBackwardsOption, cycleOption) {
var animation = this,
direction = 1;
if (definedDirection !== undefined) {
direction = definedDirection;
} else if (reverseWhenNavigatingBackwardsOption === true || isFallbackMode === true) {
if (cycleOption === true && (id === 1 && currentStepId === noOfSteps)) {
// If the cycle option is enabled, and the user is navigating from
// the last step to the first, navigate forwards
direction = 1;
} else {
direction = (id < currentStepId) ? -1: 1;
}
} else {
direction = 1;
}
return direction;
},
/**
* Does Sequence need to go into fallback mode because the browser doesn't
* support transitions?
*
* @param {Object} propertySupport - List of properties and whether the
* browser supports them
*/
requiresFallbackMode: function(propertySupport) {
var transitions = propertySupport.transitions,
isFallbackMode = false;
if (transitions === false) {
isFallbackMode = true;
}
return isFallbackMode;
},
/**
* Determine what properties the browser supports. Currently tests:
*
* - transitions
* - animations
*
* @param {Object} properties - The properties to be used (on the canvas)
* @returns {Object} The list of properties we've tested and their support
*/
getPropertySupport: function(properties) {
var transitions = false,
animations = false;
// Are transitions supported?
if (Modernizr.csstransitions === true) {
transitions = true;
}
// Are animations supported?
if (Modernizr.cssanimations === true) {
animations = true;
}
return {
transitions: transitions,
animations: animations
};
}
};
/**
* Controls Sequence's animations when in a browser that doesn't support
* CSS transitions
*/
self.animationFallback = {
/**
* Animate an element using JavaScript
*
* @param {HTMLElement} element - The element to animate
* @param {String} style - The style to be animated
* @param {String} unit - The value unit such as "px", "%" etc
* @param {Number} from - The start value of the animation
* @param {Number} to - The end value of the animation
* @param {Number} time - how long to animate for
* @param {Function} callback - A function to execute once the animation
* has finished
*/
animate: function(element, style, unit, from, to, time, callback) {
if (element === false) {
return;
}
var start = new Date().getTime();
var timer = setInterval(function() {
var step = Math.min(1, (new Date().getTime()-start) / time);
element.style[style] = (from + step * (to - from)) + unit;
if (step === 1) {
if (callback !== undefined) {
callback();
}
clearInterval(timer);
}
}, 25);
element.style[style] = from + unit;
},
/**
* Setup the canvas ready for the fallback animation
*
* @param {Number} id - The first Id that Sequence will go to
*/
setupCanvas: function(id) {
var i,
step,
stepId;
if (self.isFallbackMode === true) {
// Add the "seq-fallback" class to the Sequence element
addClass(self.$container, "seq-fallback");
if (self.$screen !== undefined) {
// Prevent steps from appearing outside of the Sequence screen
self.$screen.style.overflow = "hidden";
self.$screen.style.width = "100%";
self.$screen.style.height = "100%";
}
// Make the canvas and screen 100% width/height
self.$canvas.style.width = "100%";
self.$canvas.style.height = "100%";
// Get the width of the canvas
this.canvasWidth = self.$canvas.offsetWidth;
// Make each step 100% width/height
for (i = 0; i < self.noOfSteps; i++) {
// Get the step and its ID (one-based)
step = self.$steps[i];
stepId = i + 1;
/**
* Move each step to its "seq-in" position
*
* Note: in fallback mode, steps will always remain in their
* "seq-in" position
*/
addClass(step, "seq-in");
// Make the step 100% width/height
step.style.width = "100%";
step.style.height = "100%";
step.style.position = "absolute";
step.style.whiteSpace = "normal";
// Move all steps to "seq-out"
step.style.left = "100%";
}
}
},
/**
* Move the canvas using basic animation
*
* @param {HTMLElement} nextStepElement - The element that is the next step
* @param {HTMLElement} currentStepElement - The element that is the current step
* @param {Number} direction - The direction to animate in
* @param {Boolean} animate - Show the canvas animate or snap?
*/
moveCanvas: function(nextStepElement, currentStepElement, direction, animate) {
// Animate steps
if (animate === true) {
var currentFrom = 0,
currentTo = -100,
nextFrom = 100,
nextTo = 0;
if (direction === -1) {
currentTo = 100;
nextFrom = -100;
}
this.animate(currentStepElement, "left", "%", currentFrom, currentTo, self.options.fallback.speed);
this.animate(nextStepElement, "left", "%", nextFrom, nextTo, self.options.fallback.speed);
}
// Snap steps into place
else {
currentStepElement.style.left = "-100%";
nextStepElement.style.left = "0";
}
},
/**
* Go to a step using basic animation
*
* @param {Number} id - The ID of the step to go to
* @param {String} currentStep - The name of the current step
* @param {HTMLElement} currentStepElement - The element that is the current step
* @param {String} nextStep - The name of the next step
* @param {HTMLElement} nextStepElement - The element that is the next step
* @param {Number} direction - The direction of navigation
* @param {Boolean} hashTagNav - If navigation is triggered by the hashTag
*/
goTo: function(id, currentStep, currentStepElement, nextStep, nextStepElement, direction, hashTagNav) {
var from;
// The next ID is now the current ID
self.prevStepId = self.currentStepId;
self.currentStepId = id;
// Update the hashTag if being used
if (hashTagNav === undefined) {
self.hashTags.update();
}
self.pagination.update();
// When should the "seq-in" phase start and how long until the step
// completely finishes animating?
if (self.firstRun === false) {
this.moveCanvas(nextStepElement, currentStepElement, direction, true);
// Sequence is now animating
self.isAnimating = true;
// Callback
self.animationStarted(self.currentStepId, self);
// Wait for the step (both phases) to finish animating
self.stepEndedTimer = setTimeout(function() {
self.animation.stepEnded(self.currentStepId);
}, self.options.fallback.speed);
}
// This is the first step we're going to
else {
this.moveCanvas(nextStepElement, currentStepElement, direction, false);
self.firstRun = false;
if (self.options.autoPlay === true) {
self.autoPlay.start(true);
}
}
}
};
/**
* Manage pagination
*/
self.pagination = {
/**
* Get the links from each pagination element (any top level elements)
*
* @param {HTMLElement} element - The pagination element
* @param {String} rel - Which Sequence element the pagination relates
* to (if any)
* @param {Number} i - The number of the pagination element
* @returns {Array} paginationLinks - Each link in an array
*/
getLinks: function(element, rel) {
var childElement,
childElements = element.childNodes,
childElementsLength = childElements.length,
paginationLinks = [],
i;
// Get each top level pagination link and add it to the array
for (i = 0; i < childElementsLength; i++) {
childElement = childElements[i];
if (childElement.nodeType === 1) {
paginationLinks.push(childElement);
}
}
return paginationLinks;
},
/**
* Update the pagination to activate the relevant link
*
* @returns {Array} - the pagination element(s) that are currently active
*/
update: function() {
if(self.$pagination.elements !== undefined) {
var i,
j,
id = self.currentStepId - 1,
currentPaginationLink,
currentPaginationLinksLength,
paginationLength = self.$pagination.elements.length;
// Remove the "seq-current" class from a previous pagination link
// if there is one
if (self.$pagination.currentLinks !== undefined) {
currentPaginationLinksLength = self.$pagination.currentLinks.length;
for (i = 0; i < currentPaginationLinksLength; i++) {
currentPaginationLink = self.$pagination.currentLinks[i];
removeClass(currentPaginationLink, "seq-current");
}
}
// Where we'll save the current pagination links
self.$pagination.currentLinks = [];
// Get the current pagination link from each pagination element,
// add the "seq-current" class to them, then save them for later
// for when they need to have the "seq-current" class removed
for (j = 0; j < paginationLength; j++) {
currentPaginationLink = self.$pagination.links[j][id];
self.$pagination.currentLinks.push(currentPaginationLink);
addClass(currentPaginationLink, "seq-current");
}
}
return self.$pagination.currentLinks;
}
};
/**
* Manage Sequence hashTag support
*/
self.hashTags = {
/**
* Set up hashTags
*
* @param {Number} id - The id of the first step
* @returns {Number} id - The id of the first step (hashTags.init() will
* override this if an entering URL contains a hashTag that corresponds
* to a step)
*/
init: function(id) {
if (self.options.hashTags === true) {
var correspondingStepId,
newHashTag;
// Does the browser support pushstate?
self.hasPushstate = !!(window.history && history.pushState);
// Get the current hashTag
newHashTag = location.hash.replace("#!", "");
// Get each step's hashTag
self.stepHashTags = this.getStepHashTags();
// If there is a hashTag but no value, don't go any further
if (newHashTag !== "") {
// Get the current hashTag's step ID's
self.currentHashTag = newHashTag;
correspondingStepId = this.hasCorrespondingStep(self.currentHashTag);
// If the entering URL contains a hashTag, and the hashTag relates to
// a corresponding step, the step's ID will override the startStepId
// defined in options
if (correspondingStepId > -1) {
id = correspondingStepId + 1;
}
}
}
// Return either the startingStepId as defined in settings or if the
// entering URL contained a hashTag that corresponds to a step, return
// its ID instead
return id;
},
/**
* Does a hashTag have a corresponding step?
*
* @returns {Number} correspondingStep - The step ID relating to the hashTag or -1 meaning there is no correspondingStep
*/
hasCorrespondingStep: function(hashTag) {
var correspondingStep = -1,
correspondingStepId = self.stepHashTags.indexOf(hashTag);
if (correspondingStepId > -1) {
correspondingStep = correspondingStepId;
}
return correspondingStep;
},
/**
* Get each steps hashTag to return an array of hashTags
*
* @returns {Array} stepHashTags - An array of hashTags
*/
getStepHashTags: function() {
var i,
elementHashTag,
stepHashTags = [];
// Get each steps hashtag
for (i = 0; i < self.noOfSteps; i++) {
elementHashTag = (self.options.hashDataAttribute === false) ? self.$steps[i].id: self.$steps[i].getAttribute("data-seq-hashtag");
// Add the hashtag to an array
stepHashTags.push(elementHashTag);
}
return stepHashTags;
},
/**
* Update the hashTag if:
*
* - hashTags are being used and this isn't the first run
* - hashTags are being used, this is the first run, and the first hash
* change is allowed in the options
* - the current step has a hashTag
*/
update: function() {
if (self.options.hashTags === true && self.firstRun === false || (self.options.hashTags === true && self.firstRun === true && self.options.hashChangesOnFirstStep === true)) {
// Zero-base the currentStepId
var hashTagId = self.currentStepId - 1;
// Get the current hashTag
self.currentHashTag = self.stepHashTags[hashTagId];
if (self.currentHashtag !== "") {
// Add the hashTag to the URL
if (self.hasPushstate === true) {
history.pushState(null, null, "#!" + self.currentHashTag);
}
else {
location.hash = "#!" + self.currentHashTag;
}
}
}
},
/**
* Cross Browser helper for an hashchange event
* Source: http://stackoverflow.com/questions/9339865/get-the-hashchange-event-to-work-in-all-browsers-including-ie7/
*/
setupEvent: function() {
if ('onhashchange' in window) {
if (window.addEventListener) {
window.addHashChange = function(func, before) {
window.addEventListener('hashchange', func, before);
};
window.removeHashChange = function(func) {
window.removeEventListener('hashchange', func);
};
return;
} else if (window.attachEvent) {
window.addHashChange = function(func) {
window.attachEvent('onhashchange', func);
};
window.removeHashChange = function(func) {
window.detachEvent('onhashchange', func);
};
return;
}
}
var hashChangeFuncs = [];
var oldHref = location.href;
window.addHashChange = function(func, before) {
if (typeof func === 'function') {
hashChangeFuncs[before?'unshift':'push'](func);
}
};
window.removeHashChange = function(func) {
for (var i=hashChangeFuncs.length-1; i>=0; i--) {
if (hashChangeFuncs[i] === func) {
hashChangeFuncs.splice(i, 1);
}
}
};
setInterval(function() {
var newHref = location.href;
if (oldHref !== newHref) {
var _oldHref = oldHref;
oldHref = newHref;
for (var i=0; i<hashChangeFuncs.length; i++) {
hashChangeFuncs[i].call(window, {
'type': 'hashchange',
'newURL': newHref,
'oldURL': _oldHref
});
}
}
}, 100);
}
};
/**
* Manage Sequence preloading
*/
self.preload = {
// Sequence's default HTML for the preloader
defaultHtml: '<svg width="39" height="16" viewBox="0 0 39 16" xmlns="http://www.w3.org/2000/svg" class="seq-preload-indicator"><title>Sequence.js Preloading Indicator</title><desc>Three orange dots increasing in size from left to right</desc><g fill="#F96D38"><path class="seq-preload-circle seq-preload-circle-1" d="M3.999 12.012c2.209 0 3.999-1.791 3.999-3.999s-1.79-3.999-3.999-3.999-3.999 1.791-3.999 3.999 1.79 3.999 3.999 3.999z"/><path class="seq-preload-circle seq-preload-circle-2" d="M15.996 13.468c3.018 0 5.465-2.447 5.465-5.466 0-3.018-2.447-5.465-5.465-5.465-3.019 0-5.466 2.447-5.466 5.465 0 3.019 2.447 5.466 5.466 5.466z"/><path class="seq-preload-circle seq-preload-circle-3" d="M31.322 15.334c4.049 0 7.332-3.282 7.332-7.332 0-4.049-3.282-7.332-7.332-7.332s-7.332 3.283-7.332 7.332c0 4.05 3.283 7.332 7.332 7.332z"/></g></svg>',
fallbackHtml: '<div class="seq-preload-indicator seq-preload-indicator-fallback"><div class="seq-preload-circle seq-preload-circle-1"></div><div class="seq-preload-circle seq-preload-circle-2"></div><div class="seq-preload-circle seq-preload-circle-3"></div></div>',
// Sequence's default preloader styles and animation for the preloader
defaultStyles: '@'+prefixTranslations.animation[Modernizr.prefixed("animation")]+'keyframes seq-preloader {50% {opacity: 1;}100% {opacity: 0;}}.seq-preloader {background: white;visibility: visible;opacity: 1;position: absolute;z-index: 9999;height: 100%;width: 100%;top: 0;left: 0;right: 0;bottom: 0;} .seq-preloader.seq-preloaded {opacity: 0;visibility: hidden;'+Modernizr.prefixed("transition")+': visibility 0s .5s, opacity .5s;}.seq-preload-indicator {overflow: visible;position: relative;top: 50%;left: 50%;-webkit-transform: translate(-50%, -50%);transform: translate(-50%, -50%);}.seq-preload-circle {display: inline-block;height: 12px;width: 12px;fill: #F96D38;opacity: 0;'+prefixTranslations.animation[Modernizr.prefixed("animation")]+'animation: seq-preloader 1.25s infinite;}.seq-preload-circle-2 {'+prefixTranslations.animation[Modernizr.prefixed("animation")]+'animation-delay: .15s;}.seq-preload-circle-3 {'+prefixTranslations.animation[Modernizr.prefixed("animation")]+'animation-delay: .3s;}.seq-preload-indicator-fallback{width: 42px;margin-left: -21px;overflow: visible;}.seq-preload-indicator-fallback .seq-preload-circle {width: 8px; height:8px;background-color: #F96D38;border-radius: 100%; opacity: 1;display: inline-block; vertical-align: middle;}.seq-preload-indicator-fallback .seq-preload-circle-2{margin-left: 3px; margin-right: 3px; width: 12px; height: 12px;}.seq-preload-indicator-fallback .seq-preload-circle-3{width: 16px; height: 16px;}',
/**
* Setup Sequence preloading
*
* @param {Function} callback - Function to execute when preloading has finished
* @returns {Boolean} Whether preload was initiated
*/
init: function(callback) {
if (self.options.preloader !== false) {
var preload = this,
stepImagesToPreload,
individualImagesToPreload,
imagesToPreload,
imgLoad,
progress,
result;
// Add a class of "seq-preloading" to the Sequence element
addClass(self.$container, "seq-preloading");
// Get the preloader
self.$preloader = self.ui.getElements("preloader", self.options.preloader);
// Add the preloader element if necessary
preload.append();
// Add the preloader's default styles
preload.addStyles();
// Hide steps if necessary
preload.toggleStepsVisibility("hide");
// Get images from particular Sequence steps to be preloaded
// Get images with specific source values to be preloaded
stepImagesToPreload = preload.getImagesToPreload(self.options.preloadTheseSteps);
individualImagesToPreload = preload.getImagesToPreload(self.options.preloadTheseImages, true);
// Combine step images and individual images
imagesToPreload = stepImagesToPreload.concat(individualImagesToPreload);
// Initiate the imagesLoaded plugin
imgLoad = imagesLoaded(imagesToPreload);
// When imagesLoaded() has finished (regardless of whether images
// completed or failed to load)
imgLoad.on("always", function(instance) {
preload.complete(callback);
});
// Track the number of images that have loaded so far
progress = 1;
imgLoad.on("progress", function(instance, image) {
// Has the image loaded or is it broken?
result = image.isLoaded ? 'loaded': 'broken';
// Callback
self.preloadProgress(result, image.img.src, progress++, imagesToPreload.length, self);
});
return true;
}
return false;
},
/**
* When preloading has finished, show the steps again and hide the preloader
*
* @param {Function} callback - Function to execute when preloading has finished
*/
complete: function(callback) {
// Callback
self.preloaded(self);
// (Debugging only) prevent the preloader from hiding so it's styles
// can be tested
if (self.options.pausePreloader === true) {
return;
}
// Show steps if necessary
this.toggleStepsVisibility("show");
// Remove the "preloading" class and add the "preloaded" class
removeClass(self.$container, "seq-preloading");
addClass(self.$container, "seq-preloaded");
addClass(self.$preloader[0], "seq-preloaded");
if (this.preloadIndicatorTimer !== undefined) {
clearInterval(this.preloadIndicatorTimer);
}
// Remove the fallback preloader
if (Modernizr.prefixed("animation") === false || Modernizr.svg === false) {
self.$preloader[0].style.display = "none";
}
if (callback !== undefined) {
callback();
}
},
/**
* Add the preloader's styles to the <head></head>
*/
addStyles: function() {
if (self.options.preloader === true) {
// Get the <head> and create the <style> element
var head = document.head || document.getElementsByTagName('head')[0];
this.styleElement = document.createElement('style');
// Add the default styles to the <style> element
this.styleElement.type = 'text/css';
if (this.styleElement.styleSheet) {
this.styleElement.styleSheet.cssText = this.defaultStyles;
} else {
this.styleElement.appendChild(document.createTextNode(this.defaultStyles));
}
// Add the <style> element to the <head>
head.appendChild(this.styleElement);
// Animate the preloader using JavaScript if the browser doesn't support SVG
if (Modernizr.prefixed("animation") === false || Modernizr.svg === false) {
// Get the preload indicator
var preloadIndicator = self.$preloader[0].firstChild;
var indicatorFlash = function() {
preloadIndicator.style.visibility = "hidden";
preloadFlashTime = 1000;
setTimeout(function() {
preloadIndicator.style.visibility = "visible";
}, 500);
};
indicatorFlash();
// Make the preload indicator flash
this.preloadIndicatorTimer = setInterval(function() {
indicatorFlash();
}, 1000);
}
return true;
}
return false;
},
/**
* Remove the preloader's styles from the <head></head>
*/
removeStyles: function() {
this.styleElement.parentNode.removeChild(this.styleElement);
},
/**
* Get <img> elements and return them to be preloaded. Elements can be got
* either via the <img> element itself or a src attribute.
*
* @param {Array} elements - An integer array of steps to get images from or a string array of image sources
* @param {Boolean} srcOnly - Is the element to be retrieved via the src?
* @returns {Array} imagesToPreload - The images to preload in an array
*/
getImagesToPreload: function(elements, srcOnly) {
// Where we'll save the images
var imagesToPreload = [];
if (isArray(elements) === true) {
var i,
j,
elementLength = elements.length,
step,
imagesInStep,
imagesInStepLength,
image,
img,
src;
// Get each step's <img> elements and add them to imagesToPreload
if (srcOnly !== true) {
// Get each step
for (i = 0; i < elementLength; i++) {
// Get the step and any images belonging to it
step = self.$steps[i];
imagesInStep = step.getElementsByTagName("img");
imagesInStepLength = imagesInStep.length;
// Get each image within the step
for (j = 0; j < imagesInStepLength; j++) {
image = imagesInStep[j];
imagesToPreload.push(image);
}
}
}
// Get each step's <img> elements via the src and add them to imagesToPreload
else {
img = [];
for (i = 0; i < elementLength; i++) {
src = elements[i];
img[i] = new Image();
img[i].src = src;
imagesToPreload.push(img[i]);
}
}
}
return imagesToPreload;
},
/**
* Append the default preloader
*
* @returns {Boolean} whether the default preloader was appended or not
*/
append: function() {
if (self.options.preloader === true) {
// Set up the preloader container
self.$preloader = document.createElement("div");
self.$preloader.className = "seq-preloader";
// Convert the preloader to an array
self.$preloader = [self.$preloader];
// Use the fallback preloader if CSS keyframes or SVG aren't supported
if (Modernizr.prefixed("animation") !== false && Modernizr.svg === true) {
self.$preloader[0].innerHTML = self.preload.defaultHtml;
} else {
self.$preloader[0].innerHTML = self.preload.fallbackHtml;
}
// Add the preloader
self.$container.insertBefore(self.$preloader[0], null);
return true;
}
return false;
},
/**
* If enabled, hide/show Sequence steps until preloading has finished
*
* @param {String} type - "show" or "hide"
*/
toggleStepsVisibility: function(type) {
if (self.options.hideStepsUntilPreloaded === true) {
var i,
step;
// Hide or show each step
for (i = 0; i < self.noOfSteps; i++) {
step = self.$steps[i];
if (type === "hide") {
self.ui.hide(step, 0);
}else {
self.ui.show(step, 0);
}
}
return true;
}
return false;
}
};
/**
* Add and remove Sequence events
*/
self.manageEvents = {
// Keep track of the added events here
list: {
"load": [],
"click": [],
"touchstart": [],
"mousemove": [],
"mouseleave": [],
"hammer": [],
"keyup": [],
"hashchange": [],
"resize": []
},
/**
* Set up events on init
*/
init: function() {
// Add visibilityChange to the list of events
self.manageEvents.list[visibilityChange] = [];
if (self.options.hashTags === true) {
this.add.hashChange();
}
if (self.options.swipeNavigation === true) {
this.add.swipeNavigation();
} else {
self.hammerTime = false;
}
if (self.options.keyNavigation === true) {
this.add.keyNavigation();
}
// Throttles the resize event to once every 100ms
this.add.resizeThrottle();
// Used to start/stop Sequence when page visibility changes
this.add.pageVisibility();
// If being used, get the next button(s) and set up the events
if (self.options.nextButton !== false) {
self.$next = self.ui.getElements("nextButton", self.options.nextButton);
this.add.button(self.$next, "nav", self.next);
}
// If being used, get the next button(s) and set up the events
if (self.options.prevButton !== false) {
self.$prev = self.ui.getElements("prevButton", self.options.prevButton);
this.add.button(self.$prev, "nav", self.prev);
}
// If being used, get the autoPlay button(s) and set up the events
if (self.options.autoPlayButton !== false) {
self.$autoPlay = self.ui.getElements("autoPlayButton", self.options.autoPlayButton);
this.add.button(self.$autoPlay, "nav", self.toggleAutoPlay);
}
// If being used, set up the stopOnHover event
this.add.stopOnHover();
// If being used, get the pagination element(s) and set up the events
if (self.options.pagination !== false) {
self.$pagination = {};
// The Sequence element and the links that will control it
self.$pagination.relatedElementId = instance;
self.$pagination.links = [];
self.$pagination.elements = self.ui.getElements("pagination", self.options.pagination);
this.add.button(self.$pagination.elements, "pagination");
}
return null;
},
/**
* Remove all events at once
*/
removeAll: function(eventList) {
var eventType,
theEvents;
// Remove each event
for (eventType in eventList) {
if (eventList.hasOwnProperty(eventType) === true) {
theEvents = eventList[eventType];
this.remove(eventType);
}
}
return null;
},
/**
* Remove an event from all of the elements it is attached to
*
* @param {String} type - The type of event to remove eg. "click"
*/
remove: function(type) {
// Get the elements using the event and count them
var eventElements = self.manageEvents.list[type];
var eventElementsLength = eventElements.length;
switch(type) {
case "hashchange":
if (self.options.hashTags === true) {
removeHashChange(eventElements[0].handler);
}
break;
case "hammer":
if (self.manageEvents.list.hammer.length > 0 && document.querySelectorAll !== undefined) {
var handler = self.manageEvents.list.hammer[0].handler;
self.hammerTime.off("swipe", [handler]);
}
break;
default:
// Remove the event from each element
for (var i = 0; i < eventElementsLength; i++) {
var eventProperties = eventElements[i];
removeEvent(eventProperties.element, type, eventProperties.handler);
}
}
},
add: {
/**
* Add the hashchange event
*
* @returns {Array} Containing an object with the element and handler
*/
hashChange: function() {
// Setup the cross-browser hashchange event
self.hashTags.setupEvent();
var handler = function(e) {
var newHashTag,
id;
// Get the hashTag from the URL
newHashTag = e.newURL || location.href;
newHashTag = newHashTag.split("#!")[1];
// Go to the new step if we're not already on it
if (self.currentHashTag !== newHashTag) {
// Get the ID of the new hash tag and one-base it
id = self.stepHashTags.indexOf(newHashTag) + 1;
self.currentHashTag = newHashTag;
/**
* Go to the new step
*
* Note: When the user is navigating through history via their
* browser's back/forward buttons for example, we can't prevent
* going to a step to meet the navigationSkipThreshold option. To
* prevent the hashTag and the current step from becoming
* unsynchronized we must ignore the navigationSkipThreshold
* setting.
*/
self.goTo(id, undefined, undefined, true);
}
};
addHashChange(handler);
self.manageEvents.list.hashchange.push({"element": window, "handler": handler});
return self.manageEvents.list.hashchange;
},
/**
* Add buttons
*
* @param {Array} elements - The element or elements acting as the next button
* @param {String} type - The type of button being added - "nav" or "pagination"
* @param {Function} callback - Function to execute when the button is clicked
* @returns {Array} Containing an object with the element and handler
*/
button: function(elements, type, callback) {
// Count the number of elements being added
var elementsLength = elements.length,
handler,
element,
targetElement,
buttonEvent,
parent,
rel,
id,
i;
switch (type) {
// Set up a click event for navigation elements
case "nav":
buttonEvent = function(element) {
handler = addEvent(element, "click", function(event) {
if (!event) {
event = window.event;
}
// Prevent the default action - this is so <a href="#step1">
// can act as a navigation element but the anchor link will
// only activate when JS is disabled
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
callback();
});
};
break;
// Set up a click event for pagination
case "pagination":
buttonEvent = function(element, rel) {
handler = addEvent(element, "click", function(event, element) {
if (!event) {
event = window.event;
}
// Prevent the default action - this is so <a href="#step1">
// can act as a pagination element but the anchor link will
// only activate when JS is disabled
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
var targetElement = event.target || event.srcElement;
parent = this;
// Get the ID of the clicked pagination link
id = hasParent(parent, targetElement);
// Go to the clicked pagination ID
self.goTo(id);
});
// Save the pagination links
self.$pagination.links.push(self.pagination.getLinks(element, rel));
};
break;
}
// Add a click event for each element
for (i = 0; i < elementsLength; i++) {
element = elements[i];
// Does the button control a specific Sequence instance?
rel = element.getAttribute("rel");
// The button controls one Sequence instance
// (defined via the rel attribute)
if (rel === self.$container.id && element.getAttribute("data-seq-button") === null) {
element.setAttribute("data-seq-button", true);
buttonEvent(element, rel);
}
// The button controls all Sequence instances
else if (rel === null && element.getAttribute("data-seq-button") === null) {
buttonEvent(element, rel);
}
// Save the element and its handler for later, should it need to
// be removed
self.manageEvents.list.click.push({"element": element, "handler": handler});
}
return self.manageEvents.list.click;
},
/**
* Stop and start autoPlay when the user's cursor enters and leaves
* the Sequence element accordingly.
*
* Note: autoPlay will be stopped only when the cursor is inside the
* boundaries of the Sequence element, either on the element itself or
* its children. Child elements overflowing the Sequence element will
* not cause Sequence to be stopped.
*/
stopOnHover: function() {
var previouslyInside = false,
touchHandler,
handler;
self.isMouseOver = false;
/**
* Determine when the user touches the container. This is so we can
* disable the use of stopOnHover for touches, but not for mousemove
*/
touchHandler = addEvent(self.$container, "touchstart", function(e) {
self.isTouched = true;
});
// Save the event
self.manageEvents.list.touchstart.push({"element": self.$container, "handler": touchHandler});
/**
* Stop autoPlay only when the cursor is inside the boundaries of the
* Sequence element
*/
handler = addEvent(self.$container, "mousemove", function(e) {
e = e || window.event;
// If the user touched the container, don't stop - stopOnHover
// should only occur when a mouse cursor is used
if (self.isTouched === true) {
self.isTouched = false;
return;
}
// Is the cursor inside the Sequence element?
if (insideElement(self.$container, e) === true) {
// Pause if the cursor was previously outside the Sequence element
if (self.options.autoPlayPauseOnHover === true && self.isMouseOver === false) {
self.autoPlay.pause();
}
// We're now inside the Sequence element
self.isMouseOver = true;
}
// The cursor is outside of the main container, but over child
// elements belonging to the container
else {
// Unpause if the cursor was previously inside the Sequence element
if (self.options.autoPlayPauseOnHover === true && self.isMouseOver === true) {
self.autoPlay.unpause();
}
// We're now outside the Sequence element
self.isMouseOver = false;
}
});
// Save the event
self.manageEvents.list.mousemove.push({"element": self.$container, "handler": handler});
/**
* Start autoPlay when the cursor leaves the Sequence element
*/
handler = addEvent(self.$container, "mouseleave", function(e) {
if (self.options.autoPlayPauseOnHover === true) {
self.autoPlay.unpause();
}
// We're now outside the Sequence element
self.isMouseOver = false;
});
// Save the event
self.manageEvents.list.mouseleave.push({"element": self.$container, "handler": handler});
return null;
},
/**
* Navigate to a step when Sequence is swiped
*/
swipeNavigation: function() {
// Don't use swipe navigation if the browser doesn't support
// addEventListener (Hammer.js needs it)
if (window.addEventListener === undefined) {
self.hammerTime = false;
return;
}
var hammerDirection,
handler;
if (typeof Hammer === "function") {
handler = function(e) {
switch(e.direction) {
// Left
case 2:
self.options.swipeEvents.left(self);
break;
// Right
case 4:
self.options.swipeEvents.right(self);
break;
// Up
case 8:
self.options.swipeEvents.up(self);
break;
// Down
case 16:
self.options.swipeEvents.down(self);
break;
}
};
// Set up the swipe event
self.hammerTime = new Hammer(self.$container).on("swipe", handler);
// Set Hammer's Swipe options
self.hammerTime.get("swipe").set(self.options.swipeHammerOptions);
// determine the Hammer direction that needs to be set based on the
// swipe directions being used (hammerjs.github.io/api/#directions)
hammerDirection = getHammerDirection(self.options.swipeEvents);
self.hammerTime.get("swipe").set({direction: hammerDirection});
self.manageEvents.list.hammer.push({"element": self.$container, "handler": handler});
}
},
/**
* Navigate to a step when corresponding keys are pressed
*/
keyNavigation: function() {
var handler = addEvent(document, "keyup", function(event) {
if (!event) {
event = window.event;
}
// Get the key pressed
var keyCodeChar = parseInt(String.fromCharCode(event.keyCode));
// Go to the numeric key pressed
if ((keyCodeChar > 0 && keyCodeChar <= self.noOfSteps) && (self.options.numericKeysGoToSteps)) {
self.goTo(keyCodeChar);
}
// When left/right arrow keys are pressed, go to prev/next steps
switch(event.keyCode) {
case 37:
self.options.keyEvents.left(self);
break;
case 39:
self.options.keyEvents.right(self);
break;
}
});
self.manageEvents.list.keyup.push({"element": document, "handler": handler});
},
/**
* Throttle the window resize event so it only occurs every x amount of
* milliseconds, as defined by the resizeThreshold global variable.
*/
resizeThrottle: function() {
// Events to be executed when the throttled window resize occurs
function throttledEvents() {
// Use a delay to give the browser a chance to repaint before
// getting transform properties
setTimeout(function() {
self.canvas.getTransformProperties();
/**
* Snap to the currently active step
*
* Assume the canvas is laid out in a 2 x 2 grid, the Sequence
* element has a height of 100%, and the user is viewing the second
* row of steps -- when the user resizes the window, the second row
* of steps will no longer be positioned perfectly in the window.
* This event will immediately snap the canvas back into place.
*/
if (self.propertySupport.transitions === true) {
self.canvas.move(self.currentStepId, false);
}
}, 500);
// Callback
self.throttledResize(self);
}
/**
* Throttle the resize event to only execute throttledEvents() every
* 100ms. This is so not too many events occur during a resize. The
* threshold can be changed using the resizeThreshold global variable.
*/
var throttleTimer,
handler;
handler = addEvent(window, "resize", function(e) {
clearTimeout(throttleTimer);
throttleTimer = setTimeout(throttledEvents, resizeThreshold);
});
self.manageEvents.list.resize.push({"element": window, "handler": handler});
},
/**
* When the page loses visibility, pause autoPlay, then unpause when
* the page gains visibility again
*/
pageVisibility: function() {
var handler = addEvent(document, visibilityChange, function() {
if (document[hidden]) {
self.autoPlay.pause();
} else {
self.autoPlay.unpause();
}
}, false);
self.manageEvents.list[visibilityChange].push({"element": document, "handler": handler});
}
}
};
/**
* Set up an instance of Sequence
*
* @param {Object} element - The element Sequence is attached to
*/
self.init = function(element) {
var id,
prevStep,
prevStepId,
transformProperties,
goToFirstStep;
// Merge developer options with defaults
self.options = extend(defaults, options);
self.isAnimating = false;
self.isReady = false;
// Get the element Sequence is attached to, the screen,
// the canvas and it's steps
self.$container = element;
self.$screen = self.$container.querySelectorAll(".seq-screen")[0];
self.$canvas = self.$container.querySelectorAll(".seq-canvas")[0];
self.$steps = self.canvas.getSteps(self.$canvas);
// Count number of steps
self.noOfSteps = self.$steps.length;
self.phasesAnimating = 0;
self.phasesSkipped = [];
// Add classes to the Sequence container that represent features supported
addFeatureSupportClasses(self.$container, Modernizr);
// Get the first step's ID
id = self.options.startingStepId;
addClass(self.$container, "seq-active");
// Find out what properties the browser supports
// and whether Sequence should go into fallback mode
self.propertySupport = self.animation.getPropertySupport();
self.isFallbackMode = self.animation.requiresFallbackMode(self.propertySupport);
// Set up the canvas and screen with the necessary CSS properties
self.canvas.init(id);
// Remove the no-JS "seq-in" class from a step
self.canvas.removeNoJsClass(self);
// Set up events
self.manageEvents.init();
// Set up autoPlay
self.autoPlay.init();
// On the first run, we need to treat the animation a little differently
self.firstRun = true;
// Set up hashTag support if being used and override the first ID if there
// is a hashTag in the entering URL that has a corresponding step
id = self.hashTags.init(id);
// Get the previous step ID
if (self.options.autoPlayDirection === 1) {
prevStepId = id - 1;
self.prevStepId = (prevStepId < 1) ? self.noOfSteps: prevStepId;
} else {
prevStepId = id + 1;
self.prevStepId = (prevStepId > self.noOfSteps) ? 1: prevStepId;
}
// Get the previous step and next step
self.currentStepId = self.prevStepId;
prevStep = self.prevStepId;
// If the browser doesn't support CSS transitions, setup the fallback
self.animationFallback.setupCanvas(id);
goToFirstStep = function() {
self.animation.domDelay(function() {
// Snap the previous step into position
self.animation.domDelay(function() {
self.animation.resetInheritedSpeed(prevStep);
});
self.isReady = true;
// Callback
self.ready(self);
// Go to the first step
self.goTo(id, self.options.autoPlayDirection, true);
});
};
// Set up preloading if required, then go to the first step
if (self.options.preloader !== false && document.querySelectorAll !== undefined && typeof imagesLoaded === "function") {
self.preload.init(function() {
goToFirstStep();
});
} else {
goToFirstStep();
}
};
/**
* Destroy an instance of Sequence
*
* @returns {Boolean}
*/
self.destroy = function() {
var i,
step,
lastStep;
// Stop autoPlay
self.autoPlay.stop();
// Stop timers
clearTimeout(self.autoPlayTimer);
clearTimeout(self.phaseThresholdTimer);
clearTimeout(self.stepEndedTimer);
clearTimeout(self.currentPhaseEndedTimer);
clearTimeout(self.nextPhaseStartedTimer);
clearTimeout(self.nextPhaseEndedTimer);
clearTimeout(self.fadeStepTimer);
clearTimeout(self.hideTimer);
clearTimeout(self.navigationSkipThresholdTimer);
// Remove all events
self.manageEvents.removeAll(self.manageEvents.list);
// Remove classes
removeClass(self.$pagination.currentLinks, "seq-current");
removeClass(self.$container, "seq-step" + self.currentStepId);
removeClass(self.$container, "seq-active");
// Remove styles
self.$container.removeAttribute("style");
if (self.$screen !== undefined) {
self.$screen.removeAttribute("style");
}
self.$canvas.removeAttribute("style");
// Remove styles from steps and snap them to their "seq-out" position
for (i = 0; i < self.noOfSteps; i++) {
step = self.$steps[i];
step.removeAttribute("style");
self.animation.resetInheritedSpeed(i + 1);
removeClass(step, "seq-in");
removeClass(step, "seq-out");
}
// Snap the starting step back into its "seq-in" position
lastStep = self.$steps[self.options.startingStepId - 1];
self.animation.resetInheritedSpeed(self.options.startingStepId);
addClass(lastStep, "seq-in");
// Allow the same element to have Sequence initated on it in the future
element.removeAttribute("data-seq-enabled");
// Callback
self.destroyed(self);
// Finally, clear the instance's properties and methods
self = null;
return self;
};
/**
* Go to the next step
*
* @returns {Number | false} The ID of the next step Sequence will
* navigate to or false if next() is prevented because the current step is
* the last and the cycle option is disabled
*/
self.next = function() {
var nextStepId = self.currentStepId + 1;
// Cycle to the first step if on the current step and cycle is enabled
if (nextStepId > self.noOfSteps && self.options.cycle === false) {
return false;
} else if (nextStepId > self.noOfSteps) {
nextStepId = 1;
}
self.goTo(nextStepId);
return nextStepId;
};
/**
* Go to the previous step
*
* @returns {Number | false} The ID of the previous step Sequence will
* navigate to or false if prev() is prevented because the current step is
* the first and the cycle option is disabled
*/
self.prev = function() {
var prevStepId = self.currentStepId - 1,
direction;
if (prevStepId < 1 && self.options.cycle === false) {
return false;
} else if (prevStepId < 1) {
prevStepId = self.noOfSteps;
}
if (self.options.reverseWhenNavigatingBackwards === true) {
direction = -1;
}
self.goTo(prevStepId, direction);
return prevStepId;
};
/**
* Stop and start Sequence's autoPlay feature
*
* @param {Boolean/Number} delay - Whether a delay should be applied before
* starting autoPlay (true = same amount as options.autoPlayInterval,
* false = no delay, number = custom delay period). Applied to
* autoPlay.start()
* @returns {Boolean} isAutoPlaying - true if autoPlay was started, false if stopped
*/
self.toggleAutoPlay = function(delay) {
if (self.isAutoPlaying === false) {
self.start(delay);
} else {
self.stop();
}
return self.isAutoPlaying;
};
self.stop = function() {
self.autoPlay.stop();
};
self.start = function(delay) {
self.autoPlay.start(delay);
};
/**
* Go to a specific step
*
* @param {Number} id - The ID of the step to go to
* @param {Number} direction - Direction to get to the step
* (1 = forward, -1 = reverse)
* @param {Boolean} ignorePhaseThreshold - if true, ignore the
* transitionThreshold setting and immediately go to the specified step
* @param {Boolean} hashTagNav - If navigation is triggered by the hashTag
* @returns {Boolean} false when goTo was disallowed true when allowed
*/
self.goTo = function(id, direction, ignorePhaseThreshold, hashTagNav) {
// Get the direction to navigate if one wasn't specified
direction = self.animation.getDirection(id, direction, self.currentStepId, self.noOfSteps, self.isFallbackMode, self.options.reverseWhenNavigatingBackwards, self.options.cycle);
/**
* Don't go to a step if:
*
* - ID isn't defined
* - It doesn't exist
* - It is already animating
* - navigationSkip isn't allowed and an animation is active
* - navigationSkip is allowed but the threshold is yet to expire (unless
* navigating via forward/back button with hashTags enabled - see
* manageEvents.add.hashChange() for an explanation of this)
* - transitions aren't supported and Sequence is active (navigation
* skipping isn't allowed in fallback mode, unless navigating via
* forward/back buttons)
* - preventReverseSkipping is enabled and the user is trying to navigate
* in a different direction to the one already active
*/
if (id === undefined || id < 1 || id > self.noOfSteps || id === self.currentStepId || (self.options.navigationSkip === false && self.isAnimating === true) || (self.options.navigationSkip === true && self.navigationSkipThresholdActive === true && hashTagNav === undefined) || (self.isFallbackMode === true && self.isAnimating === true && hashTagNav === undefined) || (self.options.preventReverseSkipping === true && self.direction !== direction && self.isAnimating === true)) {
return false;
}
var currentStepElement,
nextStepElement;
// Clear the previous autoPlayTimer
clearTimeout(self.autoPlayTimer);
// Save the latest direction
self.direction = direction;
// Add the "seq-reversed" class if navigating in reverse
if (direction === 1) {
// Add a "seq-reversed" class
removeClass(self.$container, "seq-reversed");
} else {
// Add a "seq-reversed" class
addClass(self.$container, "seq-reversed");
}
// Get the current and next step elements
currentStepElement = self.$steps[self.currentStepId - 1];
nextStepElement = self.$steps[id - 1];
// Move the active step to the top (via a higher z-index)
self.animation.moveActiveStepToTop(currentStepElement, nextStepElement);
// Change the step number on the Sequence element
self.animation.changeStep(id);
if (self.isFallbackMode === false) {
// Reset the next step's elements durations to 0ms so it can be snapped
// into place
self.animation.resetInheritedSpeed(id);
if (self.firstRun === false || (self.firstRun === true && self.options.startingStepAnimatesIn === true)) {
// Callback
self.animationStarted(id, self);
}
// Animate the canvas
self.canvas.move(id, true);
// Determine how often goTo() can be used based on
// navigationSkipThreshold and manage step fading accordingly
self.animation.manageNavigationSkip(id, nextStepElement);
// Are we moving the active phases forward or in reverse?
if (direction === 1) {
self.animation.forward(id, currentStepElement, nextStepElement, ignorePhaseThreshold, hashTagNav);
} else {
self.animation.reverse(id, currentStepElement, nextStepElement, ignorePhaseThreshold, hashTagNav);
}
}
// Use fallback animation
else {
self.animationFallback.goTo(id, self.currentStepId, currentStepElement, id, nextStepElement, direction, hashTagNav);
}
return true;
};
/* --- CALLBACKS --- */
/**
* Callback executed when autoPlay is started
*/
self.started = function(self) {
// console.log("auto play started");
};
/**
* Callback executed when autoPlay is stopped
*/
self.stopped = function(self) {
// console.log("auto play stopped");
};
/**
* Callback executed when a step animation starts
*
* @param {Number} id - The ID of the step that was started
* @param {Object} self - Properties and methods available to this instance
*/
self.animationStarted = function(id, self) {
// console.log("Animation started", id);
};
/**
* Callback executed when a step animation finishes
*
* @param {Number} id - The ID of the step that finished
* @param {Object} self - Properties and methods available to this instance
*/
self.animationEnded = function(id, self) {
// console.log("Animation ended", id);
};
/**
* Callback executed when the current phase starts animating
*
* @param {Object} self - Properties and methods available to this instance
*/
self.currentPhaseStarted = function(id, self) {
// console.log("Current phase started", id);
};
/**
* Callback executed when the current phase finishes animating
*
* @param {Object} self - Properties and methods available to this instance
*/
self.currentPhaseEnded = function(id, self) {
// console.log("Current phase ended", id);
};
/**
* Callback executed when the next phase starts animating
*
* @param {Object} self - Properties and methods available to this instance
*/
self.nextPhaseStarted = function(id, self) {
// console.log("Next phase started", id);
};
/**
* Callback executed when the next phase finishes animating
*
* @param {Object} self - Properties and methods available to this instance
*/
self.nextPhaseEnded = function(id, self) {
// console.log("Next phase ended", id);
};
/**
* When the throttled window resize event occurs
*
* @param {Object} self - Properties and methods available to this instance
*/
self.throttledResize = function(self) {
};
/**
* Callback executed when preloading has finished
*
* @param {Object} self - Properties and methods available to this instance
*/
self.preloaded = function(self) {
// console.log("preloaded");
};
/**
* Callback executed every time an image to be preloaded returns a status
*
* @param {String} result - Whether the image is "loaded" or "broken"
* @param {String} src - The source of the image
* @param {Number} progress - The number of images that have returned a result
* @param {Number} length - The total number of images that are being preloaded
* @param {Object} self - Properties and methods available to this instance
*/
self.preloadProgress = function(result, src, progress, length, self) {
// console.log(progress, length);
};
/**
* Callback executed when Sequence is ready to be interacted with
* = preloading + domDelay
*
* @param {Object} self - Properties and methods available to this instance
*/
self.ready = function(self) {
// console.log("Ready");
};
/**
* Callback executed when Sequence has finished being destroyed via .destroy()
*
* @param {Object} self - Properties and methods available to this instance
*/
self.destroyed = function(self) {
// console.log("Destroyed");
};
/**
* Make some of Sequence's helper functions public
*
* addClass() / removeClass() etc can be useful for custom theme code
*/
self.utils = {
addClass: addClass,
removeClass: removeClass,
addEvent: addEvent,
removeEvent: removeEvent
};
/* --- INIT --- */
self.init(element);
// Save the instance globally so we can return it if an init is tried again
instances.push(self);
return self;
});
return Sequence;
}
if (typeof define === "function" && define.amd) {
define(["imagesLoaded", "Hammer"], defineSequence);
} else {
/* Make dependencies optional */
Hammer = (typeof Hammer !== "function") ? null: Hammer;
imagesLoaded = (typeof imagesLoaded !== "function") ? null: imagesLoaded;
var sequence = defineSequence(imagesLoaded, Hammer);
}