Blame | Last modification | View Log | RSS feed
/** Swipe 2.0** Brad Birdsall* Copyright 2013, MIT License**/function Swipe(container, options) {"use strict";// utilitiesvar noop = function() {}; // simple no operation functionvar offloadFn = function(fn) { setTimeout(fn || noop, 0) }; // offload a functions execution// check browser capabilitiesvar browser = {addEventListener: !!window.addEventListener,touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,transitions: (function(temp) {var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true;return false;})(document.createElement('swipe'))};// quit if no root elementif (!container) return;var element = container.children[0];var slides, slidePos, width, length;options = options || {};var index = parseInt(options.startSlide, 10) || 0;var speed = options.speed || 300;options.continuous = options.continuous !== undefined ? options.continuous : true;function setup() {// cache slidesslides = element.children;length = slides.length;// set continuous to false if only one slideif (slides.length < 2) options.continuous = false;//special case if two slidesif (browser.transitions && options.continuous && slides.length < 3) {element.appendChild(slides[0].cloneNode(true));element.appendChild(element.children[1].cloneNode(true));slides = element.children;}// create an array to store current positions of each slideslidePos = new Array(slides.length);// determine width of each slidewidth = container.getBoundingClientRect().width || container.offsetWidth;element.style.width = (slides.length * width) + 'px';// stack elementsvar pos = slides.length;while(pos--) {var slide = slides[pos];slide.style.width = width + 'px';slide.setAttribute('data-index', pos);if (browser.transitions) {slide.style.left = (pos * -width) + 'px';move(pos, index > pos ? -width : (index < pos ? width : 0), 0);}}// reposition elements before and after indexif (options.continuous && browser.transitions) {move(circle(index-1), -width, 0);move(circle(index+1), width, 0);}if (!browser.transitions) element.style.left = (index * -width) + 'px';container.style.visibility = 'visible';}function prev() {if (options.continuous) slide(index-1);else if (index) slide(index-1);}function next() {if (options.continuous) slide(index+1);else if (index < slides.length - 1) slide(index+1);}function circle(index) {// a simple positive modulo using slides.lengthreturn (slides.length + (index % slides.length)) % slides.length;}function slide(to, slideSpeed) {// do nothing if already on requested slideif (index == to) return;if (browser.transitions) {var direction = Math.abs(index-to) / (index-to); // 1: backward, -1: forward// get the actual position of the slideif (options.continuous) {var natural_direction = direction;direction = -slidePos[circle(to)] / width;// if going forward but to < index, use to = slides.length + to// if going backward but to > index, use to = -slides.length + toif (direction !== natural_direction) to = -direction * slides.length + to;}var diff = Math.abs(index-to) - 1;// move all the slides between index and to in the right directionwhile (diff--) move( circle((to > index ? to : index) - diff - 1), width * direction, 0);to = circle(to);move(index, width * direction, slideSpeed || speed);move(to, 0, slideSpeed || speed);if (options.continuous) move(circle(to - direction), -(width * direction), 0); // we need to get the next in place} else {to = circle(to);animate(index * -width, to * -width, slideSpeed || speed);//no fallback for a circular continuous if the browser does not accept transitions}index = to;offloadFn(options.callback && options.callback(index, slides[index]));}function move(index, dist, speed) {translate(index, dist, speed);slidePos[index] = dist;}function translate(index, dist, speed) {var slide = slides[index];var style = slide && slide.style;if (!style) return;style.webkitTransitionDuration =style.MozTransitionDuration =style.msTransitionDuration =style.OTransitionDuration =style.transitionDuration = speed + 'ms';style.webkitTransform = 'translate(' + dist + 'px,0)' + 'translateZ(0)';style.msTransform =style.MozTransform =style.OTransform = 'translateX(' + dist + 'px)';}function animate(from, to, speed) {// if not an animation, just repositionif (!speed) {element.style.left = to + 'px';return;}var start = +new Date;var timer = setInterval(function() {var timeElap = +new Date - start;if (timeElap > speed) {element.style.left = to + 'px';if (delay) begin();options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);clearInterval(timer);return;}element.style.left = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px';}, 4);}// setup auto slideshowvar delay = options.auto || 0;var interval;function begin() {interval = setTimeout(next, delay);}function stop() {delay = 0;clearTimeout(interval);}// setup initial varsvar start = {};var delta = {};var isScrolling;// setup event capturingvar events = {handleEvent: function(event) {switch (event.type) {case 'touchstart': this.start(event); break;case 'touchmove': this.move(event); break;case 'touchend': offloadFn(this.end(event)); break;case 'webkitTransitionEnd':case 'msTransitionEnd':case 'oTransitionEnd':case 'otransitionend':case 'transitionend': offloadFn(this.transitionEnd(event)); break;case 'resize': offloadFn(setup.call()); break;}if (options.stopPropagation) event.stopPropagation();},start: function(event) {var touches = event.touches[0];// measure start valuesstart = {// get initial touch coordsx: touches.pageX,y: touches.pageY,// store time to determine touch durationtime: +new Date};// used for testing first move eventisScrolling = undefined;// reset delta and end measurementsdelta = {};// attach touchmove and touchend listenerselement.addEventListener('touchmove', this, false);element.addEventListener('touchend', this, false);},move: function(event) {// ensure swiping with one touch and not pinchingif ( event.touches.length > 1 || event.scale && event.scale !== 1) returnif (options.disableScroll) event.preventDefault();var touches = event.touches[0];// measure change in x and ydelta = {x: touches.pageX - start.x,y: touches.pageY - start.y}// determine if scrolling test has run - one time testif ( typeof isScrolling == 'undefined') {isScrolling = !!( isScrolling || Math.abs(delta.x) < Math.abs(delta.y) );}// if user is not trying to scroll verticallyif (!isScrolling) {// prevent native scrollingevent.preventDefault();// stop slideshowstop();// increase resistance if first or last slideif (options.continuous) { // we don't add resistance at the endtranslate(circle(index-1), delta.x + slidePos[circle(index-1)], 0);translate(index, delta.x + slidePos[index], 0);translate(circle(index+1), delta.x + slidePos[circle(index+1)], 0);} else {delta.x =delta.x /( (!index && delta.x > 0 // if first slide and sliding left|| index == slides.length - 1 // or if last slide and sliding right&& delta.x < 0 // and if sliding at all) ?( Math.abs(delta.x) / width + 1 ) // determine resistance level: 1 ); // no resistance if false// translate 1:1translate(index-1, delta.x + slidePos[index-1], 0);translate(index, delta.x + slidePos[index], 0);translate(index+1, delta.x + slidePos[index+1], 0);}}},end: function(event) {// measure durationvar duration = +new Date - start.time;// determine if slide attempt triggers next/prev slidevar isValidSlide =Number(duration) < 250 // if slide duration is less than 250ms&& Math.abs(delta.x) > 20 // and if slide amt is greater than 20px|| Math.abs(delta.x) > width/2; // or if slide amt is greater than half the width// determine if slide attempt is past start and endvar isPastBounds =!index && delta.x > 0 // if first slide and slide amt is greater than 0|| index == slides.length - 1 && delta.x < 0; // or if last slide and slide amt is less than 0if (options.continuous) isPastBounds = false;// determine direction of swipe (true:right, false:left)var direction = delta.x < 0;// if not scrolling verticallyif (!isScrolling) {if (isValidSlide && !isPastBounds) {if (direction) {if (options.continuous) { // we need to get the next in this direction in placemove(circle(index-1), -width, 0);move(circle(index+2), width, 0);} else {move(index-1, -width, 0);}move(index, slidePos[index]-width, speed);move(circle(index+1), slidePos[circle(index+1)]-width, speed);index = circle(index+1);} else {if (options.continuous) { // we need to get the next in this direction in placemove(circle(index+1), width, 0);move(circle(index-2), -width, 0);} else {move(index+1, width, 0);}move(index, slidePos[index]+width, speed);move(circle(index-1), slidePos[circle(index-1)]+width, speed);index = circle(index-1);}options.callback && options.callback(index, slides[index]);} else {if (options.continuous) {move(circle(index-1), -width, speed);move(index, 0, speed);move(circle(index+1), width, speed);} else {move(index-1, -width, speed);move(index, 0, speed);move(index+1, width, speed);}}}// kill touchmove and touchend event listeners until touchstart called againelement.removeEventListener('touchmove', events, false)element.removeEventListener('touchend', events, false)},transitionEnd: function(event) {if (parseInt(event.target.getAttribute('data-index'), 10) == index) {if (delay) begin();options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);}}}// trigger setupsetup();// start auto slideshow if applicableif (delay) begin();// add event listenersif (browser.addEventListener) {// set touchstart event on elementif (browser.touch) element.addEventListener('touchstart', events, false);if (browser.transitions) {element.addEventListener('webkitTransitionEnd', events, false);element.addEventListener('msTransitionEnd', events, false);element.addEventListener('oTransitionEnd', events, false);element.addEventListener('otransitionend', events, false);element.addEventListener('transitionend', events, false);}// set resize event on windowwindow.addEventListener('resize', events, false);} else {window.onresize = function () { setup() }; // to play nice with old IE}// expose the Swipe APIreturn {setup: function() {setup();},slide: function(to, speed) {// cancel slideshowstop();slide(to, speed);},prev: function() {// cancel slideshowstop();prev();},next: function() {// cancel slideshowstop();next();},getPos: function() {// return current index positionreturn index;},getNumSlides: function() {// return total number of slidesreturn length;},kill: function() {// cancel slideshowstop();// reset elementelement.style.width = 'auto';element.style.left = 0;// reset slidesvar pos = slides.length;while(pos--) {var slide = slides[pos];slide.style.width = '100%';slide.style.left = 0;if (browser.transitions) translate(pos, 0, 0);}// removed event listenersif (browser.addEventListener) {// remove current event listenerselement.removeEventListener('touchstart', events, false);element.removeEventListener('webkitTransitionEnd', events, false);element.removeEventListener('msTransitionEnd', events, false);element.removeEventListener('oTransitionEnd', events, false);element.removeEventListener('otransitionend', events, false);element.removeEventListener('transitionend', events, false);window.removeEventListener('resize', events, false);}else {window.onresize = null;}}}}if ( window.jQuery || window.Zepto ) {(function($) {$.fn.Swipe = function(params) {return this.each(function() {$(this).data('Swipe', new Swipe($(this)[0], params));});}})( window.jQuery || window.Zepto )}