/*! * Peppermint touch slider * v. 1.3.7 | https://github.com/wilddeer/Peppermint * Copyright Oleg Korsunsky | http://wd.dizaina.net/ * * Depends on Event Burrito (included) | https://github.com/wilddeer/Event-Burrito * MIT License */ function Peppermint(_this, options) { var slider = { slides: [], dots: [], left: 0 }, slidesNumber, flickThreshold = 200, //Flick threshold (ms) activeSlide = 0, slideWidth, dotBlock, slideBlock, slideshowTimeoutId, slideshowActive, animationTimer; //default options var o = { speed: 300, //transition between slides in ms touchSpeed: 300, //transition between slides in ms after touch slideshow: false, //launch slideshow at start slideshowInterval: 4000, stopSlideshowAfterInteraction: false, //stop slideshow after user interaction startSlide: 0, //first slide to show mouseDrag: true, //enable mouse drag disableIfOneSlide: true, cssPrefix: 'peppermint-', dots: false, //show dots dotsPrepend: false, //dots before slides dotsContainer: undefined, slidesContainer: undefined, onSlideChange: undefined, //slide change callback onSetup: undefined //setup callback }; //merge user options into defaults options && mergeObjects(o, options); var classes = { inactive: o.cssPrefix + 'inactive', active: o.cssPrefix + 'active', mouse: o.cssPrefix + 'mouse', drag: o.cssPrefix + 'drag', slides: o.cssPrefix + 'slides', dots: o.cssPrefix + 'dots', activeDot: o.cssPrefix + 'active-dot', mouseClicked: o.cssPrefix + 'mouse-clicked' }; //feature detects var support = { transforms: testProp('transform'), transitions: testProp('transition') }; function mergeObjects(targetObj, sourceObject) { for (var key in sourceObject) { if (sourceObject.hasOwnProperty(key)) { targetObj[key] = sourceObject[key]; } } } function testProp(prop) { var prefixes = ['Webkit', 'Moz', 'O', 'ms'], block = document.createElement('div'); if (block.style[prop] !== undefined) return true; prop = prop.charAt(0).toUpperCase() + prop.slice(1); for (var i in prefixes) { if (block.style[prefixes[i]+prop] !== undefined) return true; } return false; } function addClass(el, cl) { if (!new RegExp('(\\s|^)'+cl+'(\\s|$)').test(el.className)) { el.className += ' ' + cl; } } function removeClass(el, cl) { el.className = el.className.replace(new RegExp('(\\s+|^)'+cl+'(\\s+|$)', 'g'), ' ').replace(/^\s+|\s+$/g, ''); } //n - slide number (starting from 0) //speed - transition in ms, can be omitted function changeActiveSlide(n, speed) { if (n<0) { n = 0; } else if (n>slidesNumber-1) { n = slidesNumber-1; } //change active dot for (var i = slider.dots.length - 1; i >= 0; i--) { removeClass(slider.dots[i], classes.activeDot); } addClass(slider.dots[n], classes.activeDot); activeSlide = n; changePos(-n*slider.width, (speed===undefined?o.speed:speed)); //reset slideshow timeout whenever active slide is changed for whatever reason stepSlideshow(); //API callback o.onSlideChange && o.onSlideChange(n); return n; } //changes position of the slider (in px) with given speed (in ms) function changePos(pos, speed) { var time = speed?speed+'ms':''; slideBlock.style.webkitTransitionDuration = slideBlock.style.MozTransitionDuration = slideBlock.style.msTransitionDuration = slideBlock.style.OTransitionDuration = slideBlock.style.transitionDuration = time; setPos(pos); } //fallback to `setInterval` animations for UAs with no CSS transitions function changePosFallback(pos, speed) { animationTimer && clearInterval(animationTimer); if (!speed) { setPos(pos); return; } var startTime = +new Date, startPos = slider.left; animationTimer = setInterval(function() { //rough bezier emulation var diff, y, elapsed = +new Date - startTime, f = elapsed / speed, bezier = [0, 0.7, 1, 1]; function getPoint(p1, p2) { return (p2-p1)*f + p1; } if (f >= 1) { setPos(pos); clearInterval(animationTimer); return; } diff = pos - startPos; y = getPoint( getPoint(getPoint(bezier[0], bezier[1]), getPoint(bezier[1], bezier[2])), getPoint(getPoint(bezier[1], bezier[2]), getPoint(bezier[2], bezier[3])) ); setPos(Math.floor(y*diff + startPos)); }, 15); } //sets position of the slider (in px) function setPos(pos) { slideBlock.style.webkitTransform = 'translate('+pos+'px,0) translateZ(0)'; slideBlock.style.msTransform = slideBlock.style.MozTransform = slideBlock.style.OTransform = slideBlock.style.transform = 'translateX('+pos+'px)'; slider.left = pos; } //`setPos` fallback for UAs with no CSS transforms support function setPosFallback(pos) { slideBlock.style.left = pos+'px'; slider.left = pos; } function nextSlide() { var n = activeSlide + 1; if (n > slidesNumber - 1) { n = 0; } return changeActiveSlide(n); } function prevSlide() { var n = activeSlide - 1; if (n < 0) { n = slidesNumber - 1; } return changeActiveSlide(n); } function startSlideshow() { slideshowActive = true; stepSlideshow(); } //sets or resets timeout before switching to the next slide function stepSlideshow() { if (slideshowActive) { slideshowTimeoutId && clearTimeout(slideshowTimeoutId); slideshowTimeoutId = setTimeout(function() { nextSlide(); }, o.slideshowInterval); } } //pauses slideshow until `stepSlideshow` is invoked function pauseSlideshow() { slideshowTimeoutId && clearTimeout(slideshowTimeoutId); } function stopSlideshow() { slideshowActive = false; slideshowTimeoutId && clearTimeout(slideshowTimeoutId); } //this should be invoked when the width of the slider is changed function onWidthChange() { slider.width = _this.offsetWidth; //have to do this in `px` because of webkit's rounding errors :-( slideBlock.style.width = slider.width*slidesNumber+'px'; for (var i = 0; i < slidesNumber; i++) { slider.slides[i].style.width = slider.width+'px'; } changePos(-activeSlide*slider.width); } function addEvent(el, event, func, bool) { if (!event) return; el.addEventListener? el.addEventListener(event, func, !!bool): el.attachEvent('on'+event, func); } //init touch events function touchInit() { EventBurrito(slideBlock, { mouse: o.mouseDrag, start: function(event, start) { //firefox doesn't want to apply cursor from `:active` CSS rule, have to add a class :-/ addClass(_this, classes.drag); }, move: function(event, start, diff, speed) { pauseSlideshow(); //pause the slideshow when touch is in progress //if it's first slide and moving left or last slide and moving right -- resist! diff.x = diff.x / ( (!activeSlide && diff.x > 0 || activeSlide == slidesNumber - 1 && diff.x < 0) ? (Math.abs(diff.x)/slider.width*2 + 1) : 1 ); //change position of the slider appropriately changePos(diff.x - slider.width*activeSlide); }, end: function(event, start, diff, speed) { if (diff.x) { var ratio = Math.abs(diff.x)/slider.width, //How many slides to skip. Remainder > 0.25 counts for one slide. skip = Math.floor(ratio) + (ratio - Math.floor(ratio) > 0.25?1:0), //Super-duper formula to detect a flick. //First, it's got to be fast enough. //Second, if `skip==0`, 20px move is enough to switch to the next slide. //If `skip>0`, it's enough to slide to the middle of the slide minus `slider.width/9` to skip even further. flick = diff.time < flickThreshold+flickThreshold*skip/1.8 && Math.abs(diff.x) - skip*slider.width > (skip?-slider.width/9:20); skip += (flick?1:0); if (diff.x < 0) { changeActiveSlide(activeSlide+skip, o.touchSpeed); } else { changeActiveSlide(activeSlide-skip, o.touchSpeed); } o.stopSlideshowAfterInteraction && stopSlideshow(); } //remove the drag class removeClass(_this, classes.drag); } }); } function setup() { var slideSource = o.slidesContainer || _this, dotsTarget = o.dotsContainer || _this; if (o.disableIfOneSlide && slideSource.children.length <= 1) return; //If current UA doesn't support css transforms or transitions -- use fallback functions. //(Using separate functions instead of checks for better performance) if (!support.transforms || !!window.opera) setPos = setPosFallback; if (!support.transitions || !!window.opera) changePos = changePosFallback; slideBlock = o.slidesContainer || document.createElement('div'); addClass(slideBlock, classes.slides); //get slides & generate dots for (var i = 0, l = slideSource.children.length; i < l; i++) { var slide = slideSource.children[i], dot = document.createElement('li'); slider.slides.push(slide); //`tabindex` makes dots tabbable dot.setAttribute('tabindex', '0'); dot.setAttribute('role', 'button'); dot.innerHTML = ''; (function(x, dotClosure) { //bind events to dots addEvent(dotClosure, 'click', function(event) { changeActiveSlide(x); o.stopSlideshowAfterInteraction && stopSlideshow(); }); //Bind the same function to Enter key except for the `blur` part -- I dont't want //the focus to be lost when the user is using his keyboard to navigate. addEvent(dotClosure, 'keyup', function(event) { if (event.keyCode == 13) { changeActiveSlide(x); o.stopSlideshowAfterInteraction && stopSlideshow(); } }); //Don't want to disable outlines completely for accessibility reasons. //Instead, add class with `outline: 0` on mouseup and remove it on blur. addEvent(dotClosure, 'mouseup', function(event) { addClass(dotClosure, classes.mouseClicked); }); //capturing fixes IE bug addEvent(dotClosure, 'blur', function(){ removeClass(dotClosure, classes.mouseClicked); }, true); //This solves tabbing problems: //When an element inside a slide catches focus we switch to that slide //and reset `scrollLeft` of the slider block. //`SetTimeout` solves Chrome's bug. //Event capturing is used to catch events on the slide level. //Since older IEs don't have capturing, `onfocusin` is used as a fallback. addEvent(slide, 'focus', slide.onfocusin = function(e) { _this.scrollLeft = 0; setTimeout(function() { _this.scrollLeft = 0; }, 0); changeActiveSlide(x); }, true); })(i, dot) slider.dots.push(dot); } slidesNumber = slider.slides.length; slideWidth = 100/slidesNumber; addClass(_this, classes.active); removeClass(_this, classes.inactive); o.mouseDrag && addClass(_this, classes.mouse); slider.width = _this.offsetWidth; //had to do this in `px` because of webkit's rounding errors :-( slideBlock.style.width = slider.width*slidesNumber+'px'; for (var i = 0; i < slidesNumber; i++) { slider.slides[i].style.width = slider.width+'px'; slideBlock.appendChild(slider.slides[i]); } if (!o.slidesContainer) _this.appendChild(slideBlock); //append dots if (o.dots && slidesNumber > 1) { dotBlock = document.createElement('ul'); addClass(dotBlock, classes.dots); for (var i = 0, l = slider.dots.length; i < l; i++) { dotBlock.appendChild(slider.dots[i]); } if (o.dotsPrepend) { dotsTarget.insertBefore(dotBlock,dotsTarget.firstChild); } else { dotsTarget.appendChild(dotBlock); } } //watch for slider width changes addEvent(window, 'resize', onWidthChange); addEvent(window, 'orientationchange', onWidthChange); //init first slide, timeout to expose the API first setTimeout(function() { changeActiveSlide(o.startSlide, 0); }, 0); //init slideshow if (o.slideshow) startSlideshow(); touchInit(); //API callback, timeout to expose the API first setTimeout(function() { o.onSetup && o.onSetup(slidesNumber); }, 0); } //Init setup(); //expose the API return { slideTo: function(slide) { return changeActiveSlide(parseInt(slide, 10)); }, next: nextSlide, prev: prevSlide, //start slideshow start: startSlideshow, //stop slideshow stop: stopSlideshow, //pause slideshow until the next slide change pause: pauseSlideshow, //get current slide number getCurrentPos: function() { return activeSlide; }, //get total number of slides getSlidesNumber: function() { return slidesNumber; }, //invoke this when the slider's width is changed recalcWidth: onWidthChange }; }; //if jQuery is present -- create a plugin if (window.jQuery) { (function($) { $.fn.Peppermint = function(options) { this.each(function() { $(this).data('Peppermint', Peppermint(this, options)); }); return this; }; })(window.jQuery); } /*! * Event Burrito is a touch / mouse / pointer event unifier * https://github.com/wilddeer/Event-Burrito * Copyright Oleg Korsunsky | http://wd.dizaina.net/ * * MIT License */ function EventBurrito(_this, options) { var noop = function() {}, o = { preventDefault: true, clickTolerance: 0, preventScroll: false, mouse: true, start: noop, move: noop, end: noop, click: noop }; //merge user options into defaults options && mergeObjects(o, options); var support = { pointerEvents: !!window.navigator.pointerEnabled, msPointerEvents: !!window.navigator.msPointerEnabled }, start = {}, diff = {}, speed = {}, stack = [], listeners = [], isScrolling, eventType, clicksAllowed = true, //flag allowing default click actions (e.g. links) eventModel = (support.pointerEvents? 1 : (support.msPointerEvents? 2 : 0)), events = [ ['touchstart', 'touchmove', 'touchend', 'touchcancel'], //touch events ['pointerdown', 'pointermove', 'pointerup', 'pointercancel'], //pointer events ['MSPointerDown', 'MSPointerMove', 'MSPointerUp', 'MSPointerCancel'], //IE10 pointer events ['mousedown', 'mousemove', 'mouseup', false] //mouse events ], //some checks for different event types checks = [ //touch events function(e) { //skip the event if it's multitouch or pinch move return (e.touches && e.touches.length > 1) || (e.scale && e.scale !== 1); }, //pointer events function(e) { //Skip it, if: //1. event is not primary (other pointers during multitouch), //2. left mouse button is not pressed, //3. mouse drag is disabled and event is not touch return !e.isPrimary || (e.buttons && e.buttons !== 1) || (!o.mouse && e.pointerType !== 'touch' && e.pointerType !== 'pen'); }, //IE10 pointer events function(e) { //same checks as in pointer events return !e.isPrimary || (e.buttons && e.buttons !== 1) || (!o.mouse && e.pointerType !== e.MSPOINTER_TYPE_TOUCH && e.pointerType !== e.MSPOINTER_TYPE_PEN); }, //mouse events function(e) { //skip the event if left mouse button is not pressed //in IE7-8 `buttons` is not defined, in IE9 LMB is 0 return (e.buttons && e.buttons !== 1); } ]; function mergeObjects(targetObj, sourceObject) { for (var key in sourceObject) { if (sourceObject.hasOwnProperty(key)) { targetObj[key] = sourceObject[key]; } } } function addEvent(el, event, func, bool) { if (!event) return; el.addEventListener? el.addEventListener(event, func, !!bool): el.attachEvent('on'+event, func); //return event remover to easily remove anonymous functions later return { remove: function() { removeEvent(el, event, func, bool); } }; } function removeEvent(el, event, func, bool) { if (!event) return; el.removeEventListener? el.removeEventListener(event, func, !!bool): el.detachEvent('on'+event, func); } function preventDefault(event) { event.preventDefault? event.preventDefault() : event.returnValue = false; } function getDiff(event) { diff = { x: (eventType? event.clientX : event.touches[0].clientX) - start.x, y: (eventType? event.clientY : event.touches[0].clientY) - start.y, time: Number(new Date) - start.time }; if (diff.time - stack[stack.length - 1].time) { for (var i = 0; i < stack.length - 1 && diff.time - stack[i].time > 80; i++); speed = { x: (diff.x - stack[i].x) / (diff.time - stack[i].time), y: (diff.y - stack[i].y) / (diff.time - stack[i].time) }; if (stack.length >= 5) stack.shift(); stack.push({x: diff.x, y: diff.y, time: diff.time}); } } function tStart(event, eType) { clicksAllowed = true; eventType = eType; //leak event type if (checks[eventType](event)) return; //attach event listeners to the document, so that the slider //will continue to recieve events wherever the pointer is addEvent(document, events[eventType][1], tMove); addEvent(document, events[eventType][2], tEnd); addEvent(document, events[eventType][3], tEnd); //fixes WebKit's cursor while dragging if (o.preventDefault && eventType) preventDefault(event); //remember starting time and position start = { x: eventType? event.clientX : event.touches[0].clientX, y: eventType? event.clientY : event.touches[0].clientY, time: Number(new Date) }; //reset isScrolling = undefined; diff = {x:0, y:0, time: 0}; speed = {x:0, y:0}; stack = [{x:0, y:0, time: 0}]; o.start(event, start); } function tMove(event) { //if user is trying to scroll vertically -- do nothing if ((!o.preventScroll && isScrolling) || checks[eventType](event)) return; getDiff(event); if (Math.abs(diff.x) > o.clickTolerance || Math.abs(diff.y) > o.clickTolerance) clicksAllowed = false; //if there was a move -- deny all the clicks before the next touchstart //check whether the user is trying to scroll vertically if (isScrolling === undefined && eventType !== 3) { //assign and check `isScrolling` at the same time if (isScrolling = (Math.abs(diff.x) < Math.abs(diff.y)) && !o.preventScroll) return; } if (o.preventDefault) preventDefault(event); //Prevent scrolling o.move(event, start, diff, speed); } function tEnd(event) { eventType && getDiff(event); //IE likes to focus links after touchend. //Since we don't want to disable link outlines completely for accessibility reasons, //we just defocus it after touch and disable the outline for `:active` links in css. //This way the outline will remain visible when using keyboard. !clicksAllowed && event.target && event.target.blur && event.target.blur(); //detach event listeners from the document removeEvent(document, events[eventType][1], tMove); removeEvent(document, events[eventType][2], tEnd); removeEvent(document, events[eventType][3], tEnd); o.end(event, start, diff, speed); } function init() { //bind touchstart listeners.push(addEvent(_this, events[eventModel][0], function(e) {tStart(e, eventModel);})); //prevent stuff from dragging when using mouse listeners.push(addEvent(_this, 'dragstart', preventDefault)); //bind mousedown if necessary if (o.mouse && !eventModel) { listeners.push(addEvent(_this, events[3][0], function(e) {tStart(e, 3);})); } //No clicking during touch listeners.push(addEvent(_this, 'click', function(event) { clicksAllowed? o.click(event): preventDefault(event); })); } init(); //expose the API return { getClicksAllowed: function() { return clicksAllowed; }, kill: function() { for (var i = listeners.length - 1; i >= 0; i--) { listeners[i].remove(); } } } }