sync
[bootswatch] / mPurpose / js / jquery.sequence.js
1 /*
2 Sequence.js (http://www.sequencejs.com)
3 Version: 1.0.1
4 Author: Ian Lunn @IanLunn
5 Author URL: http://www.ianlunn.co.uk/
6 Github: https://github.com/IanLunn/Sequence
7
8 This is a FREE script and is available under a MIT License:
9 http://www.opensource.org/licenses/mit-license.php
10
11 Sequence.js and its dependencies are (c) Ian Lunn Design 2012 - 2013 unless otherwise stated.
12
13 Sequence also relies on the following open source scripts:
14
15 - jQuery imagesLoaded 2.1.0 (http://github.com/desandro/imagesloaded)
16         Paul Irish et al
17         Available under a MIT License: http://www.opensource.org/licenses/mit-license.php
18
19 - jQuery TouchWipe 1.1.1 (http://www.netcu.de/jquery-touchwipe-iphone-ipad-library)
20         Andreas Waltl, netCU Internetagentur (http://www.netcu.de)
21         Available under a MIT License: http://www.opensource.org/licenses/mit-license.php
22
23 - Modernizr 2.6.1 Custom Build (http://modernizr.com/) (Named Modernizr for Sequence to prevent conflicts)
24         Copyright (c) Faruk Ates, Paul Irish, Alex Sexton
25         Available under the BSD and MIT licenses: www.modernizr.com/license/
26         */
27
28 ;(function($) {
29         var windowLoaded = false;
30         $(window).bind("load", function() {
31                 windowLoaded = true;
32         });
33
34         function Sequence(element, options, defaults, get) {
35                 var self = this;
36                 self.container = $(element); //the Sequence containing element
37                 self.canvas = self.container.children('.sequence-canvas'); //the Sequence canvas which holds Sequence's frames (<li> elements)
38                 self.frames = self.canvas.children('li'); //the Sequence frames (top level <li> elements within the Sequence canvas)
39
40                 self._modernizrForSequence(); //get the custom build necessary for Sequence
41
42                 var prefixes = { //convert JS transition/animation names to CSS names
43                         'WebkitTransition' : '-webkit-',
44                         'WebkitAnimation'  : '-webkit-',
45                         'MozTransition'    : '-moz-',
46                         'MozAnimation '    : '-moz-',
47                         'OTransition'      : '-o-',
48                         'OAnimation'       : '-o-',
49                         'msTransition'     : '-ms-',
50                         'msAnimation'      : '-ms-',
51                         'transition'       : '',
52                         'animation'        : ''
53                 };
54                 
55                 var transitionsAndAnimations = { //convert JS transition names to JS transition end and animation end event names (also apply a classname of .sequence to the event)
56                         'WebkitTransition' : 'webkitTransitionEnd.sequence',
57                         'WebkitAnimation'  : 'webkitAnimationEnd.sequence',
58                         'MozTransition'    : 'transitionend.sequence',
59                         'MozAnimation'     : 'animationend.sequence',
60                         'OTransition'      : 'otransitionend.sequence',
61                         'OAnimation'       : 'oanimationend.sequence',
62                         'msTransition'     : 'MSTransitionEnd.sequence',
63                         'msAnimation'      : 'MSAnimationEnd.sequence',
64                         'transition'       : 'transitionend.sequence',
65                         'animation'        : 'animationend.sequence'
66                 };
67
68                 self.transitionPrefix = prefixes[ModernizrForSequence.prefixed('transition')], //work out the CSS transition prefix for the browser being used (-webkit- for example)
69                 self.animationPrefix = prefixes[ModernizrForSequence.prefixed('animation')], //work out the CSS animation prefix for the browser being used
70
71                 self.transitionProperties = {},
72                 self.transitionEnd = transitionsAndAnimations[ModernizrForSequence.prefixed('transition')] + ' ' + transitionsAndAnimations[ModernizrForSequence.prefixed('animation')], //work out the JS transitionEnd name for the browser being used (webkitTransitionEnd webkitAnimationEnd for example)
73                 self.numberOfFrames = self.frames.length, //number of frames (<li>) Sequence consists of
74
75                 self.transitionsSupported = (self.transitionPrefix !== undefined) ? true : false, //determine if transitions are supported
76                 self.hasTouch = ("ontouchstart" in window) ? true : false, //determine if this is a touch enabled device
77                 self.isPaused = false, //whether Sequence is paused
78                 self.isBeingHoveredOver = false, //whether the Sequence canvas is currently being hovered over
79
80                 self.container.removeClass('sequence-destroyed'); //if Sequence is destroyed using .destroy(), it is given a clas of "destroy", remove that now if present
81
82                 //CALLBACKS
83                 self.paused = function() {},                                                                                            //executes when Sequence is paused
84                 self.unpaused = function() {},                                                                                  //executes when Sequence is unpaused
85
86                 self.beforeNextFrameAnimatesIn = function() {},                 //executes before the next frame animates in
87                 self.afterNextFrameAnimatesIn = function() {},                  //executes after the next frame animates in
88                 self.beforeCurrentFrameAnimatesOut = function() {},     //executes before the current frame animates out
89                 self.afterCurrentFrameAnimatesOut = function() {},      //executes after the current frame animates out
90
91                 self.afterLoaded = function() {};                                                                               //executes after Sequence is initiated
92                 self.destroyed = function() {};                                                                                 //executes when Sequence is destroyed via the destory() function
93
94                 //INIT
95                 self.settings = $.extend({}, defaults, options); //combine default options with developer defined ones
96                 self.settings.preloader = self._renderUiElements(self.settings.preloader, '.sequence-preloader'); //set up the preloader and save it
97                 self.isStartingFrame = (self.settings.animateStartingFrameIn) ? true : false; //determine if the first frame should animate in
98                 self.settings.unpauseDelay = (self.settings.unpauseDelay === null) ? self.settings.autoPlayDelay : self.settings.unpauseDelay; //if the unpauseDelay is not specified, make it the same as the autoPlayDelay speed
99                 self.getHashTagFrom = (self.settings.hashDataAttribute) ? "data-sequence-hashtag": "id"; //get the hashtag from the ID or data attribute?
100                 self.frameHashID = []; //array that matches frames with has IDs
101                 self.direction = self.settings.autoPlayDirection;
102
103                 if(self.settings.hideFramesUntilPreloaded && self.settings.preloader !== undefined  && self.settings.preloader !== false) { //if using a preloader and hiding frames until preloading has completed...
104                         self.frames.hide(); //hide Sequence's frames
105                 }
106
107                 if(self.transitionPrefix === "-o-") { //if Opera prefixes are required...
108                         self.transitionsSupported = self._operaTest(); //run a test to see if Opera correctly supports transitions (Opera 11 has bugs relating to transitions)
109                 }
110
111                 self.frames.removeClass("animate-in"); //remove any instance of "animate-in", which should be used incase JS is disabled
112
113                 //functionality to run once Sequence has preloaded
114                 function oncePreloaded() {
115                         self.afterLoaded(); //callback
116                         if(self.settings.hideFramesUntilPreloaded && self.settings.preloader !== undefined  && self.settings.preloader !== false) {
117                                 self.frames.show();
118                         }
119                         if(self.settings.preloader !== undefined  && self.settings.preloader !== false){
120                                 if(self.settings.hidePreloaderUsingCSS && self.transitionsSupported) {
121                                         self.prependPreloadingCompleteTo = (self.settings.prependPreloadingComplete === true) ? self.settings.preloader : $(self.settings.prependPreloadingComplete);
122                                         self.prependPreloadingCompleteTo.addClass("preloading-complete");
123                                         setTimeout(init, self.settings.hidePreloaderDelay);
124                                 }else{
125                                         self.settings.preloader.fadeOut(self.settings.hidePreloaderDelay, function() {
126                                                 clearInterval(self.defaultPreloader);
127                                                 init();
128                                         });
129                                 }
130                         }else{
131                                 init();
132                         }
133                 }
134
135                 var preloadTheseFramesLength = self.settings.preloadTheseFrames.length; //how many frames to preload?
136                 var preloadTheseImagesLength = self.settings.preloadTheseImages.length; //how many single images to load?
137
138                 function saveImagesToArray(length, srcOnly) {
139                         var imagesToPreload = []; //saves the images that are to be preloaded
140                         if(!srcOnly){
141                                 for(var i = length; i > 0; i--){ //for each frame to be preloaded...
142                                         self.frames.eq(self.settings.preloadTheseFrames[i-1]-1).find("img").each(function() { //find <img>'s in specific frames, and for each found...
143                                                 imagesToPreload.push($(this)[0]); //add it to the array of images to be preloaded
144                                         });
145                                 }
146                         }else{
147                                 for(var j = length; j > 0; j--) { //for each frame to be preloaded...
148                                         imagesToPreload.push($("body").find('img[src="'+self.settings.preloadTheseImages[j-1]+'"]')); //find any <img> with the given source and add it to the array of images to be preloaded
149                                 }
150                         }
151                         return imagesToPreload;
152                 }
153
154                 //jQuery imagesLoaded plugin v2.1.0 (http://github.com/desandro/imagesloaded)
155                 function imagesLoaded(imagesToPreload, callback) {
156                         var BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
157                         var $this = imagesToPreload,
158                         deferred = $.isFunction($.Deferred) ? $.Deferred() : 0,
159                         hasNotify = $.isFunction(deferred.notify),
160                         $images = $this.find('img').add( $this.filter('img') ),
161                         loaded = [],
162                         proper = [],
163                         broken = [];
164
165                         //Register deferred callbacks
166                         if($.isPlainObject(callback)) {
167                                 $.each(callback, function(key, value) {
168                                         if(key === 'callback') {
169                                                 callback = value;
170                                         }else if(deferred) {
171                                                 deferred[key](value);
172                                         }
173                                 });
174                         }
175
176                         function doneLoading() {
177                                 var $proper = $(proper),
178                                 $broken = $(broken);
179
180                                 if(deferred) {
181                                         if(broken.length) {
182                                                 deferred.reject($images, $proper, $broken);
183                                         }else{
184                                                 deferred.resolve($images);
185                                         }
186                                 }
187
188                                 if($.isFunction(callback)) {
189                                         callback.call($this, $images, $proper, $broken);
190                                 }
191                         }
192
193                         function imgLoaded( img, isBroken ) {
194                                 if(img.src === BLANK || $.inArray(img, loaded) !== -1) { // don't proceed if BLANK image, or image is already loaded
195                                         return;
196                         }
197
198                                 loaded.push(img); // store element in loaded images array
199
200                                 if(isBroken) { // keep track of broken and properly loaded images
201                                         broken.push(img);
202                                 }else{
203                                         proper.push(img);
204                                 }
205
206                                 $.data(img, 'imagesLoaded', {isBroken: isBroken, src: img.src }); // cache image and its state for future calls
207
208                                 if(hasNotify) { // trigger deferred progress method if present
209                                         deferred.notifyWith($(img), [isBroken, $images, $(proper), $(broken)]);
210                                 }
211
212                                 if($images.length === loaded.length) { // call doneLoading and clean listeners if all images are loaded
213                                         setTimeout(doneLoading);
214                                         $images.unbind('.imagesLoaded');
215                                 }
216                         }
217
218                         if(!$images.length) { // if no images, trigger immediately
219                                 doneLoading();
220                         }else{
221                                 $images.bind('load.imagesLoaded error.imagesLoaded', function(event) {
222                                         imgLoaded(event.target, event.type === 'error'); // trigger imgLoaded
223                                 }).each(function(i, el) {
224                                         var src = el.src;
225                                         var cached = $.data(el, 'imagesLoaded'); // find out if this image has been already checked for status if it was, and src has not changed, call imgLoaded on it
226                                         if(cached && cached.src === src) {
227                                                 imgLoaded(el, cached.isBroken);
228                                                 return;
229                                         }
230
231                                         if(el.complete && el.naturalWidth !== undefined) { // if complete is true and browser supports natural sizes, try to check for image status manually
232                                                 imgLoaded(el, el.naturalWidth === 0 || el.naturalHeight === 0);
233                                                 return;
234                                         }
235
236                                         // cached images don't fire load sometimes, so we reset src, but only when dealing with IE, or image is complete (loaded) and failed manual check webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
237                                         if(el.readyState || el.complete) {
238                                                 el.src = BLANK;
239                                                 el.src = src;
240                                         }
241                                 });
242                         }
243                 }
244
245                 if(self.settings.preloader !== undefined && self.settings.preloader !== false && (preloadTheseFramesLength !== 0 || preloadTheseImagesLength !== 0)) { //if using the preloader and the dev has specified some images should preload...
246                         var frameImagesToPreload = saveImagesToArray(preloadTheseFramesLength); //get images from particular Sequence frames to be preloaded
247                         var individualImagesToPreload = saveImagesToArray(preloadTheseImagesLength, true); //get images with specific source values to be preloaded
248                         var imagesToPreload = $(frameImagesToPreload.concat(individualImagesToPreload)); //combine frame images and individual images
249
250                         imagesLoaded(imagesToPreload, oncePreloaded);
251                 }else{ //if not using the preloader...
252                         if(windowLoaded === true) { //if the window has already loaded...
253                                 oncePreloaded(); //run the init functionality when the preloader has finished
254                                 $(this).unbind("load.sequence"); //unbind the load event as it's no longer needed
255                         }else{ //if the window hasn't already loaded...
256                                 $(window).bind("load.sequence", function() { //when the window loads... 
257                                         oncePreloaded(); //run the init functionality when the preloader has finished
258                                         $(this).unbind("load.sequence"); //unbind the load event as it's no longer needed
259                                 });
260                         }
261                 }
262
263                 function init() {
264                         $(self.settings.preloader).remove(); //remove the preloader element
265
266                         self.nextButton = self._renderUiElements(self.settings.nextButton, ".sequence-next"); //set up the next button
267                         self.prevButton = self._renderUiElements(self.settings.prevButton, ".sequence-prev"); //set up the previous button
268                         self.pauseButton = self._renderUiElements(self.settings.pauseButton, ".sequence-pause"); //set up the pause button
269                         self.pagination = self._renderUiElements(self.settings.pagination, ".sequence-pagination"); //set up the pagination
270
271                         if((self.nextButton !== undefined && self.nextButton !== false) && self.settings.showNextButtonOnInit === true){self.nextButton.show();} //if using a next button, show it
272                         if((self.prevButton !== undefined && self.prevButton !== false) && self.settings.showPrevButtonOnInit === true){self.prevButton.show();} //if using a previous button, show it
273                         if((self.pauseButton !== undefined && self.pauseButton !== false) && self.settings.showPauseButtonOnInit === true){self.pauseButton.show();} //if using a pause button, show it
274
275                         if(self.settings.pauseIcon !== false) {
276                                 self.pauseIcon = self._renderUiElements(self.settings.pauseIcon, ".sequence-pause-icon");
277                                 if(self.pauseIcon !== undefined) {
278                                         self.pauseIcon.hide();
279                                 }
280                         }else{
281                                 self.pauseIcon = undefined;
282                         }
283
284                         if(self.pagination !== undefined && self.pagination !== false) {
285                                 self.paginationLinks = self.pagination.children(); //get each pagination link
286
287                                 self.paginationLinks.on('click.sequence', function() { //when a pagination link is clicked...
288                                         var associatedFrameNumber = $(this).index() + 1; //get the number of the frame this link is associated with
289                                         self.goTo(associatedFrameNumber); //go to the associate frame
290                                 });
291
292                                 if(self.settings.showPaginationOnInit === true) {
293                                         self.pagination.show();
294                                 }
295                         }
296
297                         self.nextFrameID = self.settings.startingFrameID;
298
299                         if(self.settings.hashTags === true) { //if using hashtags...
300                                 self.frames.each(function() { //for each frame...
301                                         self.frameHashID.push($(this).prop(self.getHashTagFrom)); //add the hashtag to an array
302                                 });
303
304                                 self.currentHashTag = location.hash.replace("#", ""); //get the current hashtag
305                                 if(self.currentHashTag === undefined || self.currentHashTag === "") { //if there is no hashtag...
306                                         self.nextFrameID = self.settings.startingFrameID; //use the startingFrameID
307                                 }else{
308                                         self.frameHashIndex = $.inArray(self.currentHashTag, self.frameHashID); //get the index of the frame that matches the hashtag
309                                         if(self.frameHashIndex !== -1){  //if the hashtag matches a Sequence frame ID...
310                                                 self.nextFrameID = self.frameHashIndex + 1; //use the frame associated to the hashtag
311                                         }else{
312                                                 self.nextFrameID = self.settings.startingFrameID; //use the startingFrameID
313                                         }
314                                 }
315                         }
316
317                         self.nextFrame = self.frames.eq(self.nextFrameID-1); //get the next frame
318                         self.nextFrameChildren = self.nextFrame.children(); //get the elements within the next frame to be animated
319
320                         if(self.pagination !== undefined) { //if using pagination, make the starting frame the current one in pagination
321                                 $(self.paginationLinks[self.settings.startingFrameID-1]).addClass('current'); //add the 'current' class to the current frame
322                         }
323
324                         if(self.transitionsSupported) { //initiate the full featured Sequence if transitions are supported...
325                                 if(!self.settings.animateStartingFrameIn) { //start first frame in animated in position
326                                         self.currentFrameID = self.nextFrameID;
327
328                                         if(self.settings.moveActiveFrameToTop) {
329                                                 self.nextFrame.css('z-index', self.numberOfFrames);
330                                         }
331
332                                         self._resetElements(self.transitionPrefix, self.nextFrameChildren, "0s");
333                                         self.nextFrame.addClass("animate-in");
334                                         if(self.settings.hashTags && self.settings.hashChangesOnFirstFrame) {
335                                                 self.currentHashTag = self.nextFrame.prop(self.getHashTagFrom);
336                                                 document.location.hash = "#"+self.currentHashTag;
337                                         }
338
339                                         setTimeout(function() {
340                                                 self._resetElements(self.transitionPrefix, self.nextFrameChildren, "");
341                                         }, 100);
342
343                                         self._resetAutoPlay(true, self.settings.autoPlayDelay);
344                                 }else if(self.settings.reverseAnimationsWhenNavigatingBackwards && self.settings.autoPlayDirection -1 && self.settings.animateStartingFrameIn) { //animate in backwards
345                                         self._resetElements(self.transitionPrefix, self.nextFrameChildren, "0s");
346                                         self.nextFrame.addClass("animate-out");
347                                         self.goTo(self.nextFrameID, -1, true);
348                                 }else{ //animate in forwards
349                                         self.goTo(self.nextFrameID, 1, true);
350                                 }
351                         }else{ //initiate a basic slider for browsers that don't support CSS3 transitions
352                                 self.container.addClass("sequence-fallback");
353                                 self.currentFrameID = self.nextFrameID;
354                                 if(self.settings.hashTags && self.settings.hashChangesOnFirstFrame){
355                                         self.currentHashTag = self.nextFrame.prop(self.getHashTagFrom);
356                                         document.location.hash = "#"+self.currentHashTag;
357                                 }
358
359                                 self.frames.addClass("animate-in"); //move each frame into its animate-in position
360                                 self.frames.not(':eq('+(self.nextFrameID-1)+')').css({"display": "none", "opacity": 0}); //set all frames (except the next one) to display: none, opacity: 0
361                                 self._resetAutoPlay(true, self.settings.autoPlayDelay);
362                         }
363                         //END INIT
364                         //EVENTS
365                         if(self.nextButton !== undefined) { //if a next button is defined...
366                                 self.nextButton.bind('click.sequence', function() { //when the next button is clicked...
367                                         self.next(); //go to the next frame
368                                 });
369                         }
370
371                         if(self.prevButton !== undefined) { //if a previous button is defined...
372                                 self.prevButton.bind('click.sequence', function() { //when the previous button is clicked...
373                                         self.prev(); //go to the previous frame
374                                 });
375                         }
376
377                         if(self.pauseButton !== undefined) { //if a pause button is defined...
378                                 self.pauseButton.bind('click.sequence', function() { //when the pause button is clicked...
379                                         self.pause(true); //pause Sequence and set hardPause to true
380                                 });
381                         }
382
383                         function keyEvents(keyPressed, keyDirections) {
384                                 var keyCode;
385                                 var keyCodes;
386
387                                 for(keyCodes in keyDirections) {
388                                         if(keyCodes === "left" || keyCodes === "right") {
389                                                 keyCode = defaultKeys[keyCodes];
390                                         }else{
391                                                 keyCode = keyCodes;
392                                         }
393
394                                         if(keyPressed === parseFloat(keyCode)) { //if the key pressed is associated with a function...
395                                                 self._initCustomKeyEvent(keyDirections[keyCodes]); //initiate the function
396                                         }
397                                 }
398                         }
399
400                         if(self.settings.keyNavigation) {
401                                 var defaultKeys = {
402                                         'left'  : 37,
403                                         'right' : 39
404                                 };
405
406                                 $(document).bind('keydown.sequence', function(e) { //when a key is pressed...
407                                         var keyCodeChar = String.fromCharCode(e.keyCode);
408                                         if((keyCodeChar > 0 && keyCodeChar <= self.numberOfFrames) && (self.settings.numericKeysGoToFrames)) {
409                                                 self.nextFrameID = keyCodeChar;
410                                                 self.goTo(self.nextFrameID); //go to specified frame
411                                         }
412
413                                         keyEvents(e.keyCode, self.settings.keyEvents); //run default keyevents
414                                         keyEvents(e.keyCode, self.settings.customKeyEvents); //run custom keyevents
415                                 });
416                         }
417                         
418                         self.canvas.on({
419                                 'mouseenter.sequence': function() { //when the mouse enter the Sequence element...
420                                         if(self.settings.pauseOnHover && self.settings.autoPlay && !self.hasTouch) { //if using pauseOnHover and autoPlay on non touch devices
421                                                 self.isBeingHoveredOver = true;
422                                                 if(!self.isHardPaused) { //if Sequence is hard paused (via a pause button)...
423                                                         self.pause(); //pause autoPlay
424                                                 }
425                                         }
426                                 },
427                                 'mouseleave.sequence': function() { //when the mouse leaves the Sequence element...
428                                         if(self.settings.pauseOnHover && self.settings.autoPlay && !self.hasTouch) { //if using pauseOnHover and autoPlay on non touch devices
429                                                 self.isBeingHoveredOver = false;
430                                                 if(!self.isHardPaused) { //if Sequence is not hard paused (via a pause button)...
431                                                         self.unpause(); //unpause autoPlay
432                                                 }
433                                         }
434                                 }
435                         });
436
437                         if(self.settings.hashTags) { //if hashchange is enabled in the settings...
438                                 $(window).bind('hashchange.sequence', function() { //when the hashtag changes...
439                                         var newTag = location.hash.replace("#", ""); //grab the new hashtag
440
441                                         if(self.currentHashTag !== newTag) { //if the last hashtag is not the same as the current one...
442                                                 self.currentHashTag = newTag; //save the new tag
443                                                 self.frameHashIndex = $.inArray(self.currentHashTag, self.frameHashID); //get the index of the frame that matches the hashtag
444                                                 if(self.frameHashIndex !== -1) { //if the hashtag matches a Sequence frame ID...
445                                                         self.nextFrameID = self.frameHashIndex + 1; //set that frame as the next one
446                                                                 self.goTo(self.nextFrameID); //go to the next frame
447                                                         }
448                                                 }
449                                         });
450                         }
451
452                         function cancelTouch() {
453                                 self.canvas.on("touchmove.sequence", onTouchMove);
454                                 startX = null;
455                                 isMoving = false;
456                         }
457
458                         function onTouchMove(e) {
459                                 if(self.settings.swipePreventsDefault) {
460                                         e.preventDefault();
461                                 }
462                                 if(isMoving) {
463                                         var x = e.originalEvent.touches[0].pageX;
464                                         var y = e.originalEvent.touches[0].pageY;
465                                         var dx = startX - x;
466                                         var dy = startY - y;
467                                         if(Math.abs(dx) >= self.settings.swipeThreshold) {
468                                                 cancelTouch();
469                                                 if(dx > 0) {
470                                                         self._initCustomKeyEvent(self.settings.swipeEvents.left);
471                                                 }else{
472                                                         self._initCustomKeyEvent(self.settings.swipeEvents.right);
473                                                 }
474                                         }else if(Math.abs(dy) >= self.settings.swipeThreshold) {
475                                                 cancelTouch();
476                                                 if(dy > 0) {
477                                                         self._initCustomKeyEvent(self.settings.swipeEvents.down);
478                                                 }else{
479                                                         self._initCustomKeyEvent(self.settings.swipeEvents.up);
480                                                 }
481                                         }
482                                 }
483                         }
484
485                         function onTouchStart(e) {
486                                 if(e.originalEvent.touches.length === 1) {
487                                         startX = e.originalEvent.touches[0].pageX;
488                                         startY = e.originalEvent.touches[0].pageY;
489                                         isMoving = true;
490                                         self.canvas.on("touchmove.sequence", onTouchMove);
491                                 }
492                         }
493
494                         if(self.settings.swipeNavigation && self.hasTouch) { //if using swipeNavigation and the device has touch capabilities...
495                                 //jQuery TouchWipe v1.1.1 (http://www.netcu.de/jquery-touchwipe-iphone-ipad-library)
496                                 var startX;
497                                 var startY;
498                                 var isMoving = false;
499
500                                 self.canvas.on("touchstart.sequence", onTouchStart);
501                         }
502                         //END EVENTS
503                 }
504         } //END CONSTRUCTOR
505
506         Sequence.prototype = {
507
508                 //PUBLIC METHODS
509                 /*
510                 start autoPlay -- causing Sequence to automatically change frame every x amount of milliseconds
511                 
512                 delay: a time in ms before starting the autoPlay feature (if unspecified, the default will be used)
513                 */
514                 startAutoPlay: function(delay) {
515                         var self = this;
516                         delay = (delay === undefined) ? self.settings.autoPlayDelay : delay; //if a delay isn't specified, use the default
517                         self.unpause();
518
519                         self._resetAutoPlay(); //stop autoPlay before starting it again
520                         self.autoPlayTimer = setTimeout(function() { //start a new autoPlay timer and...
521                                 if(self.settings.autoPlayDirection === 1) { //go to either the next or previous frame
522                                         self.next();
523                                 }else{
524                                         self.prev();
525                                 }
526                         }, delay); //after a specified delay
527                 },
528
529                 //stop causing Sequence to automatically change frame every x amount of seconds
530                 stopAutoPlay: function() {
531                         var self = this;
532                         self.pause(true);
533                         clearTimeout(self.autoPlayTimer); //stop the autoPlay timer
534                 },
535
536                 /*
537                 Toggle startAutoPlay (unpausing autoPlay) and stopAutoPlay (pausing autoPlay)
538
539                 hardPause: if true, Sequence's pauseOnHover will not execute. Useful for pause buttons.
540
541                 Note: Sequence 0.7.3 and below didn't have an .unpause() function -- .pause() would pause/unpause
542                 based on the current state. .unpause() is now included for clarity but the .pause() function will
543                 still toggle between paused and unpaused states.
544                 */
545                 pause: function(hardPause) {
546                         var self = this;
547                         if(!self.isSoftPaused) { //if pausing Sequence...
548                                 if(self.pauseButton !== undefined) { //if a pause button is defined...
549                                         self.pauseButton.addClass("paused"); //add the class of "paused" to the pause button
550                                         if(self.pauseIcon !== undefined) { //if a pause icon is defined...
551                                                 self.pauseIcon.show(); //show the pause icon
552                                         }
553                                 }
554                                 self.paused(); //callback when Sequence is paused
555                                 self.isSoftPaused = true;
556                                 self.isHardPaused = (hardPause) ? true : false; //if hardPausing, set hardPause to true
557                                 self.isPaused = true;
558                                 self._resetAutoPlay(); //stop autoPlay
559                         }else{ //if unpausing Sequence...
560                                 self.unpause();
561                         }
562                 },
563
564                 /*
565                 Start the autoPlay feature, as well as deal with any changes to pauseButtons, pauseIcons and public variables etc
566                 
567                 callback: if false, the unpause callback will not be initiated (this is because unpause is used internally during the stop and start of each frame)
568                 */
569                 unpause: function(callback) {
570                         var self = this;
571                         if(self.pauseButton !== undefined) { //if a pause button is defined...
572                                 self.pauseButton.removeClass("paused"); //remove the class of "paused" from the pause button
573                                 if(self.pauseIcon !== undefined) { //if a pause icon is defined...
574                                         self.pauseIcon.hide(); //hide the pause icon
575                                 }
576                         }
577
578                         self.isSoftPaused = false;
579                         self.isHardPaused = false;
580                         self.isPaused = false;
581
582                         if(!self.active) {
583                                 if(callback !== false) {
584                                         self.unpaused(); //callback when Sequence is unpaused
585                                 }
586                                 self._resetAutoPlay(true, self.settings.unpauseDelay); //start autoPlay after a delay specified via the unpauseDelay setting
587                         }else{
588                                 self.delayUnpause = true; //Sequence is animating so delay the unpause event until the animation completes
589                         }
590                 },
591
592                 //Go to the frame ahead of the current one
593                 next: function() {
594                         var self = this;
595                         self.nextFrameID = (self.currentFrameID !== self.numberOfFrames) ? self.currentFrameID + 1 : 1; //work out the next frame
596                         if(self.active === false || self.active === undefined) { //if Sequence isn't currently animating...
597                                 self.goTo(self.nextFrameID, 1); //go to the next frame
598                         }else{ //if Sequence is currently animating...
599                                 self.goTo(self.nextFrameID, 1, true); //go immediately to the next frame (ignoring the transition threshold)
600                         }
601                 },
602
603                 //Go to the frame prior to the current one
604                 prev: function() {
605                         var self = this;
606                         self.nextFrameID = (self.currentFrameID === 1) ? self.numberOfFrames : self.currentFrameID - 1; //work out the prev frame
607                         if(self.active === false || self.active === undefined) { //if Sequence isn't currently animating...
608                                 self.goTo(self.nextFrameID, -1); //go to the prev frame
609                         }else{ //if Sequence is currently animating...
610                                 self.goTo(self.nextFrameID, -1, true); //go immediately to the prev frame (ignoring the transition threshold)
611                         }
612                 },
613
614                 /*
615                 Go to a specific frame
616                 
617                 id: number of the frame to go to
618                 direction: direction to get to that frame (1 = forward, -1 = reverse)
619                 ignoreTransitionThreshold: if true, ignore the transitionThreshold setting and immediately go to the specified frame
620                 */
621                 goTo: function(id, direction, ignoreTransitionThreshold) {
622                         var self = this;
623                         id = parseFloat(id); //convert the id to a number just in case
624                         var transitionThreshold = (ignoreTransitionThreshold === true) ? 0 : self.settings.transitionThreshold; //if transitionThreshold is to be ignored, set it to zero
625
626                         if((id === self.currentFrameID) //if the id of the frame the user is trying to go to is the same as the currently active one...
627                         || (self.settings.navigationSkip && self.navigationSkipThresholdActive) //or navigationSkip is enabled and the navigationSkipThreshold is active (which prevents frame from being navigated too fast)...
628                         || (!self.settings.navigationSkip && self.active) //or navigationSkip is disbaled but Sequence is animating...
629                         || (!self.transitionsSupported && self.active) //or Sequence is in fallback mode and Sequence is animating...
630                         || (!self.settings.cycle && direction === 1 && self.currentFrameID === self.numberOfFrames) //or cycling is disabled, the user is navigating forward and this is the last frame...
631                         || (!self.settings.cycle && direction === -1 && self.currentFrameID === 1) //or cycling is disabled, the user is navigating backwards and this is the first frame...
632                         || (self.settings.preventReverseSkipping && self.direction !== direction && self.active)) { //or Sequence is animating and the user is trying to change the direction of navigation...
633                                 return false; //don't go to another frame
634                         }else if(self.settings.navigationSkip && self.active) { //if navigationSkip is enabled and Sequence is animating (a frame is being skipped before it has finished animating)...
635                                 self.navigationSkipThresholdActive = true; //the navigationSkipThreshold is now active
636                                 if(self.settings.fadeFrameWhenSkipped) { //if a frame should fade when skipped...
637                                         self.nextFrame.stop().animate({"opacity": 0}, self.settings.fadeFrameTime); //fade
638                                 }
639
640                                 clearTimeout(self.transitionThresholdTimer);
641
642                                 setTimeout(function() { //start the navigationSkipThreshold timer to prevent being able to navigate too quickly
643                                         self.navigationSkipThresholdActive = false; //once the timer is complete, navigationSkip can occur again
644                                 }, self.settings.navigationSkipThreshold);
645                         }
646
647                         if(!self.active || self.settings.navigationSkip) { //if there are no animations running or navigationSkip is enabled...
648                                 self.active = true; //Sequence is now animating
649                                 self._resetAutoPlay(); //stop any autoPlay timer that may be running
650
651                                 if(direction === undefined) { //if no direction to navigate was defined...
652                                         self.direction = (id > self.currentFrameID) ? 1 : -1; //work out which way to go based on what frame is currently active
653                                 }else{
654                                         self.direction = direction; //go to the developer defined frame
655                                 }
656
657                                 self.currentFrame = self.canvas.children(".animate-in"); //find which frame is active -- the frame currently being viewed (and about to be animated out)
658                                 self.nextFrame = self.frames.eq(id-1); //grab the next frame
659                                 self.currentFrameChildren = self.currentFrame.children();       //save the child elements of the current frame
660                                 self.nextFrameChildren = self.nextFrame.children(); //save the child elements of the next frame
661
662                                 if(self.pagination !== undefined) { //if using pagination...
663                                         self.paginationLinks.removeClass('current'); //remove the 'current' class from all pagination links
664                                         $(self.paginationLinks[id-1]).addClass('current'); //add the 'current' class to the current frame
665                                 }
666
667                                 if(self.transitionsSupported) { //if the browser supports CSS3 transitions...
668                                         if(self.currentFrame.length !== undefined) { //if there is a current frame (one that is in it's animate-in position)...
669                                                 self.beforeCurrentFrameAnimatesOut(); //callback
670                                                 if(self.settings.moveActiveFrameToTop) { //if the active frame should move to the top...
671                                                         self.currentFrame.css("z-index", 1); //move this frame to the bottom as it is now inactive
672                                                 }
673                                                 self._resetElements(self.transitionPrefix, self.nextFrameChildren, "0s"); //give the next frame elements a transition-duration and transition-delay of 0s so they don't transition to their reset position
674                                                 if(!self.settings.reverseAnimationsWhenNavigatingBackwards || self.direction === 1) { //if user hit next button...
675                                                         self.nextFrame.removeClass("animate-out"); //reset the next frame back to its starting position
676                                                         self._resetElements(self.transitionPrefix, self.currentFrameChildren, "");  //remove any inline styles from the elements to be animated so styles via the "animate-out" class can take full effect
677                                                 }else if(self.settings.reverseAnimationsWhenNavigatingBackwards && self.direction === -1) { //if the user hit prev button
678                                                         self.nextFrame.addClass("animate-out"); //reset the next frame back to its animate-out position
679                                                         self._reverseTransitionProperties(); //reverse the transition-duration, transition-delay and transition-timing-function
680                                                 }
681                                         }else{
682                                                 self.isStartingFrame = false; //no longer the first frame
683                                         }
684
685                                         self.active = true; //Sequence is now animating
686                                         self.currentFrame.unbind(self.transitionEnd); //remove the animation end event
687                                         self.nextFrame.unbind(self.transitionEnd); //remove the animation end event
688
689                                         if(self.settings.fadeFrameWhenSkipped && self.settings.navigationSkip) { //if a frame may have faded out when it was previously skipped...
690                                                 self.nextFrame.css("opacity", 1); //show it again
691                                         }
692
693                                         self.beforeNextFrameAnimatesIn(); //callback
694                                         if(self.settings.moveActiveFrameToTop) { //if an active frame should be moved to the top...
695                                                 self.nextFrame.css('z-index', self.numberOfFrames);
696                                         }
697
698                                         //modifications to the current and next frame's elements to get them ready to animate
699                                         if(!self.settings.reverseAnimationsWhenNavigatingBackwards || self.direction === 1) { //if user hit next button...
700                                                 setTimeout(function() { //50ms timeout to give the browser a chance to modify the DOM sequentially
701                                                         self._resetElements(self.transitionPrefix, self.nextFrameChildren, ""); //remove any inline styles from the elements to be animated so styles via the "animate-in" class can take full effect
702                                                         self._waitForAnimationsToComplete(self.nextFrame, self.nextFrameChildren, "in"); //wait for the next frame to animate in
703                                                         if(self.afterCurrentFrameAnimatesOut !== "function () {}" || (self.settings.transitionThreshold === true && ignoreTransitionThreshold !== true)) { //if the afterCurrentFrameAnimatesOut is being used...
704                                                                 self._waitForAnimationsToComplete(self.currentFrame, self.currentFrameChildren, "out", true, 1); //wait for the current frame to animate out as well
705                                                         }
706                                                 }, 50);
707
708                                                 //final class changes to make animations happen
709                                                 setTimeout(function() { //50ms timeout to give the browser a chance to modify the DOM sequentially
710                                                         if(self.settings.transitionThreshold === false || self.settings.transitionThreshold === 0 || ignoreTransitionThreshold === true) { //if not using a transitionThreshold...
711                                                                 self.currentFrame.toggleClass("animate-out animate-in"); //remove the "animate-in" class and add the "animate-out" class to the current frame
712                                                                 self.nextFrame.addClass("animate-in"); //add the "animate-in" class
713                                                         }else { //if using a transitionThreshold...
714                                                                 self.currentFrame.toggleClass("animate-out animate-in"); //remove the "animate-in" class and add the "animate-out" class to the current frame
715                                                                 if(self.settings.transitionThreshold !== true) { //if there's no transitionThreshold or the dev specified a transitionThreshold in milliseconds
716                                                                         self.transitionThresholdTimer = setTimeout(function() { //cause the next frame to animate in after a certain period
717                                                                                 self.nextFrame.addClass("animate-in"); //add the "animate-in" class
718                                                                         }, transitionThreshold);
719                                                                 }
720                                                         }
721                                                 }, 50);
722                                         }else if(self.settings.reverseAnimationsWhenNavigatingBackwards && self.direction === -1) { //if the user hit prev button
723                                                 setTimeout(function() { //50ms timeout to give the browser a chance to modify the DOM sequentially
724                                                         //remove any inline styles from the elements so styles via the "animate-in" and "animate-out" class can take full effect
725                                                         self._resetElements(self.transitionPrefix, self.currentFrameChildren, "");
726                                                         self._resetElements(self.transitionPrefix, self.nextFrameChildren, "");
727                                                         self._reverseTransitionProperties(); //reverse the transition-duration, transition-delay and transition-timing-function
728
729                                                         self._waitForAnimationsToComplete(self.nextFrame, self.nextFrameChildren, "in"); //wait for the next frame to animate in
730                                                         if(self.afterCurrentFrameAnimatesOut !== "function () {}" || (self.settings.transitionThreshold === true && ignoreTransitionThreshold !== true)) { //if the afterCurrentFrameAnimatesOut is being used...
731                                                                 self._waitForAnimationsToComplete(self.currentFrame, self.currentFrameChildren, "out", true, -1); //wait for the current frame to animate out as well
732                                                         }
733                                                 }, 50);
734
735                                                 //final class changes to make animations happen
736                                                 setTimeout(function() { //50ms timeout to give the browser a chance to modify the DOM sequentially
737                                                         if(self.settings.transitionThreshold === false || self.settings.transitionThreshold === 0 || ignoreTransitionThreshold === true) { //if not using a transitionThreshold...
738                                                                 self.currentFrame.removeClass("animate-in"); //remove the "animate-in" class from the current frame
739                                                                 self.nextFrame.toggleClass("animate-out animate-in"); //add the "animate-out" class and remove the "animate-in" class from the next frame
740                                                         }else{ //if using a transitionThreshold...
741                                                                 self.currentFrame.removeClass("animate-in");
742                                                                 if(self.settings.transitionThreshold !== true) { //if there's no transitionThreshold or the dev specified a transitionThreshold in milliseconds
743                                                                         self.transitionThresholdTimer = setTimeout(function() { //cause the next frame to animate in after a certain period
744                                                                                 self.nextFrame.toggleClass("animate-out animate-in"); //add the "animate-in" class and remove the "animate-out" class
745                                                                         }, transitionThreshold);
746                                                                 }
747                                                         }
748                                                 }, 50);
749                                         }
750                                 }else{ //if the browser doesn't support CSS3 transitions...
751                                         function animationComplete() {
752                                                 self._setHashTag();
753                                                 self.active = false;
754                                                 self._resetAutoPlay(true, self.settings.autoPlayDelay);
755                                         }
756
757                                         switch(self.settings.fallback.theme) {
758                                                 case "fade": //if using the fade fallback theme...
759                                                         self.frames.css({"position": "relative"}); //this allows for fadein/out in IE
760                                                         self.beforeCurrentFrameAnimatesOut();
761                                                         self.currentFrame = self.frames.eq(self.currentFrameID-1);
762                                                         self.currentFrame.animate({"opacity": 0}, self.settings.fallback.speed, function() { //hide the current frame
763                                                                 self.currentFrame.css({"display": "none", "z-index": "1"});
764                                                                 self.afterCurrentFrameAnimatesOut();
765                                                                 self.beforeNextFrameAnimatesIn();
766                                                                 self.nextFrame.css({"display": "block", "z-index": self.numberOfFrames}).animate({"opacity": 1}, 500, function() {
767                                                                         self.afterNextFrameAnimatesIn();
768                                                                 }); //make the next frame the current one and show it
769                                                                 animationComplete();
770                                                         });
771
772                                                         self.frames.css({"position": "relative"}); //this allows for fadein/out in IE
773                                                 break;
774
775                                                 case "slide": //if using the slide fallback theme...
776                                                 default:
777                                                         //create objects which will save the .css() and .animation() objects
778                                                         var animateOut = {};
779                                                         var animateIn = {};
780                                                         var moveIn = {};
781
782                                                         //construct the .css() and .animation() objects
783                                                         if(self.direction === 1) {
784                                                                 animateOut.left = "-100%";
785                                                                 animateIn.left = "100%";
786                                                         }else{
787                                                                 animateOut.left = "100%";
788                                                                 animateIn.left = "-100%";
789                                                         }
790
791                                                         moveIn.left = "0";
792                                                         moveIn.opacity = 1;
793
794                                                         self.currentFrame = self.frames.eq(self.currentFrameID-1);
795                                                         self.beforeCurrentFrameAnimatesOut();
796                                                         self.currentFrame.animate(animateOut, self.settings.fallback.speed, function() {
797                                                                 self.currentFrame.css({"display": "none", "z-index": "1"});
798                                                                 self.afterCurrentFrameAnimatesOut();
799                                                         }); //cause the current frame to animate out
800                                                         self.beforeNextFrameAnimatesIn(); //callback
801                                                         self.nextFrame.show().css(animateIn);
802                                                         self.nextFrame.css({"display": "block", "z-index": self.numberOfFrames}).animate(moveIn, self.settings.fallback.speed, function() { //cause the next frame to animate in
803                                                                 animationComplete();
804                                                                 self.afterNextFrameAnimatesIn(); //callback
805                                                         });
806                                                 break;
807                                         }
808                                 }
809                                 self.currentFrameID = id; //make the currentFrameID the same as the one that is to animate in
810                         }
811                 },
812
813                 /* 
814                         removes Sequence from the element it's attached to
815
816                         callback: a callback to run once .destroy() has finished (or see the sequence.destroyed() callback)
817                 */
818                 destroy: function(callback) {
819                         var self = this;
820
821                         self.container.addClass('sequence-destroyed'); //add a class of "destroyed" in case the developer wants to animate opacity etc
822
823                         //REMOVE EVENTS
824                         if(self.nextButton !== undefined) { //remove the next button click event if a next button is defined
825                                 self.nextButton.unbind('click.sequence');
826                         }
827                         if(self.prevButton !== undefined) { //remove the previous button click event if a previous button is defined
828                                 self.prevButton.unbind('click.sequence');
829                         }
830                         if(self.pauseButton !== undefined) { //remove the pause button click event if a pause button is defined
831                                 self.pauseButton.unbind('click.sequence');
832                         }
833
834                         if(self.pagination !== undefined) {
835                                 self.paginationLinks.unbind('click.sequence');
836                         }
837
838                         $(document).unbind('keydown.sequence'); //unbind key events
839                         self.canvas.unbind('mouseenter.sequence, mouseleave.sequence, touchstart.sequence, touchmove.sequence'); //unbind mouse and touch events
840                         $(window).unbind('hashchange.sequence'); //unbind hashchange
841
842                         //CLEAR TIMERS
843                         self.stopAutoPlay();
844                         clearTimeout(self.transitionThresholdTimer);
845
846                         //TIDY UP THE DOM
847                         self.canvas.children('li').remove(); //because Sequence rearranges frames so the active one is always on top, remove them all...
848                         self.canvas.prepend(self.frames); //then add them back in, in their original order
849                         self.frames.removeClass('animate-in animate-out').removeAttr('style'); //remove classes and inline styles from all frames
850                         self.frames.eq(self.currentFrameID-1).addClass('animate-in'); //keep the current frame in it's animate-in position
851
852                         //HIDE UI ELEMENTS
853                         if(self.nextButton !== undefined && self.nextButton !== false) { //if a next button is defined and was shown on initiation, hide it
854                                 self.nextButton.hide();
855                         }
856                         if(self.prevButton !== undefined && self.prevButton !== false) { //if a prev button is defined and was shown on initiation, hide it
857                                 self.prevButton.hide();
858                         }
859                         if(self.pauseButton !== undefined && self.pauseButton !== false) { //if a pause button is defined and was shown on initiation, hide it
860                                 self.pauseButton.hide();
861                         }
862                         if(self.pauseIcon !== undefined && self.pauseIcon !== false) { //if a pause icon is defined, hide it
863                                 self.pauseIcon.hide();
864                         }
865                         if(self.pagination !== undefined && self.pagination !== false) { //if pagination is defined and was shown on initiation, hide it
866                                 self.pagination.hide();
867                         }
868
869                         //CALLBACKS - a callback can either be passed into the destroy() function or by using the sequence.destroyed() publc method
870                         if(callback !== undefined) {
871                                 callback(); //callback past into the function
872                         }
873
874                         self.destroyed(); //callback
875                         self.container.removeData(); //remove data
876                 },
877                 //END PUBLIC METHODS
878
879                 //PRIVATE METHODS
880
881                 //trigger keyEvents, customKeyEvents and swipeEvents
882                 _initCustomKeyEvent: function(event) {
883                         var self = this;
884
885                         switch(event) {
886                                 case "next":
887                                 self.next();
888                                 break;
889                                 case "prev":
890                                 self.prev();
891                                 break;
892                                 case "pause":
893                                 self.pause(true);
894                                 break;
895                         }
896                 },
897
898                 /*
899                 reset the transition-duration and transition-delay properties of an element
900                 
901                 elementToReset = the element that is to have it's properties reset
902                 cssValue = the value to be given to the transition-duration and transition-delay properties
903                 */
904                 _resetElements: function(prefix, elementToReset, cssValue) {
905                         var self = this;
906
907                         elementToReset.css(
908                                 self._prefixCSS(prefix, {
909                                         "transition-duration": cssValue,
910                                         "transition-delay": cssValue,
911                                         "transition-timing-function": ""
912                                 })
913                         );
914                 },
915
916                 /*
917                 when navigating backwards and reverseAnimationsWhenNavigatingBackwards is true, take the transition properties for forward animation and manipulate the animated elements to create a perfect reversal
918                 */
919                 _reverseTransitionProperties: function() {
920                         var self = this;
921
922                         var currentFrameChildrenDurations = []; //saves the duration for each of the current frame's element
923                         var nextFrameChildrenDurations = []; //saves the duration for each of the next frame's element
924
925                         self.currentFrameChildren.each(function() { //get the overall duration (including delay) for each animated element in the current frame
926                                 currentFrameChildrenDurations.push(parseFloat($(this).css(self.transitionPrefix+'transition-duration').replace('s', '')) + parseFloat($(this).css(self.transitionPrefix+'transition-delay').replace('s', '')));
927                         });
928
929                         self.nextFrameChildren.each(function() { //get the overall duration (including delay) for each animated element in the current frame
930                                 nextFrameChildrenDurations.push(parseFloat($(this).css(self.transitionPrefix+'transition-duration').replace('s', '')) + parseFloat($(this).css(self.transitionPrefix+'transition-delay').replace('s', '')));
931                         });
932
933                         var maximumCurrentFrameDuration = Math.max.apply(Math, currentFrameChildrenDurations); //find which transition duration is the longest
934                         var maximumNextFrameDuration = Math.max.apply(Math, nextFrameChildrenDurations); //find which transition duration is the longest
935                         var transitionDifference = maximumCurrentFrameDuration - maximumNextFrameDuration; //get the overal transition difference between the current and next frame
936                         var currentDelay = 0;
937                         var nextDelay = 0;
938
939                         if(transitionDifference < 0 && !self.settings.preventDelayWhenReversingAnimations) { //if the current frame has a greater duration than the next frame...
940                                 /* note: because the current frame will take longer to animate out than the next to animate in, when this animation is reversed, the current frame will have a delay applied before it animates out. By default, Sequence will aim to avoid this (via the preventDelayWhenReversingAnimations option) because a delay on the current frame may confuse the user. The delay is removed, which means the reversal of animation is slightly out of sync */
941                                 currentDelay = Math.abs(transitionDifference);
942                         }else if(transitionDifference > 0) { //if the next frame has a greater duration than the current frame, add the difference on as a delay
943                                 nextDelay = Math.abs(transitionDifference);
944                         }
945
946                         var reverseEachProperty = function(transitionProperties, currentFrameChildren, maximumFrameDuration, frameDelay) {
947
948                                 function convertTimingFunctionToCubicBezier(timingFunction) {
949
950                                         timingFunction = timingFunction.split(',')[0]; //only use one timing function
951
952                                         var timingFunctionToCubicBezier = {
953                                             "linear" : "cubic-bezier(0.0,0.0,1.0,1.0)",
954                                             "ease"   : "cubic-bezier(0.25, 0.1, 0.25, 1.0)",
955                                             "ease-in": "cubic-bezier(0.42, 0.0, 1.0, 1.0)",
956                                             "ease-in-out": "cubic-bezier(0.42, 0.0, 0.58, 1.0)",
957                                             "ease-out": "cubic-bezier(0.0, 0.0, 0.58, 1.0)"
958                                         };
959
960                                         if(timingFunction.indexOf("cubic-bezier") < 0) { //if the timing-function returned isn't a cubic-bezier()
961                                           timingFunction = timingFunctionToCubicBezier[timingFunction]; //convert it to one
962                                         }
963
964                                         return timingFunction; //return a cubic-bezier()
965                                 }
966
967                                 currentFrameChildren.each(function() {
968
969                                         var duration = parseFloat($(this).css(self.transitionPrefix+'transition-duration').replace('s', '')); //get the elements transition-duration
970                                         var delay = parseFloat($(this).css(self.transitionPrefix+'transition-delay').replace('s', '')); //get the elements transition-delay
971                                         var transitionFunction = $(this).css(self.transitionPrefix+'transition-timing-function'); //get the elements transiion-timing-function
972                                         
973                                         if(transitionFunction.indexOf('cubic') === -1) { //if the function isn't a cubic-bezier (the Blink engine returns keywords instead)...
974                                                 var transitionFunction = convertTimingFunctionToCubicBezier(transitionFunction); //convert the keyword to cubic-bezier()
975                                         }
976
977                                         var cubicBezier = transitionFunction.replace('cubic-bezier(', '').replace(')', '').split(','); //remove the CSS function and just get the array
978                                         $.each(cubicBezier, function(index, value) { //for each point that makes up the cubic bezier...
979                                                 cubicBezier[index] = parseFloat(value); //turn the point into a number (rather than text)
980                                         });
981
982                                         //reverse the cubic bezier
983                                         var reversedCubicBezier = [
984                                         1 - cubicBezier[2],
985                                         1 - cubicBezier[3],
986                                         1 - cubicBezier[0],
987                                         1 - cubicBezier[1]
988                                         ];
989                                         transitionFunction = 'cubic-bezier('+reversedCubicBezier+')'; //add the reversed cubic bezier back into a CSS function
990
991                                         var frameDuration = duration + delay; //get the overall duration of the element
992
993                                         transitionProperties["transition-duration"] = duration + 's'; //reapply the element's transition-duration (to override any inline styles)
994                                         transitionProperties["transition-delay"] = (maximumFrameDuration - frameDuration + frameDelay) + 's'; //add a delay if required
995                                         transitionProperties["transition-timing-function"] = transitionFunction; //reapply the reversed transition function
996                                         $(this).css(
997                                                 self._prefixCSS(self.transitionPrefix, transitionProperties) //set the new transition properties
998                                                 );
999                                 });
1000                         };
1001
1002                         reverseEachProperty(self.transitionProperties, self.currentFrameChildren, maximumCurrentFrameDuration, currentDelay); //reverse properties for each of the current frame's elements
1003                         reverseEachProperty(self.transitionProperties, self.nextFrameChildren, maximumNextFrameDuration, nextDelay); //reverse properties for each of the next frame's elements
1004                 },
1005
1006                 /*
1007                 adds the browser vendors prefix onto multiple CSS properties
1008                 
1009                 prefix = the prefix for the browser Sequence is being viewed in (-webkit- for example)
1010                 properties = the properties to be prefixed (transition-duration for example)
1011                 */
1012                 _prefixCSS: function(prefix, properties) {
1013                         var self = this;
1014
1015                         var css = {};
1016                         for(var property in properties) { //for each property to be modified...
1017                                 css[prefix + property] = properties[property]; //add the prefix to the property name
1018                         }
1019                         return css; //return the prefixed CSS
1020                 },
1021
1022                 /*
1023                 internal function used to start and stop autoPlay
1024                 start: if true, autoPlay will be started, else it'll be stopped
1025                 delay: a time in ms before starting the autoPlay feature (if unspecified, the default will be used)
1026                 */
1027                 _resetAutoPlay: function(start, delay) {
1028                         var self = this;
1029
1030                         if(start === true) { //if starting autoPlay
1031                                 if(self.settings.autoPlay && !self.isSoftPaused) { //if using autoPlay and Sequence isn't paused...
1032                                         clearTimeout(self.autoPlayTimer); //stop the autoPlay timer
1033                                         self.autoPlayTimer = setTimeout(function() { //start a new autoPlay timer and...
1034                                                 if(self.settings.autoPlayDirection === 1) { //go to either the next or previous frame
1035                                                         self.next();
1036                                                 }else{
1037                                                         self.prev();
1038                                                 }
1039                                         }, delay); //after a specified delay
1040                                 }
1041                         }else{ //if stopping autoPlay
1042                                 clearTimeout(self.autoPlayTimer); //stop the autoPlay timer
1043                         }
1044                 },
1045
1046                 /*functionality to initiate the preloader, next/previous buttons and so on
1047         
1048                 devOption: true = the developer wants to use the default selector. false = don't use a uiElement. string = the developer defined selector to use for the UI element
1049                 defaultOption: the default selector to use for the UI element, when the developer specifies false for devOption
1050                 */
1051                 _renderUiElements: function(devOption, defaultOption) {
1052                         var self = this;
1053
1054                         switch(devOption) {
1055                                 case false: //don't set up a uiElement
1056                                 return undefined;
1057
1058                                 case true: //use the default uiElement
1059                                         if(defaultOption === ".sequence-preloader") { //if setting up the preloader...
1060                                                 self._defaultPreloader(self.container, self.transitionsSupported, self.animationPrefix); //get the default preloader
1061                                         }
1062                                         return $(defaultOption); //return the default element
1063
1064                                 default: //if using a developer defined selector...
1065                                         return $(devOption); //return the developer defined element
1066                         }
1067                 },
1068
1069                 /*
1070                 prevents the next frame from animating until the current frame has finished animating
1071
1072                 frame: the frame <li> which is animating
1073                 self.currentFrameChildren: the animated direct child elements of the frame
1074                 transitionPhase: whether the elements are animating "in" to an active position or "out" of an active position
1075                 inAfterwards: whether the next frame should animate in afterwards
1076                 direction: the direction of animation
1077                 */
1078                 _waitForAnimationsToComplete: function(frame, currentFrameChildren, transitionPhase, inAfterwards, direction) {
1079                         var self = this;
1080
1081                         if(transitionPhase === "out") { //if waiting on a frame's element to animate out...
1082                                 var onceComplete = function() {
1083                                         self.afterCurrentFrameAnimatesOut(); //callback
1084
1085                                         if(self.settings.transitionThreshold === true) {
1086                                                 if(direction === 1) {
1087                                                         self.nextFrame.addClass("animate-in"); //add the "animate-in" class
1088                                                 }else if(direction === -1) {
1089                                                         self.nextFrame.toggleClass("animate-out animate-in");
1090                                                 }
1091                                         }
1092                                 };
1093                         }else if(transitionPhase === "in") { //if waiting on a frame's element to animate in...
1094                                 var onceComplete = function() {
1095                                         self.afterNextFrameAnimatesIn(); //callback
1096                                         self._setHashTag(); //set the hashtag to represent the newly active frame
1097
1098                                         self.active = false; //Sequence is not animating
1099
1100                                         if(!self.isHardPaused && !self.isBeingHoveredOver) { //if Sequence isn't hard paused (via a pause button for example) or being hovered over...
1101                                                 if(!self.delayUnpause) { //if unpausing isn't delayed (Sequence wasn't animating when unpause was invoked)...
1102                                                         self.unpause(false); //unpause Sequence but don't run the unpause callback
1103                                                 }else{ //if unpausing was delay because Sequence was animating when unpause was invoked...
1104                                                         self.delayUnpause = false;
1105                                                         self.unpause(); //unpause Sequence
1106                                                 }
1107                                         }
1108                                 };
1109                         }
1110
1111                         currentFrameChildren.data('animationEnded', false); // set the data attribute of each animated element to indicate that the animation has not yet ended
1112                         frame.bind(self.transitionEnd, function(e) { //when an element finishes animating...
1113                                 $(e.target).data('animationEnded', true); // set the data attrbiute to indicate that the element has finished it's animation
1114
1115                                 // now check if all elements have finished animating
1116                                 var allAnimationsEnded = true;
1117                                 currentFrameChildren.each(function() { //for each element being animated within a frame...
1118                                         if($(this).data('animationEnded') === false) { //if the animation hasn't ended...
1119                                                 allAnimationsEnded = false; //not all animations have ended yet
1120                                                 return false; //break out of the animationEnded check early
1121                                         }
1122                                 });
1123
1124                                 if(allAnimationsEnded) { //if all animations have ended...
1125                                         frame.unbind(self.transitionEnd); //stop waiting for animations to end
1126                                         onceComplete();
1127                                 }
1128                         });
1129                 },
1130
1131                 _setHashTag: function() {
1132                         var self = this;
1133
1134                         if(self.settings.hashTags) { //if hashTags is enabled...
1135                                 self.currentHashTag = self.nextFrame.prop(self.getHashTagFrom); //get the hashtag name
1136                                 self.frameHashIndex = $.inArray(self.currentHashTag, self.frameHashID); //get the index of the frame that matches the hashtag
1137                                 if(self.frameHashIndex !== -1 && (self.settings.hashChangesOnFirstFrame || (!self.isStartingFrame || !self.transitionsSupported))) { //if the hashtag matches a Sequence frame ID...
1138                                         self.nextFrameID = self.frameHashIndex + 1;
1139                                         document.location.hash = "#"+self.currentHashTag;
1140                                 }else{
1141                                         self.nextFrameID = self.settings.startingFrameID;
1142                                         self.isStartingFrame = false;
1143                                 }
1144                         }
1145                 },
1146
1147                 /* Modernizr 2.6.1 (Custom Build) | MIT & BSD
1148                  * Build: http://modernizr.com/download/#-svg-prefixed-testprop-testallprops-domprefixes
1149                  */
1150                 _modernizrForSequence: function() {
1151                         ;window.ModernizrForSequence=function(a,b,c){function x(a){i.cssText=a}function y(a,b){return x(prefixes.join(a+";")+(b||""))}function z(a,b){return typeof a===b}function A(a,b){return!!~(""+a).indexOf(b)}function B(a,b){for(var d in a){var e=a[d];if(!A(e,"-")&&i[e]!==c)return b=="pfx"?e:!0}return!1}function C(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:z(f,"function")?f.bind(d||b):f}return!1}function D(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+m.join(d+" ")+d).split(" ");return z(b,"string")||z(b,"undefined")?B(e,b):(e=(a+" "+n.join(d+" ")+d).split(" "),C(e,b,c))}var d="2.6.1",e={},f=b.documentElement,g="modernizrForSequence",h=b.createElement(g),i=h.style,j,k={}.toString,l="Webkit Moz O ms",m=l.split(" "),n=l.toLowerCase().split(" "),o={svg:"http://www.w3.org/2000/svg"},p={},q={},r={},s=[],t=s.slice,u,v={}.hasOwnProperty,w;!z(v,"undefined")&&!z(v.call,"undefined")?w=function(a,b){return v.call(a,b)}:w=function(a,b){return b in a&&z(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=self;if(typeof c!="function")throw new TypeError;var d=t.call(arguments,1),e=function(){if(self instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(t.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(t.call(arguments)))};return e}),p.svg=function(){return!!b.createElementNS&&!!b.createElementNS(o.svg,"svg").createSVGRect};for(var E in p)w(p,E)&&(u=E.toLowerCase(),e[u]=p[E](),s.push((e[u]?"":"no-")+u));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)w(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,enableClasses&&(f.className+=" "+(b?"":"no-")+a),e[a]=b}return e},x(""),h=j=null,e._version=d,e._domPrefixes=n,e._cssomPrefixes=m,e.testProp=function(a){return B([a])},e.testAllProps=D,e.prefixed=function(a,b,c){return b?D(a,b,c):D(a,"pfx")},e}(self,self.document);
1152                 },
1153
1154                 /* Set up Sequence's default preloader
1155                  * prependTo = which element to prepend the preloader to
1156                  * transitions = whether CSS3 transitions are supported
1157                  * prefix = the prefix for the browser Sequence is being viewed in
1158                  */
1159                 _defaultPreloader: function(prependTo, transitions, prefix) {
1160                         var icon = '<div class="sequence-preloader"><svg class="preloading" xmlns="http://www.w3.org/2000/svg"><circle class="circle" cx="6" cy="6" r="6" /><circle class="circle" cx="22" cy="6" r="6" /><circle class="circle" cx="38" cy="6" r="6" /></svg></div>';
1161
1162                         $("head").append("<style>.sequence-preloader{height: 100%;position: absolute;width: 100%;z-index: 999999;}@"+prefix+"keyframes preload{0%{opacity: 1;}50%{opacity: 0;}100%{opacity: 1;}}.sequence-preloader .preloading .circle{fill: #ff9442;display: inline-block;height: 12px;position: relative;top: -50%;width: 12px;"+prefix+"animation: preload 1s infinite; animation: preload 1s infinite;}.preloading{display:block;height: 12px;margin: 0 auto;top: 50%;margin-top:-6px;position: relative;width: 48px;}.sequence-preloader .preloading .circle:nth-child(2){"+prefix+"animation-delay: .15s; animation-delay: .15s;}.sequence-preloader .preloading .circle:nth-child(3){"+prefix+"animation-delay: .3s; animation-delay: .3s;}.preloading-complete{opacity: 0;visibility: hidden;"+prefix+"transition-duration: 1s; transition-duration: 1s;}div.inline{background-color: #ff9442; margin-right: 4px; float: left;}</style>");
1163                         prependTo.prepend(icon);
1164                         if(!ModernizrForSequence.svg && !transitions) { //if SVG isn't supported, remain calm and add this fallback instead...
1165                                 $(".sequence-preloader").prepend('<div class="preloading"><div class="circle inline"></div><div class="circle inline"></div><div class="circle inline"></div></div>');
1166                                 setInterval(function() {
1167                                         $(".sequence-preloader .circle").fadeToggle(500);
1168                                 }, 500);
1169                         }else if(!transitions) { //if transitions aren't supported, toggle the opacity instead
1170                                 setInterval(function() {
1171                                         $(".sequence-preloader").fadeToggle(500);
1172                                 }, 500);
1173                         }
1174                 },
1175
1176                 //a quick test to work out if Opera supports transitions properly (to work around the fact that Opera 11 supports transitions but doesn't return a transition value properly)
1177                 _operaTest: function() {
1178                         $("body").append('<span id="sequence-opera-test"></span>');
1179                         var $operaTest = $("#sequence-opera-test");
1180                         $operaTest.css("-o-transition", "1s"); //add a transition value to the Opera test
1181                         if($operaTest.css("-o-transition") !== "1s") { //if the expected value isn't returned...
1182                                 $operaTest.remove();
1183                                 return false; //cause Opera to go into the fallback theme
1184                         }else{
1185                                 $operaTest.remove();
1186                                 return true;
1187                         }
1188                 }
1189                 //END PRIVATE METHODS
1190
1191         }; //END PROTOTYPE
1192
1193         var defaults = {
1194                 //General Settings
1195                 startingFrameID: 1, //The frame (the list item `<li>`) that should first be displayed when Sequence loads
1196                 cycle: true, //Whether Sequence should navigate to the first frame after the last frame and vice versa
1197                 animateStartingFrameIn: false, //Whether the first frame should animate in to its active position
1198                 transitionThreshold: false, //The delay between a frame animating out and the next animating in (false = no delay, true = the next frame will animate in only once the current frame has animated out)
1199                 reverseAnimationsWhenNavigatingBackwards: true, //Whether animations should be reversed when a user navigates backwards by clicking a previous button/swiping/pressing the left key
1200                 preventDelayWhenReversingAnimations: false, //Whether a delay should be removed when animations are reversed. This delay is removed by default to prevent user confusion
1201                 moveActiveFrameToTop: true, //Whether a frame should be given a higher `z-index` than other frames whilst it is active, to bring it above the others
1202
1203                 //Autoplay Settings
1204                 autoPlay: false, //Cause Sequence to automatically change between frames over a period of time, as defined in autoPlayDelay
1205                 autoPlayDirection: 1, //The direction in which Sequence should auto play
1206                 autoPlayDelay: 5000, //The duration in milliseconds at which frames should remain on screen before animating to the next
1207
1208                 //Frame Skipping Settings
1209                 navigationSkip: true, //Whether the user can navigate through frames before each frame has finished animating
1210                 navigationSkipThreshold: 250, //Amount of time that must pass before the next frame can be navigated to
1211                 fadeFrameWhenSkipped: true, //If a frame is skipped before it finishes animating, it will quickly fade out
1212                 fadeFrameTime: 150, //If fadeFrameWhenSkipped is true, how quickly a frame should fade out when skipped (in milliseconds)
1213                 preventReverseSkipping: false, //Whether the user can change the direction of navigation during frames animating (if navigating forward, the user can only skip forwards when other frames are animating).
1214
1215                 //Next/Prev Button Settings
1216                 nextButton: false, //if true, Sequence will use an element with class ".sequence-next" as the next button, else specify your own selector. false = don't use an in-built next button
1217                 showNextButtonOnInit: true, //if true, Sequence will make the next button display: block; once Sequence has loaded (give the next button selector display: none; in the CSS to hide it until its usable)
1218                 prevButton: false, //if true, Sequence will use an element with class ".sequence-prev" as the previous button, else specify your own selector. false = don't use an in-built previous button
1219                 showPrevButtonOnInit: true, //if true, Sequence will make the previous button display: block; once Sequence has loaded (give the previous button selector display: none; in the CSS to hide it until its usable)
1220
1221                 //Pause Settings
1222                 pauseButton: false, //if true, Sequence will use an element with class ".sequence-pause" as the pause button, else specify your own selector. false = don't use an in-built pause button
1223                 unpauseDelay: null, //the time to wait before navigating to the next frame when Sequence is unpaused. Note that if an unpauseDelay is not specified, the default is the same as the autoPlayDelay setting
1224                 pauseOnHover: true, //pause Sequence when the Sequence container is hovered over
1225                 pauseIcon: false,       //if true, Sequence will use an element with class ".sequence-pause-icon" as the pause icon, else specify your own selector. false = don't use an in-built pause icon (the pause icon will display when Sequence is paused)
1226                 showPauseButtonOnInit: true, //if true, Sequence will make the pause button display: block; once Sequence has loaded (give the pause button selector display: none; in the CSS to hide it until its usable)
1227
1228                 //Pagination Settings
1229                 pagination: false, //if true, Sequence will use an element with class ".sequence-pagination" as the paginated navigation, else specify your own selector. false = don't use in-built pagination
1230                 showPaginationOnInit: true,  //if true, Sequence will make the pagination display: block; once Sequence has loaded (give the pagination selector display: none; in the CSS to hide it until its usable)
1231
1232                 //Preloader Settings
1233                 preloader: false,
1234                 preloadTheseFrames: [1], //all images in these frames will load before Sequence initiates
1235                 preloadTheseImages: [ //specify particular images to load before Sequence initiates
1236                         /* Example usage
1237                         "images/catEatingSalad.jpg",
1238                         "images/meDressedAsBatman.png"
1239                         */
1240                 ],
1241                 /*Note: You can use preloadTheseFrames and preloadTheseImages together. You might want to load all images in frame 1 and just one big image from frame 2 for example*/
1242                 hideFramesUntilPreloaded: true,
1243                 prependPreloadingComplete: true,
1244                 hidePreloaderUsingCSS: true,
1245                 hidePreloaderDelay: 0,
1246
1247                 //Keyboard settings
1248                 keyNavigation: true, //false prevents the following keyboard settings
1249                 numericKeysGoToFrames: true,
1250                 keyEvents: {
1251                         left: "prev",
1252                         right: "next"
1253                 },
1254                 customKeyEvents: {
1255                         /* Example usage
1256                         65: "prev",     //a
1257                         68: "next",     //d
1258                         83: "prev",     //s
1259                         87: "next"      //w
1260                         */
1261                 },
1262
1263                 //Touch Swipe Settings
1264                 swipeNavigation: true,
1265                 swipeThreshold: 20,
1266                 swipePreventsDefault: false, //be careful if setting this to true
1267                 swipeEvents: {
1268                         left: "prev",
1269                         right: "next",
1270                         up: false,
1271                         down: false
1272                 },
1273
1274                 //hashTags Settings
1275                 //when using hashTags, please include a reference to Ben Alman's jQuery HashChange plugin above your reference to Sequence.js
1276
1277                 //info: http://benalman.com/projects/jquery-hashchange-plugin/
1278                 //plugin: https://raw.github.com/cowboy/jquery-hashchange/v1.3/jquery.ba-hashchange.min.js
1279                 //GitHub: https://github.com/cowboy/jquery-hashchange
1280                 hashTags: false, //when a frame is navigated to, change the hashtag to the frames ID
1281                 hashDataAttribute: false, //false = the hashTag is taken from a frames ID attribute | true = the hashTag is taken from the data attribute "data-sequence-hash"
1282                 hashChangesOnFirstFrame: false, //false = the hashTag won't change for the first frame but will for those after
1283
1284                 //Fallback Theme Settings (For browsers that don't support CSS3 transitions)
1285                 fallback: {
1286                         theme: "slide",
1287                         speed: 500
1288                 }
1289         };
1290
1291         $.fn.sequence = function(options) {
1292                 return this.each(function() {
1293                         if (!$.data(this, 'sequence')) { //prevent multiple initiations on the same element
1294                                 $.data(this, 'sequence', new Sequence($(this), options, defaults));
1295       }
1296                 });
1297         };
1298 })(jQuery);