/* * glide.js * ver: 1.0.4 * simple & efficient jquery slider * autor: @jedrzejchalubek * url: http://jedrzejchalubek.com * licensed under the mit license */ ;(function ($, window, document, undefined) { var name = 'glide', defaults = { // {int or bool} false for turning off autoplay autoplay: 4000, /** * animation time * !!! important !!! * that option will be use only, when css3 are not suported * if css3 are supported animation time is set in css declaration inside .css file * @type {int} */ animationtime: 500, /** * {bool or string} show/hide/appendto arrows * true for append arrows to slider wrapper * false for not appending arrows * id or class name (e.g. '.class-name') for appending to specific html markup */ arrows: true, // {string} arrows wrapper class arrowswrapperclass: 'slider-arrows', // {string} main class for both arrows arrowmainclass: 'slider-arrow', // {string} right arrow arrowrightclass: 'slider-arrow--right', // {string} right arrow text arrowrighttext: 'next', // {string} left arrow arrowleftclass: 'slider-arrow--left', // {string} left arrow text arrowlefttext: 'prev', /** * {bool or string} show/hide/appendto bullets navigation * true for append arrows to slider wrapper * false for not appending arrows * id or class name (e.g. '.class-name') for appending to specific html markup */ nav: true, // {bool} center bullet navigation navcenter: true, // {string} navigation class navclass: 'slider-nav', // {string} navigation item class navitemclass: 'slider-nav__item', // {string} current navigation item class navcurrentitemclass: 'slider-nav__item--current', // {int or bool} touch settings touchdistance: 60 }; /** * slider constructor * @param {object} parent * @param {object} options */ function glide(parent, options) { var _ = this; _.options = $.extend({}, defaults, options); // sidebar _.parent = parent; // slides wrapper _.wrapper = _.parent.children(); // slides _.slides = _.wrapper.children(); // current slide id _.currentslide = 0; // css3 animation support _.css3support = true; // initialize _.init(); // build dom _.build(); // start autoplay _.play(); /** * controller * touch events */ if (_.options.touchdistance) { // init swipe _.swipe(); } /** * controller * keyboard left and right arrow keys */ $(document).on('keyup', function(k) { // next if (k.keycode === 39) _.slide(1); // prev if (k.keycode === 37) _.slide(-1); }); /** * controller * mouse over slider * when mouse is over slider, pause autoplay * on out, start autoplay again */ _.parent.add(_.arrows).add(_.nav).on('mouseover mouseout', function (e) { // pasue autoplay _.pause(); // when mouse left slider or touch end, start autoplay anew if (e.type === 'mouseout') _.play(); }); /** * controller * when resize browser window * pause autoplay in fear of escalation * reinit plugin for new slider dimensions * correct crop to current slide * start autoplay from beginning */ $(window).on('resize', function() { // reinit plugin (set new slider dimensions) _.init(); // crop to current slide _.slide(0); }); /** * returning api */ return { current: function() { return -(_.currentslide) + 1; }, play: function() { _.play(); }, pause: function() { _.pause(); }, next: function(callback) { _.slide(1, false, callback); }, prev: function(callback) { _.slide(-1, false, callback); }, jump: function(distance, callback) { _.slide(distance-1, true, callback); }, nav: function(target) { /** * if navigation wrapper already exist * remove it, protection before doubled navigation */ if (_.navwrapper) { _.navwrapper.remove(); } _.options.nav = (target) ? target : _.options.nav; // build _.navigation(); }, arrows: function(target) { /** * if arrows wrapper already exist * remove it, protection before doubled arrows */ if (_.arrowswrapper) { _.arrowswrapper.remove(); } _.options.arrows = (target) ? target : _.options.arrows; // build _.arrows(); } }; } /** * building slider dom */ glide.prototype.build = function() { var _ = this; /** * arrows * if option is true and there is more than one slide * append left and right arrow */ if (_.options.arrows) _.arrows(); /** * navigation * if option is true and there is more than one slide * append navigation item for each slide */ if (_.options.nav) _.navigation(); }; /** * building navigation dom */ glide.prototype.navigation = function() { var _ = this; if (_.slides.length > 1) { // cache var o = _.options, /** * setting append target * if option is true set default target, that is slider wrapper * else get target set in options * @type {bool or string} */ target = (_.options.nav === true) ? _.parent : _.options.nav; // navigation wrapper _.navwrapper = $('
', { 'class': o.navclass }).appendto(target); // cache var nav = _.navwrapper, item; // generate navigation items for (var i = 0; i < _.slides.length; i++) { item = $('', { 'href': '#', 'class': o.navitemclass, // direction and distance -> item index forward 'data-distance': i }).appendto(nav); nav[i+1] = item; } // cache var navchildren = nav.children(); // add navcurrentitemclass to the first navigation item navchildren.eq(0).addclass(o.navcurrentitemclass); // if centered option is true if (o.navcenter) { // center bullet navigation nav.css({ 'left': '50%', 'width': navchildren.outerwidth(true) * navchildren.length, 'margin-left': -nav.outerwidth(true)/2 }); } /** * controller * on click in arrows or navigation, get direction and distance * then slide specified distance */ navchildren.on('click touchstart', function(e) { // prevent normal behaviour e.preventdefault(); // slide distance specified in data attribute _.slide( $(this).data('distance'), true ); }); } }; /** * building arrows dom */ glide.prototype.arrows = function() { var _ = this; if (_.slides.length > 1) { var o = _.options, /** * setting append target * if option is true set default target, that is slider wrapper * else get target set in options * @type {bool or string} */ target = (_.options.arrows === true) ? _.parent : _.options.arrows; // arrows wrapper _.arrowswrapper = $('
', { 'class': o.arrowswrapperclass }).appendto(target); // cache var arrows = _.arrowswrapper; // right arrow arrows.right = $('', { 'href': '#', 'class': o.arrowmainclass + ' ' + o.arrowrightclass, // direction and distance -> one forward 'data-distance': '1', 'html': o.arrowrighttext }).appendto(arrows); // left arrow arrows.left = $('', { 'href': '#', 'class': o.arrowmainclass + ' ' + o.arrowleftclass, // direction and distance -> one backward 'data-distance': '-1', 'html': o.arrowlefttext }).appendto(arrows); /** * controller * on click in arrows or navigation, get direction and distance * then slide specified distance */ arrows.children().on('click touchstart', function(e) { // prevent normal behaviour e.preventdefault(); // slide distance specified in data attribute _.slide( $(this).data('distance'), false ); }); } }; /** * slides change & animate logic * @param {int} distance * @param {bool} jump * @param {function} callback */ glide.prototype.slide = function(distance, jump, callback) { // cache elements var _ = this, currentslide = (jump) ? 0 : _.currentslide, slideslength = -(_.slides.length-1), navcurrentclass = _.options.navcurrentitemclass, slidesspread = _.slides.spread; /** * stop autoplay * clearing timer */ _.pause(); /** * check if current slide is first and direction is previous, then go to last slide * or current slide is last and direction is next, then go to the first slide * else change current slide normally */ if ( currentslide === 0 && distance === -1 ) { currentslide = slideslength; } else if ( currentslide === slideslength && distance === 1 ) { currentslide = 0; } else { currentslide = currentslide + (-distance); } /** * crop to current slide. * mul slide width by current slide number. */ var translate = slidesspread * currentslide + 'px'; // while css3 is supported if (_.css3support) { // croping by increasing/decreasing slider wrapper translate _.wrapper.css({ '-webkit-transform': 'translate3d('+ translate +', 0px, 0px)', '-moz-transform': 'translate3d('+ translate +', 0px, 0px)', '-ms-transform': 'translate3d('+ translate +', 0px, 0px)', '-o-transform': 'translate3d('+ translate +', 0px, 0px)', 'transform': 'translate3d('+ translate +', 0px, 0px)' }); // else use $.animate() } else { // croping by increasing/decreasing slider wrapper margin _.wrapper.stop().animate({ 'margin-left': translate }, _.options.animationtime); } // set to navigation item current class if (_.options.nav) { _.navwrapper.children() .eq(-currentslide) .addclass(navcurrentclass) .siblings() .removeclass(navcurrentclass); } // update current slide globaly _.currentslide = currentslide; // callback if ( (callback !== 'undefined') && (typeof callback === 'function') ) callback(); /** * start autoplay * after slide */ _.play(); }; /** * autoplay logic * setup counting */ glide.prototype.play = function() { var _ = this; if (_.options.autoplay) { _.auto = setinterval(function() { _.slide(1, false); }, _.options.autoplay); } }; /** * autoplay pause * clear counting */ glide.prototype.pause = function() { if (this.options.autoplay) { this.auto = clearinterval(this.auto); } }; /** * change sildes on swipe event */ glide.prototype.swipe = function() { // cache var _ = this, touch, touchdistance, touchstartx, touchstarty, touchendx, touchendy, touchhypotenuse, touchcathetus, touchsin, mathpi = 180 / math.pi, subexsx, subeysy, powex, powey; /** * touch start * @param {object} e event */ _.parent.on('touchstart', function(e) { // cache event touch = e.originalevent.touches[0] || e.originalevent.changedtouches[0]; // get touch start points touchstartx = touch.pagex; touchstarty = touch.pagey; }); /** * touch move * from swipe length segments calculate swipe angle * @param {obejct} e event */ _.parent.on('touchmove', function(e) { // cache event touch = e.originalevent.touches[0] || e.originalevent.changedtouches[0]; // get touch end points touchendx = touch.pagex; touchendy = touch.pagey; // calculate start, end points subexsx = touchendx - touchstartx; subeysy = touchendy - touchstarty; // bitwise subexsx pow powex = math.abs( subexsx << 2 ); // bitwise subeysy pow powey = math.abs( subeysy << 2 ); // calculate the length of the hypotenuse segment touchhypotenuse = math.sqrt( powex + powey ); // calculate the length of the cathetus segment touchcathetus = math.sqrt( powey ); // calculate the sine of the angle touchsin = math.asin( touchcathetus/touchhypotenuse ); // while touch angle is lower than 32 degrees, block vertical scroll if( (touchsin * mathpi) < 32 ) e.preventdefault(); }); /** * touch end * @param {object} e event */ _.parent.on('touchend', function(e) { // cache event touch = e.originalevent.touches[0] || e.originalevent.changedtouches[0]; // calculate touch distance touchdistance = touch.pagex - touchstartx; // while touch is positive and greater than distance set in options if ( touchdistance > _.options.touchdistance ) { // slide one backward _.slide(-1); // while touch is negative and lower than negative distance set in options } else if ( touchdistance < -_.options.touchdistance) { // slide one forward _.slide(1); } }); }; /** * initialize * get & set dimensions * set animation type */ glide.prototype.init = function() { var _ = this, // get sidebar width sliderwidth = _.parent.width(); // get slide width _.slides.spread = sliderwidth; // set wrapper width _.wrapper.width(sliderwidth * _.slides.length); // set slide width _.slides.width(_.slides.spread); // if css3 transition isn't supported switch css3support variable to false and use $.animate() if ( !iscsssupported("transition") || !iscsssupported("transform") ) _.css3support = false; }; /** * function to check css3 support * @param {string} declaration name * @return {boolean} */ function iscsssupported(declaration) { var supported = false, prefixes = 'khtml ms o moz webkit'.split(' '), clone = document.createelement('div'), declarationcapital = null; declaration = declaration.tolowercase(); if (clone.style[declaration] !== undefined) supported = true; if (supported === false) { declarationcapital = declaration.charat(0).touppercase() + declaration.substr(1); for( var i = 0; i < prefixes.length; i++ ) { if( clone.style[prefixes[i] + declarationcapital ] !== undefined ) { supported = true; break; } } } if (window.opera) { if (window.opera.version() < 13) supported = false; } return supported; } $.fn[name] = function (options) { return this.each(function () { if ( !$.data(this, 'api_' + name) ) { $.data(this, 'api_' + name, new glide($(this), options) ); } }); }; })(jquery, window, document);