/*! * excolo slider - a simple jquery slider * * examples and documentation at: * http://excolo.github.io/excolo-slider/ * * author: nikolaj dam larsen * version: 1.0.5 (30-june-2013) * * released under the mit license * https://github.com/excolo/excoloslider/blob/master/mit-license */ ; (function ($, window, document, undefined) { var plugin; /* plugin definition **************************************************************/ plugin = (function () { function plugin(elem, options) { this.elem = elem; this.$elem = $(elem); this.options = options; // this next line takes advantage of html5 data attributes // to support customization of the plugin on a per-element // basis. this.metadata = this.$elem.data('plugin-options'); } return plugin; })(); /* plugin prototype **************************************************************/ plugin.prototype = { /* default configuration **********************************************************/ defaults: { width: 800, height: 530, autosize: true, touchnav: true, mousenav: true, prevnextnav: true, prevnextautohide: true, pagernav: true, startslide: 1, autoplay: true, delay: 0, interval: 3000, repeat: true, playreverse: false, hoverpause: true, captionautohide: false, animationcsstransitions: true, animationduration: 500, animationtimingfunction: "linear", prevbuttonclass: "slide-prev", nextbuttonclass: "slide-next", prevbuttonimage: "img/left.jpg", nextbuttonimage: "img/right.jpg", activeslideclass: "es-active", slidecaptionclass: "es-caption", pagerclass: "es-pager", pagerimage: "img/pagericon.png" }, /* initialization function **********************************************************/ init: function () { var base, maxheight, $prev, $next, $buttons, $innerbase, caption, $wrapper, $children, $container; // defined variable to avoid scope problems base = this; // introduce defaults that can be extended either globally or using an object literal. base.config = $.extend({}, base.defaults, base.options, base.metadata); // initialize plugin data base.data = $.data(base); $.data(base, "currentslide", base.config.playreverse && base.config.startslide == 1 ? base.$elem.children().length-1 : base.config.startslide - 1); $.data(base, "nextslide", base.data.currentslide); $.data(base, "totalslides", base.$elem.children().length); $.data(base, "browserengineprefix", base._getbrowserengineprefix()); $.data(base, "isplaying", false); $.data(base, "isanimating", false); $.data(base, "playpaused", false); $.data(base, "justtouched", false); $.data(base, "ismoving", false); $.data(base, "width", base.config.width); // create helper html objects base.$elem.addclass("slider"); base.$elem.css({ position: "relative" }); base.$elem.wrapinner("
", base.$elem).children(); base.$elem.wrapinner("
", $(".slide-wrapper", base.$elem)).children(); base.$elem.wrapinner("
", $(".slide-container", base.$elem)).children(); $(".slide-container", base.$elem).css({ position: "relative" }); // setup common jq objects $container = $(".slide-dragcontainer", base.$elem); $wrapper = $(".slide-wrapper", base.$elem); $children = $wrapper.children(); // "saaave the children, aaaah aah ah aaaaaah" // add prev/next nagivation if (base.config.prevnextnav) { // add prev/next buttons $wrapper.after("
"); $wrapper.after("
"); $next = $("." + base.config.nextbuttonclass, base.$elem); $prev = $("." + base.config.prevbuttonclass, base.$elem); $next.append(""); $prev.append(""); $buttons = $next.add($prev); // toogle on hover if (base.config.prevnextautohide) { $buttons.hide(); base.$elem.hover( function () { $buttons.fadein("fast") }, function () { $buttons.fadeout("fast") } ); } // bind click event to buttons $prev.on("click", function (e) { base.previous(); }); $next.on("click", function (e) { base.next(); }); } // add pager navigation if (base.config.pagernav) { base.$elem.append("
    "); // loop through each slide $children.each(function () { $("
  • ").appendto($("." + base.config.pagerclass, base.$elem)) .attr("rel", $(this).index()) .css({ "background-image": "url('" + base.config.pagerimage + "')" }) .on("click", function () { $.data(base, "nextslide", parseint($(this).attr("rel"))); base._prepareslides(true); base._slide(true); base._manualinterference(); }); }); } // add data-attribute captions $children.each(function () { $innerbase = $(this); caption = $innerbase.data('plugin-slide-caption'); if (caption === undefined) return; if (this.tagname == "img") { // if the slide is an image, wrap this image in a div and append the caption div. $innerbase.wrap("
    "); $innerbase.after("
    "); $innerbase.next().append(caption); } else { // for any other type of slide element, just append the caption div at the end. $innerbase.append("
    "); $innerbase.children().last().append(caption); } // toogle on hover if (base.config.captionautohide) { $("." + base.config.slidecaptionclass, base.$elem).hide(); base.$elem.hover( function () { $("." + base.config.slidecaptionclass, base.$elem).fadein("fast") }, function () { $("." + base.config.slidecaptionclass, base.$elem).fadeout("fast") } ); } }); // add css styles $wrapper.children().addclass("slide").css({ position: "absolute", top: 0, left: 0, width: base.data.width, height: base.config.height, zindex: 0, display: "none", webkitbackfacevisibility: "hidden" }); // set the height of the wrapper to fit the max height of the slides maxheight = $children.height(); $wrapper.css({ position: "relative", left: 0, height: maxheight }); $(".slide-container", base.$elem).css({ width: base.data.width, overflow: "hidden", height: maxheight }); // setup touch event handlers if (base.config.touchnav) { $container.on("touchstart", function (e) { var eventdata = e.originalevent.touches[0]; e.preventdefault(); base._onmovestart(eventdata.pagex, eventdata.pagey); return e.stoppropagation(); }); $container.on("touchmove", function (e) { var eventdata = e.originalevent.touches[0]; e.preventdefault(); base._onmove(eventdata.pagex, eventdata.pagey); return e.stoppropagation(); }); $container.on("touchend", function (e) { e.preventdefault(); base._onmoveend(); return e.stoppropagation(); }); } // setup mouse event handlers if (base.config.mousenav) { $container.css("cursor", "pointer"); $container.on("dragstart", function (e) { return false; }); $container.on("mousedown", function (e) { base._onmovestart(e.clientx, e.clienty); $(window).attr('unselectable', 'on').on('selectstart', false).css('user-select', 'none').css('userselect', 'none').css('mozuserselect', 'none'); return e.stoppropagation(); }); // the mousemove event should also work outside the slide-wrapper container $(window).on("mousemove", function (e) { base._onmove(e.clientx, e.clienty); return e.stoppropagation(); }); // the mouseup event should also work outside the slide-wrapper container $(window).on("mouseup", function (e) { base._onmoveend(); $(window).removeattr('unselectable').unbind('selectstart').css('user-select', null).css('userselect', null).css('mozuserselect', null); return e.stoppropagation(); }); } // auto-size before preparing slides if (base.config.autosize) { settimeout(function () { base._resize(); }, 50) // setup resize event handler $(window).resize(function () { // the timeout is to let other resize events finish // e.g. if using adapt.js // this will make it flicker momentarily when resizing // large widths return settimeout(function () { base._resize(); }, 50); }); } // well, the name of the function says it all base._prepareslides(); // go to the start slide base.gotoslide(base.data.currentslide); // autoplay if so inclined if (base.config.autoplay) { // setup delay, if any settimeout(function () { base.start(); }, base.config.delay); } return this; }, /* move to previous slide **********************************************************/ previous: function () { var base, nextslide; // defined variable to avoid scope problems base = this; // store slide direction in plugin data $.data(base, "slidedirection", "previous"); // find next index nextslide = (base.data.nextslide - 1) % base.data.totalslides; // stop here if we've reached past the beginning and aren't on repeat if (!base.config.repeat && (base.data.nextslide - 1) < 0) { if (base.config.playreverse){ // stop playing $.data(base, "playpaused", true); base.stop(); } return; } else if (base.data.playpaused && (base.data.nextslide - 1) > 0) { $.data(base, "playpaused", false); base.start(); } // update data $.data(base, "nextslide", nextslide); // perform sliding to the previous slide return this._slide(); }, /* move to next slide **********************************************************/ next: function () { var base, nextslide; // defined variable to avoid scope problems base = this; // store slide direction in plugin data $.data(base, "slidedirection", "next"); // find next index nextslide = (base.data.nextslide + 1) % base.data.totalslides; // stop here if we've reached past the end and aren't on repeat if (!base.config.repeat && (base.data.nextslide + 1) > (base.data.totalslides - 1)) { if (!base.config.playreverse) { // stop playing $.data(base, "playpaused", true); base.stop(); } return; } else if (base.data.playpaused && (base.data.nextslide + 1) < (base.data.totalslides - 1)) { $.data(base, "playpaused", false); base.start(); } // update data $.data(base, "nextslide", nextslide); // perform sliding to the next slide return this._slide(); }, /* a method to start the slideshow **********************************************************/ start: function () { var base, $precontainer, timer; // defined variable to avoid scope problems base = this; // jquery objects $precontainer = $(".slide-container", base.$elem); // if we're already playing, clear previous interval if (base.data.isplaying && base.data.playtimer) clearinterval(base.data.playtimer); // setup the play timer timer = setinterval((function () { // well slide already if (base.config.playreverse) base.previous(); else base.next(); }), base.config.interval) // store the timer for reference $.data(base, "playtimer", timer); // setup pause when mouse hover if (base.config.hoverpause) { $precontainer.unbind(); $precontainer.hover(function () { $.data(base, "playpaused", true); return base.stop(); },function () { $.data(base, "playpaused", false); return base.start(); }); } // woo we're playing $.data(base, "isplaying", true); }, /* a method to stop playing the slideshow **********************************************************/ stop: function () { var base, $precontainer; // defined variable to avoid scope problems base = this; // jquery objects $precontainer = $(".slide-container", base.$elem); // stop the interval timer clearinterval(base.data.playtimer); $.data(base, "playtimer", null); // if stop was called but and it wasn't due to a pause, // unbind container events if (base.config.hoverpause && !base.data.playpaused) $precontainer.unbind(); // we've stopped $.data(base, "isplaying", false); }, /* simply jump to a given slide without transistion **********************************************************/ gotoslide: function (slideindex) { var base, nextslideindex, $container, $slides, $slide, leftpos; // define variable to avoid scope problems base = this; // data $.data(base, "nextslide", (slideindex) % base.data.totalslides); nextslideindex = (slideindex) % base.data.totalslides; $.data(base, "currentslide", nextslideindex); // jquery objects $container = $(".slide-wrapper", base.$elem); $slides = $container.children(); $slide = $container.children(":eq(" + nextslideindex + ")"); // get position of goal slide leftpos = $slide.position().left; base._setactive($slides, $slide); // gogogo if (base.config.animationcsstransitions && base.data.browserengineprefix) { base._transition((-leftpos), 0); } else { $container.position().left = -leftpos; } // align the slides to prepare for next transition base._alignslides(leftpos); }, /* user interacted, if we're playing, we must restart **********************************************************/ _manualinterference: function () { // define variable to avoid scope problems var base = this; if (base.data.isplaying) { // stop and start, to restart the timer from the beginning. base.stop(); base.start(); } }, /* position and align the slides to prepare for sliding **********************************************************/ _prepareslides: function (onlyahead) { var base, $container, $slides, width, half, i; // define variable to avoid scope problems base = this; // jquery objects $container = $(".slide-wrapper", base.$elem); $slides = $container.children(); // config width = base.data.width; half = math.floor(base.data.totalslides / 2); i = 0; $slides.each(function () { // move first half the slides ahead $(this).css({ display: "block", left: width * i, zindex: 10 }); i++; // move the other half back in line if (!onlyahead && base.config.repeat && i > half) i = base.data.totalslides % 2 ? -half : -(half - 1); }); }, /* handling the start of the movement **********************************************************/ _onmovestart: function (x, y) { // define variable to avoid scope problems var base = this; // setup touchrelated data if (!base.data.ismoving) $.data(base, "touchtime", number(new date())); $.data(base, "touchedx", x); $.data(base, "touchedy", y); // the mouse is down. $.data(base, "ismoving", true); // stop playing if (base.data.isplaying) { $.data(base, "playpaused", true); base.stop(); } }, /* handling the movement **********************************************************/ _onmove: function (x, y) { var base, $container, $slide, leftpos, prefix, translatex, limit; // define variable to avoid scope problems base = this; // only move if, we're actuall "moving" if (!base.data.ismoving) return; // jquery objects $container = $(".slide-wrapper", base.$elem); // verify whether we're scrolling or sliding $.data(base, "scrolling", math.abs(x - base.data.touchedx) < math.abs(y - base.data.touchedy)); // if we're not scrolling, we perform the translation // ...also - wait for any animation to finish // (we cant slide while animating) if (!base.data.scrolling && !base.data.isanimating) { // get the position of the slide we are heading for $slide = $container.children(":eq(" + base.data.nextslide + ")"); leftpos = $slide.position().left; // get the browser engine prefix - if any prefix = base.data.browserengineprefix.css; // get the delta movement to use for translation translatex = x - base.data.touchedx; // limit if not repeating limit = base.data.width * 0.1; if (!base.config.repeat) { if (base.data.currentslide <= 0 && -translatex < -limit) translatex = limit; else if (base.data.currentslide >= (base.data.totalslides - 1) && -translatex > limit) translatex = -limit; } // transformation base._transition(-leftpos + translatex, 0); } }, /* handling the end of the movement **********************************************************/ _onmoveend: function () { var base, $container, $slide, leftpos, half, tenth, svipe; // define variable to avoid scope problems base = this; // only move if, we're actually "moving" if (!base.data.ismoving) return; // jquery objects $container = $(".slide-wrapper", base.$elem); // set that we've just touched something such that when we slide next // the sliding duration is temporary halved. $.data(base, "justtouched", true); // get the position of the slide we are heading for $slide = $container.children(":eq(" + base.data.nextslide + ")"); leftpos = $slide.position().left; // if we've slided at least half the width of the slide - slide to next // also if we've slided 10% of the width within 1/4 of a second, // we slide to the next half = base.data.width * 0.5; tenth = base.data.width * 0.1; svipe = (number(new date()) - base.data.touchtime < 250); if (!base.config.repeat && ($container.position().left < -(leftpos) && base.data.currentslide >= (base.data.totalslides - 1) || $container.position().left > (-leftpos) && base.data.currentslide <= 0)) { // we can't move move as repeat is turned off base._transition((-leftpos), 0.15); } else if ($container.position().left > (-leftpos + half) || ($container.position().left > (-leftpos + tenth) && svipe)) { this.previous(); } else if ($container.position().left < -(leftpos + half) || ($container.position().left < -(leftpos + tenth) && svipe)) { this.next(); } else { // didn't slide enough to move on - bounce back into place. base._transition((-leftpos), 0.15); } // align the slides to prepare for the next slide base._alignslides(leftpos); // we're no longer moving and touching $.data(base, "ismoving", false); $.data(base, "justtouched", false); // restart playing playing if(base.data.playpaused) base.start(); }, /* make an "endless" line of slides **********************************************************/ // issue: too slow slide duration and too fast sliding // may result in the slides not being aligned yet. // solution:...might be to duplicate all slides in init // if number of slides is low. _alignslides : function(goalposition) { var base, $container, $slides, $slide, half, width, bufferlength, buffershortage, i, lowest, highest; // define variable to avoid scope problems base = this; if (!base.config.repeat) return; // jquery objects $container = $(".slide-wrapper", base.$elem); $slides = $container.children(); // retrieve goalposition if undefined if (goalposition === undefined) { $slide = $container.children(":eq(" + base.data.nextslide + ")"); goalposition = $slide.position().left; } // remove fraction goalposition = math.round(goalposition,0); // half of the total slides half = math.ceil(base.data.totalslides / 2); // config width = base.data.width; // get number of $slides after/before 'goalposition' - this is our buffer. // if our buffer is below half the total $slides, we need to increase it. bufferlength = 0; $slides.each(function () { var l = $(this).position().left; if (l > goalposition - width) bufferlength++; }); // calculate how much short on buffer we are buffershortage = half - bufferlength; // we're sliding the other direction thus moving a buffer to the other side if (buffershortage < 0) buffershortage = base.data.totalslides % 2 == 0 ? buffershortage + 1 : buffershortage; // align slides according to buffershortage for (i = 0; i < math.abs(buffershortage); i++) { // find the element with the lowest left position lowest = [].reduce.call($slides, function (sml, cur) { return $(sml).offset().left < $(cur).offset().left ? sml : cur; }); // find the element with the highest left position highest = [].reduce.call($slides, function (sml, cur) { return $(sml).offset().left > $(cur).offset().left ? sml : cur; }); if(buffershortage > 0) $(lowest).css("left", math.round($(highest).position().left + width)); else $(highest).css("left", math.round($(lowest).position().left - width)); } }, /* perform a slide **********************************************************/ _slide: function (postalign) { var base, nextslideindex, $container, $slides, $slide, leftpos; // define variable to avoid scope problems base = this; // data nextslideindex = base.data.nextslide; // jquery objects $container = $(".slide-wrapper", base.$elem); $slides = $container.children(); $slide = $container.children(":eq(" + nextslideindex + ")"); // get the position of the slide we are heading for leftpos = math.round($slide.position().left); // --- base._setactive($slides, $slide); // --- // pre-align the slides in a line to prepare for the transition animation if (!postalign) base._alignslides(leftpos); // animate - css transitions are much better. $.data(base, "isanimating", true); if (base.config.animationcsstransitions && base.data.browserengineprefix) { base._transition((-leftpos), (base.data.justtouched ? 0.5 : 1)); // set nextslide on end of transition $container.on("transitionend webkittransitionend otransitionend otransitionend mstransitionend", function () { $.data(base, "currentslide", nextslideindex); $container.unbind("transitionend webkittransitionend otransitionend otransitionend mstransitionend"); // post-align the slides in a line to prepare for the transition animation if (postalign) base._alignslides(leftpos); }); } else { // we must resolve to sucky animations $container.stop().animate({ left: -leftpos }, base.config.animationduration, function () { $.data(base, "currentslide", nextslideindex); $.data(base, "isanimating", false); $.data(base, "justtouched", false); }); } }, /* perform the transition **********************************************************/ _transition: function (leftpos, durationmodifier) { var base, $container, prefix, transform, duration, timing; // define variable to avoid scope problems base = this; // jquery objects $container = $(".slide-wrapper", base.$elem); // limit duration modifier if (durationmodifier === undefined || durationmodifier < 0) { durationmodifier = 1; } // note: we add both prefixed transition and the default // for browser compatibility. // select the css code based on browser engine prefix = base.data.browserengineprefix.css; transform = prefix + "transform"; duration = prefix + "transitionduration"; timing = prefix + "transitiontimingfunction"; // set style to activate the slide transition $container[0].style[duration] = (base.config.animationduration * durationmodifier) + "ms"; $container[0].style[timing] = base.config.animationtimingfunction; $container[0].style[transform] = "translatex(" + leftpos + "px)"; $container.on("transitionend webkittransitionend otransitionend otransitionend mstransitionend", function () { $.data(base, "isanimating", false); $.data(base, "justtouched", false); $container.unbind("transitionend webkittransitionend otransitionend otransitionend mstransitionend"); }); }, /* update active slide **********************************************************/ _setactive: function ($slides, $slide) { var base = this, activeslideclass, pager; activeslideclass = base.config.activeslideclass; // clear old active class $slides.removeclass(activeslideclass); // set new active class $slide.addclass(activeslideclass); // set active page in pager if (base.config.pagernav) { pager = $("." + base.config.pagerclass, base.$elem); pager.children().removeclass("act"); pager.find("[rel=" + $slide.index() + "]").addclass("act"); } }, /* auto-size the slider **********************************************************/ _resize: function () { var base, newwidth, ratio, newheight, maxheight; // define variable to avoid scope problems base = this; // stop playing if (base.data.isplaying){ $.data(base, "playpaused", true); base.stop(); } // getting width from parent container newwidth = base.$elem.width(); // calculate w/h ratio ratio = base.config.height / base.config.width; // get height from w/h ratio newheight = newwidth * ratio; // update width $.data(base, "width", newwidth); // add css styles $(".slide", base.$elem).css({ width: newwidth, height: newheight }); // set the height of the wrapper to fit the max height of the slides maxheight = $(".slide-wrapper", base.$elem).children().height(); $(".slide-wrapper", base.$elem).css({ height: maxheight }); $(".slide-container", base.$elem).css({ width: newwidth, height: maxheight }); // restart playing if (base.data.playpaused){ $.data(base, "playpaused", false); base.start(); } // realign now to make it look good. base._prepareslides(); base.gotoslide(base.data.currentslide); }, /* find out which browser engine is used **********************************************************/ _getbrowserengineprefix: function () { var transition, vendor, i; transition = "transition"; vendor = ["moz", "webkit", "khtml", "o", "ms"]; i = 0; while (i < vendor.length) { if (typeof document.body.style[vendor[i] + transition] === "string") { return { css: vendor[i] }; } i++; } return false; } } plugin.defaults = plugin.prototype.defaults; /* add the plugin to the jquery namespace. **************************************************************/ $.fn.excoloslider = function (options) { return this.each(function () { // instantiate and initialize new plugin(this, options).init(); }); }; })(jquery, window, document);