/*! carousel transition plugin for Cycle2; version: 20130528 */ (function($) { "use strict"; $(document).on('cycle-bootstrap', function(e, opts, API) { if (opts.fx !== 'carousel') return; API.getSlideIndex = function(el) { var slides = this.opts()._carouselWrap.children(); var i = slides.index(el); return i % slides.length; }; // override default 'next' function API.next = function() { var count = opts.reverse ? -1 : 1; if (opts.allowWrap === false && (opts.currSlide + count) > opts.slideCount - opts.carouselVisible) return; opts.API.advanceSlide(count); opts.API.trigger('cycle-next', [opts]).log('cycle-next'); }; }); $.fn.cycle.transitions.carousel = { // transition API impl preInit: function(opts) { opts.hideNonActive = false; opts.container.on('cycle-destroyed', $.proxy(this.onDestroy, opts.API)); // override default API implementation opts.API.stopTransition = this.stopTransition; // issue #10 for (var i = 0; i < opts.startingSlide; i++) { opts.container.append(opts.slides[0]); } }, // transition API impl postInit: function(opts) { var i, j, slide, pagerCutoffIndex, wrap; var vert = opts.carouselVertical; if (opts.carouselVisible && opts.carouselVisible > opts.slideCount) opts.carouselVisible = opts.slideCount - 1; var visCount = opts.carouselVisible || opts.slides.length; var slideCSS = {display: vert ? 'block' : 'inline-block', position: 'static'}; // required styles opts.container.css({position: 'relative', overflow: 'hidden'}); opts.slides.css(slideCSS); opts._currSlide = opts.currSlide; // wrap slides in a div; this div is what is animated wrap = $('
') .prependTo(opts.container) .css({margin: 0, padding: 0, top: 0, left: 0, position: 'absolute'}) .append(opts.slides); opts._carouselWrap = wrap; if (!vert) wrap.css('white-space', 'nowrap'); if (opts.allowWrap !== false) { // prepend and append extra slides so we don't see any empty space when we // near the end of the carousel. for fluid containers, add even more clones // so there is plenty to fill the screen // @todo: optimzie this based on slide sizes for (j = 0; j < (opts.carouselVisible === undefined ? 2 : 1); j++) { for (i = 0; i < opts.slideCount; i++) { wrap.append(opts.slides[i].cloneNode(true)); } i = opts.slideCount; while (i--) { // #160, #209 wrap.prepend(opts.slides[i].cloneNode(true)); } } wrap.find('.cycle-slide-active').removeClass('cycle-slide-active'); opts.slides.eq(opts.startingSlide).addClass('cycle-slide-active'); } if (opts.pager && opts.allowWrap === false) { // hide "extra" pagers pagerCutoffIndex = opts.slideCount - visCount; $(opts.pager).children().filter(':gt(' + pagerCutoffIndex + ')').hide(); } opts._nextBoundry = opts.slideCount - opts.carouselVisible; this.prepareDimensions(opts); }, prepareDimensions: function(opts) { var dim, offset, pagerCutoffIndex, tmp; var vert = opts.carouselVertical; var visCount = opts.carouselVisible || opts.slides.length; if (opts.carouselFluid && opts.carouselVisible) { if (!opts._carouselResizeThrottle) { // fluid container AND fluid slides; slides need to be resized to fit container this.fluidSlides(opts); } } else if (opts.carouselVisible && opts.carouselSlideDimension) { dim = visCount * opts.carouselSlideDimension; opts.container[ vert ? 'height' : 'width' ](dim); } else if (opts.carouselVisible) { dim = visCount * $(opts.slides[0])[vert ? 'outerHeight' : 'outerWidth'](true); opts.container[ vert ? 'height' : 'width' ](dim); } // else { // // fluid; don't size the container // } offset = (opts.carouselOffset || 0); if (opts.allowWrap !== false) { if (opts.carouselSlideDimension) { offset -= ((opts.slideCount + opts.currSlide) * opts.carouselSlideDimension); } else { // calculate offset based on actual slide dimensions tmp = opts._carouselWrap.children(); for (var j = 0; j < (opts.slideCount + opts.currSlide); j++) { offset -= $(tmp[j])[vert ? 'outerHeight' : 'outerWidth'](true); } } } opts._carouselWrap.css(vert ? 'top' : 'left', offset); }, fluidSlides: function(opts) { var timeout; var slide = opts.slides.eq(0); var adjustment = slide.outerWidth() - slide.width(); var prepareDimensions = this.prepareDimensions; // throttle resize event $(window).on('resize', resizeThrottle); opts._carouselResizeThrottle = resizeThrottle; onResize(); function resizeThrottle() { clearTimeout(timeout); timeout = setTimeout(onResize, 20); } function onResize() { opts._carouselWrap.stop(false, true); var slideWidth = opts.container.width() / opts.carouselVisible; slideWidth = Math.ceil(slideWidth - adjustment); opts._carouselWrap.children().width(slideWidth); if (opts._sentinel) opts._sentinel.width(slideWidth); prepareDimensions(opts); } }, // transition API impl transition: function(opts, curr, next, fwd, callback) { var moveBy, props = {}; var hops = opts.nextSlide - opts.currSlide; var vert = opts.carouselVertical; var speed = opts.speed; // handle all the edge cases for wrapping & non-wrapping if (opts.allowWrap === false) { fwd = hops > 0; var currSlide = opts._currSlide; var maxCurr = opts.slideCount - opts.carouselVisible; if (hops > 0 && opts.nextSlide > maxCurr && currSlide == maxCurr) { hops = 0; } else if (hops > 0 && opts.nextSlide > maxCurr) { hops = opts.nextSlide - currSlide - (opts.nextSlide - maxCurr); } else if (hops < 0 && opts.currSlide > maxCurr && opts.nextSlide > maxCurr) { hops = 0; } else if (hops < 0 && opts.currSlide > maxCurr) { hops += opts.currSlide - maxCurr; } else currSlide = opts.currSlide; moveBy = this.getScroll(opts, vert, currSlide, hops); opts.API.opts()._currSlide = opts.nextSlide > maxCurr ? maxCurr : opts.nextSlide; } else { if (fwd && opts.nextSlide === 0) { // moving from last slide to first moveBy = this.getDim(opts, opts.currSlide, vert); callback = this.genCallback(opts, fwd, vert, callback); } else if (!fwd && opts.nextSlide == opts.slideCount - 1) { // moving from first slide to last moveBy = this.getDim(opts, opts.currSlide, vert); callback = this.genCallback(opts, fwd, vert, callback); } else { moveBy = this.getScroll(opts, vert, opts.currSlide, hops); } } props[ vert ? 'top' : 'left' ] = fwd ? ("-=" + moveBy) : ("+=" + moveBy); // throttleSpeed means to scroll slides at a constant rate, rather than // a constant speed if (opts.throttleSpeed) speed = (moveBy / $(opts.slides[0])[vert ? 'height' : 'width']()) * opts.speed; opts._carouselWrap.animate(props, speed, opts.easing, callback); }, getDim: function(opts, index, vert) { var slide = $(opts.slides[index]); return slide[ vert ? 'outerHeight' : 'outerWidth'](true); }, getScroll: function(opts, vert, currSlide, hops) { var i, moveBy = 0; if (hops > 0) { for (i = currSlide; i < currSlide + hops; i++) moveBy += this.getDim(opts, i, vert); } else { for (i = currSlide; i > currSlide + hops; i--) moveBy += this.getDim(opts, i, vert); } return moveBy; }, genCallback: function(opts, fwd, vert, callback) { // returns callback fn that resets the left/top wrap position to the "real" slides return function() { var pos = $(opts.slides[opts.nextSlide]).position(); var offset = 0 - pos[vert ? 'top' : 'left'] + (opts.carouselOffset || 0); opts._carouselWrap.css(opts.carouselVertical ? 'top' : 'left', offset); callback(); }; }, // core API override stopTransition: function() { var opts = this.opts(); opts.slides.stop(false, true); opts._carouselWrap.stop(false, true); }, // core API supplement onDestroy: function(e) { var opts = this.opts(); if (opts._carouselResizeThrottle) $(window).off('resize', opts._carouselResizeThrottle); opts.slides.prependTo(opts.container); opts._carouselWrap.remove(); } }; })(jQuery);