From 28762c5efd715cb59dd404a4efc6f2be0e55defa Mon Sep 17 00:00:00 2001 From: Zoltan Toth Date: Fri, 20 Oct 2017 17:55:27 -0400 Subject: [PATCH] Bugfix for live() for caousels with no dots --- CHANGELOG.md | 7 + dist/index.html | 39 +- dist/vanilla-js-carousel.min.js | 397 ++++++++++++++++++++- docs/javascript/vanilla-js-carousel.min.js | 2 +- package.json | 4 +- src/javascript/vanilla-js-carousel.js | 24 +- test/spec/multi_image.spec.js | 8 +- 7 files changed, 430 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0159f7..fcb570f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log --- +## [3.1.1] - 2017-10-20 + +### Bugfix +1. *_live()_* was returning the wrong index on carousels with no dots + + + ## [3.1.0] - 2017-09-27 ### Enhancements diff --git a/dist/index.html b/dist/index.html index fa8486d..6347028 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1,31 +1,8 @@ - - - - - Vanilla JavaScript Carousel - - - -
- -
- - - - \ No newline at end of file +Vanilla JavaScript Carousel
\ No newline at end of file diff --git a/dist/vanilla-js-carousel.min.js b/dist/vanilla-js-carousel.min.js index 84270c1..1c5d89a 100644 --- a/dist/vanilla-js-carousel.min.js +++ b/dist/vanilla-js-carousel.min.js @@ -1,5 +1,394 @@ /** - * Vanilla Javascript Carousel v3.1.0 - * https://zoltantothcom.github.io/vanilla-js-carousel - */ -function Carousel(t){function e(t,e,n){var i=S.querySelectorAll("."+j+" > ul li")[t];i.style.marginLeft=e,S.querySelector("."+j+" > ul").removeChild(i),S.querySelector("."+j+" > ul").insertAdjacentHTML(n,i.outerHTML)}function n(){var t=document.createElement("ul");t.classList.add(H),t.addEventListener("click",r.bind(this));for(var e=0;e ul li")[0]),e(I-1,-S.offsetWidth+"px","afterBegin"),y(-1)}function v(){C(),0!==B&&(o(document.querySelectorAll("."+j+" > ul li")[B-1]),y(-1))}function m(){t.infinite?p():L(),h()}function p(){u(document.querySelectorAll("."+j+" > ul li")[1]),e(0,"","beforeEnd"),y(1)}function L(){if(B===I-1)return void C();u(document.querySelectorAll("."+j+" > ul li")[B]),y(1)}function y(e){B+=e,t.dots&&i()}function h(){N&&(C(),b())}function b(){N||(N=setInterval(m.bind(this),q))}function C(){clearInterval(N),N=null}function E(){return B}var S=document.getElementById(t.elem||"carousel"),q=t.interval||3e3,g=t.btnPlayText||"Play",A=t.btnStopText||"Stop",x=t.arrNextText||"›",T=t.arrPrevText||"‹",j="js-Carousel",k="js-Carousel-arrowPrev",w="js-Carousel-arrowNext",H="js-Carousel-dots",M="js-Carousel-btnStop",P="js-Carousel-btnPlay",I=S.querySelectorAll("li").length,B=0,N=null;return I>1&&function(){var i={dots:function(){return n()},arrows:function(){return l()},buttons:function(){return a()},autoplay:function(){return b()},infinite:function(){return e(I-1,-S.offsetWidth+"px","afterBegin")},initial:function(){return c(t.initial>=I?I:t.initial)}};for(var r in i)t.hasOwnProperty(r)&&t[r]&&i[r]()}(),{live:E,show:c,prev:d,next:m,play:b,stop:C}} \ No newline at end of file +* @fileOverview +* @author Zoltan Toth +* @version 3.1.0 +*/ + +/** +* @description +* 1Kb (gzipped) pure JavaScript carousel with all the basic features. +* +* @class +* @param {object} options - User defined settings for the carousel. +* @param {string} options.elem [options.elem=carousel] - The HTML id of the carousel container. +* @param {(boolean)} [options.infinite=false] - Enables infinite mode for the carousel. +* @param {(boolean)} [options.autoplay=false] - Enables auto play for slides. +* @param {number} [options.interval=3000] - The interval between slide change. +* @param {number} [options.show=0] - Index of the slide to start on. Numeration begins at 0. +* +* @param {(boolean)} [options.dots=true] - Display navigation dots. +* @param {(boolean)} [options.arrows=true] - Display navigation arrows (PREV/NEXT). +* @param {(boolean)} [options.buttons=true] - Display navigation buttons (STOP/PLAY). +* +* @param {(string)} [options.btnPlayText=Play] - Text for _PLAY_ button. +* @param {(string)} [options.btnStopText=Stop] - Text for _STOP_ button. +* @param {(string)} [options.arrPrevText=«] - Text for _PREV_ arrow. +* @param {(string)} [options.arrNextText=»] - Text for _NEXT_ arrow. +*/ +function Carousel(options) { + var element = document.getElementById(options.elem || 'carousel'), + interval = options.interval || 3000, + + btnPlayText = options.btnPlayText || 'Play', + btnStopText = options.btnStopText || 'Stop', + + arrNextText = options.arrNextText || '›', + arrPrevText = options.arrPrevText || '‹', + + crslClass = 'js-Carousel', + crslArrowPrevClass = 'js-Carousel-arrowPrev', + crslArrowNextClass = 'js-Carousel-arrowNext', + crslDotsClass = 'js-Carousel-dots', + crslButtonStopClass = 'js-Carousel-btnStop', + crslButtonPlayClass = 'js-Carousel-btnPlay', + + count = element.querySelectorAll('li').length, + current = 0, + cycle = null; + + /** + * Render the carousel if more than one slide. + * Otherwise just show the single item. + */ + if (count > 1) { + render(); + } + + /** + * Render the carousel and all the navigation elements (arrows, dots, + * play/stop buttons) if needed. Start with a particular slide, if set. + * If infinite - move the last item to the very beginning and off the display area. + */ + function render() { + var actions = { + dots: function() { + return showDots(); + }, + arrows: function() { + return showArrows(); + }, + buttons: function() { + return showButtons(); + }, + autoplay: function() { + return play(); + }, + infinite: function() { + return moveItem(count - 1, -element.offsetWidth + 'px', 'afterBegin'); + }, + initial: function() { + var initial = 0 || (options.initial >= count) ? count : options.initial; + return show(initial); + } + }; + + for (var key in actions) { + if (options.hasOwnProperty(key) && options[key]) { + actions[key](); + } + } + } + + /** + * Helper for moving items - last to be first or first to be the last. Needed + * for infinite rotation of the carousel. + * + * @param {number} i - Position of the list item to move (either first or last). + * @param {number} marginLeft - Left margin to position the item off-screen + * at the beginning or no margin at the end. + * @param {string} position - Where to insert the item. One of the following - + * 'afterBegin' or 'beforeEnd'. + */ + function moveItem(i, marginLeft, position) { + var itemToMove = element.querySelectorAll('.' + crslClass + ' > ul li')[i]; + itemToMove.style.marginLeft = marginLeft; + + element.querySelector('.' + crslClass + ' > ul') + .removeChild(itemToMove); + + element.querySelector('.' + crslClass + ' > ul') + .insertAdjacentHTML(position, itemToMove.outerHTML); + } + + /** + * Create the navigation dots and attach to carousel. + */ + function showDots() { + var dotContainer = document.createElement('ul'); + dotContainer.classList.add(crslDotsClass); + dotContainer.addEventListener('click', scrollToImage.bind(this)); + + for (var i = 0; i < count; i++) { + var dotElement = document.createElement('li'); + dotElement.setAttribute('data-position', i); + + dotContainer.appendChild(dotElement); + } + + element.appendChild(dotContainer); + currentDot(); + } + + /** + * Highlight the corresponding dot of the currently visible carousel item. + */ + function currentDot() { + [].forEach.call(element.querySelectorAll('.' + crslDotsClass + ' li'), function(item) { + item.classList.remove('is-active'); + }); + + element.querySelectorAll('.' + crslDotsClass + ' li')[current].classList.add('is-active'); + } + + /** + * Moves the carousel to the desired slide on a navigation dot click. + * + * @param {object} e - The clicked dot element. + */ + function scrollToImage(e) { + if (e.target.tagName === 'LI') { + show(e.target.getAttribute('data-position')); + + resetInterval(); + } + } + + /** + * Create the navigation arrows (prev/next) and attach to carousel. + */ + function showArrows() { + var buttonPrev = document.createElement('button'); + buttonPrev.innerHTML = arrPrevText; + buttonPrev.classList.add(crslArrowPrevClass); + + var buttonNext = document.createElement('button'); + buttonNext.innerHTML = arrNextText; + buttonNext.classList.add(crslArrowNextClass); + + buttonPrev.addEventListener('click', showPrev); + buttonNext.addEventListener('click', showNext); + + element.appendChild(buttonPrev); + element.appendChild(buttonNext); + } + + /** + * Create the navigation buttons (play/stop) and attach to carousel. + */ + function showButtons() { + var buttonPlay = document.createElement('button'); + buttonPlay.innerHTML = btnPlayText; + buttonPlay.classList.add(crslButtonPlayClass); + buttonPlay.addEventListener('click', play); + + var buttonStop = document.createElement('button'); + buttonStop.innerHTML = btnStopText; + buttonStop.classList.add(crslButtonStopClass); + buttonStop.addEventListener('click', stop); + + element.appendChild(buttonPlay); + element.appendChild(buttonStop); + } + + /** + * Animate the carousel to go back 1 slide. Moves the very first (off-screen) + * item to the visible area. + * + * @param {object} item - The element to move into view. + */ + function animatePrev(item) { + item.style.marginLeft = ''; + } + + /** + * Animate the carousel to go forward 1 slide. + * + * @param {object} item - The element to move into view. + */ + function animateNext(item) { + item.style.marginLeft = -element.offsetWidth + 'px'; + } + + /** + * Move the carousel to the desired slide. + * + * @param {number} slide - The index of the item. + * @public + */ + function show(slide) { + var delta = current - slide; + + if (delta < 0) { + moveByDelta(-delta, showNext); + } else { + moveByDelta(delta, showPrev); + } + } + + /** + * Helper to move the slides by index. + * + * @param {number} delta - how many slides to move. + * @param {function} direction - function to move forward or back. + */ + function moveByDelta(delta, direction) { + for (var i = 0; i < delta; i++) { + direction(); + } + } + + /** + * Move the carousel back. + * + * @public + */ + function showPrev() { + if (options.infinite) { + showPrevInfinite(); + } else { + showPrevLinear(); + } + + resetInterval(); + } + + /** + * Helper function to show the previous slide for INFINITE carousel. + * Do the sliding, move the last item to the very beginning. + */ + function showPrevInfinite() { + animatePrev(document.querySelectorAll('.' + crslClass + ' > ul li')[0]); + moveItem(count - 1, -element.offsetWidth + 'px', 'afterBegin'); + + adjustCurrent(-1); + } + + /** + * Helper function to show the previous slide for LINEAR carousel. + * Stop the autoplay if user goes back. If on the first slide - do nothing. + */ + function showPrevLinear() { + stop(); + if (current === 0) { + return; + } + animatePrev(document.querySelectorAll('.' + crslClass + ' > ul li')[current - 1]); + + adjustCurrent(-1); + } + + /** + * Move the carousel forward. + * + * @public + */ + function showNext() { + if (options.infinite) { + showNextInfinite(); + } else { + showNextLinear(); + } + + resetInterval(); + } + + /** + * Helper function to show the next slide for INFINITE carousel. + * Do the sliding, move the second item to the very end. + */ + function showNextInfinite() { + animateNext(document.querySelectorAll('.' + crslClass + ' > ul li')[1]); + moveItem(0, '', 'beforeEnd'); + + adjustCurrent(1); + } + + /** + * Helper function to show the next slide for LINEAR carousel. + * If on the last slide - stop the play and do nothing else. + */ + function showNextLinear() { + if (current === count - 1) { + stop(); + return; + } + animateNext(document.querySelectorAll('.' + crslClass + ' > ul li')[current]); + + adjustCurrent(1); + } + + /** + * Adjust _current_ and highlight the respective dot. + * + * @param {number} val - defines which way current should be corrected. + */ + function adjustCurrent(val) { + current += val; + + switch (current) { + case -1: + current = count - 1; + break; + case count: + current = 0; + break; + default: + current = current; + } + + if (options.dots) { + currentDot(); + } + } + + /** + * Reset the autoplay interval. + */ + function resetInterval() { + if (cycle) { + stop(); + play(); + } + } + + /** + * Start the auto play. + * If already playing do nothing. + * + * @public + */ + function play() { + if (cycle) { + return; + } + cycle = setInterval(showNext.bind(this), interval); + } + + /** + * Stop the auto play. + * + * @public + */ + function stop() { + clearInterval(cycle); + cycle = null; + } + + /** + * Returns the current slide index. + * + * @public + */ + function live() { + return current; + } + + return { + 'live': live, + 'show': show, + 'prev': showPrev, + 'next': showNext, + 'play': play, + 'stop': stop + }; +} \ No newline at end of file diff --git a/docs/javascript/vanilla-js-carousel.min.js b/docs/javascript/vanilla-js-carousel.min.js index 84270c1..ba10cf5 100644 --- a/docs/javascript/vanilla-js-carousel.min.js +++ b/docs/javascript/vanilla-js-carousel.min.js @@ -2,4 +2,4 @@ * Vanilla Javascript Carousel v3.1.0 * https://zoltantothcom.github.io/vanilla-js-carousel */ -function Carousel(t){function e(t,e,n){var i=S.querySelectorAll("."+j+" > ul li")[t];i.style.marginLeft=e,S.querySelector("."+j+" > ul").removeChild(i),S.querySelector("."+j+" > ul").insertAdjacentHTML(n,i.outerHTML)}function n(){var t=document.createElement("ul");t.classList.add(H),t.addEventListener("click",r.bind(this));for(var e=0;e ul li")[0]),e(I-1,-S.offsetWidth+"px","afterBegin"),y(-1)}function v(){C(),0!==B&&(o(document.querySelectorAll("."+j+" > ul li")[B-1]),y(-1))}function m(){t.infinite?p():L(),h()}function p(){u(document.querySelectorAll("."+j+" > ul li")[1]),e(0,"","beforeEnd"),y(1)}function L(){if(B===I-1)return void C();u(document.querySelectorAll("."+j+" > ul li")[B]),y(1)}function y(e){B+=e,t.dots&&i()}function h(){N&&(C(),b())}function b(){N||(N=setInterval(m.bind(this),q))}function C(){clearInterval(N),N=null}function E(){return B}var S=document.getElementById(t.elem||"carousel"),q=t.interval||3e3,g=t.btnPlayText||"Play",A=t.btnStopText||"Stop",x=t.arrNextText||"›",T=t.arrPrevText||"‹",j="js-Carousel",k="js-Carousel-arrowPrev",w="js-Carousel-arrowNext",H="js-Carousel-dots",M="js-Carousel-btnStop",P="js-Carousel-btnPlay",I=S.querySelectorAll("li").length,B=0,N=null;return I>1&&function(){var i={dots:function(){return n()},arrows:function(){return l()},buttons:function(){return a()},autoplay:function(){return b()},infinite:function(){return e(I-1,-S.offsetWidth+"px","afterBegin")},initial:function(){return c(t.initial>=I?I:t.initial)}};for(var r in i)t.hasOwnProperty(r)&&t[r]&&i[r]()}(),{live:E,show:c,prev:d,next:m,play:b,stop:C}} \ No newline at end of file +function Carousel(t){function e(t,e,n){var i=S.querySelectorAll("."+j+" > ul li")[t];i.style.marginLeft=e,S.querySelector("."+j+" > ul").removeChild(i),S.querySelector("."+j+" > ul").insertAdjacentHTML(n,i.outerHTML)}function n(){var t=document.createElement("ul");t.classList.add(H),t.addEventListener("click",r.bind(this));for(var e=0;e ul li")[0]),e(I-1,-S.offsetWidth+"px","afterBegin"),y(-1)}function v(){C(),0!==B&&(o(document.querySelectorAll("."+j+" > ul li")[B-1]),y(-1))}function m(){t.infinite?p():L(),h()}function p(){u(document.querySelectorAll("."+j+" > ul li")[1]),e(0,"","beforeEnd"),y(1)}function L(){if(B===I-1)return void C();u(document.querySelectorAll("."+j+" > ul li")[B]),y(1)}function y(e){switch(B+=e){case-1:B=I-1;break;case I:B=0;break;default:B=B}t.dots&&i()}function h(){N&&(C(),b())}function b(){N||(N=setInterval(m.bind(this),q))}function C(){clearInterval(N),N=null}function E(){return B}var S=document.getElementById(t.elem||"carousel"),q=t.interval||3e3,g=t.btnPlayText||"Play",A=t.btnStopText||"Stop",x=t.arrNextText||"›",T=t.arrPrevText||"‹",j="js-Carousel",k="js-Carousel-arrowPrev",w="js-Carousel-arrowNext",H="js-Carousel-dots",M="js-Carousel-btnStop",P="js-Carousel-btnPlay",I=S.querySelectorAll("li").length,B=0,N=null;return I>1&&function(){var i={dots:function(){return n()},arrows:function(){return l()},buttons:function(){return a()},autoplay:function(){return b()},infinite:function(){return e(I-1,-S.offsetWidth+"px","afterBegin")},initial:function(){return c(t.initial>=I?I:t.initial)}};for(var r in i)t.hasOwnProperty(r)&&t[r]&&i[r]()}(),{live:E,show:c,prev:d,next:m,play:b,stop:C}} \ No newline at end of file diff --git a/package.json b/package.json index 7f29894..8a86548 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vanilla-js-carousel", - "version": "3.1.0", - "description": "1Kb (gzipped) pure JavaScript carousel with all the basic features.", + "version": "3.1.1", + "description": "1Kb (gzipped) pure JavaScript carousel with all the features most of us will ever need.", "main": "index.js", "scripts": { "start": "gulp", diff --git a/src/javascript/vanilla-js-carousel.js b/src/javascript/vanilla-js-carousel.js index f8939bf..8c1e145 100644 --- a/src/javascript/vanilla-js-carousel.js +++ b/src/javascript/vanilla-js-carousel.js @@ -1,7 +1,7 @@ /** * @fileOverview * @author Zoltan Toth -* @version 3.1.0 +* @version 3.1.1 */ /** @@ -137,17 +137,6 @@ function Carousel(options) { item.classList.remove('is-active'); }); - switch (current) { - case -1: - current = count - 1; - break; - case count: - current = 0; - break; - default: - current = current; - } - element.querySelectorAll('.' + crslDotsClass + ' li')[current].classList.add('is-active'); } @@ -336,6 +325,17 @@ function Carousel(options) { function adjustCurrent(val) { current += val; + switch (current) { + case -1: + current = count - 1; + break; + case count: + current = 0; + break; + default: + current = current; + } + if (options.dots) { currentDot(); } diff --git a/test/spec/multi_image.spec.js b/test/spec/multi_image.spec.js index e16fcfc..b0c2dc1 100644 --- a/test/spec/multi_image.spec.js +++ b/test/spec/multi_image.spec.js @@ -198,7 +198,8 @@ describe('CAROUSEL - MULTI IMAGE', function() { loadFixtures(regularFixture); this.carousel = new Carousel({ - dots: true + infinite: true, + dots: false }); }); @@ -210,6 +211,11 @@ describe('CAROUSEL - MULTI IMAGE', function() { this.carousel.show(3); expect( this.carousel.live() ).toEqual(3); }); + + it('should equal 1 on show(5)', function() { + this.carousel.show(5); + expect( this.carousel.live() ).toEqual(1); + }); });