Carousel - Cyclic Rotation

by abhitalks

HTML

<div class="controls">
    <label>Speed <input id="speed" type="number" min="1" max="8" value="2" />x</label>
    &nbsp;|&nbsp;
    <label>Iterations <input id="iter" type="number" min="0" max="8" value="2" /></label>
</div>
<div class="cardList">
    <div class="card">1</div>
    <div class="card">2</div>
    <div class="card">3</div>
    <div class="card">4</div>
</div>
<button id="start">Start</button>
<button id="stop">Stop</button>
<button id="reset">Reset</button>

CSS

* { box-sizing: border-box; padding: 0; margin: 0; }
.cardList { 
    position: relative; height: 100px; width: 300px; 
    margin: 10px; border: 2px solid #33e; 
    overflow: hidden; white-space: nowrap; 
}
.card { 
    position: absolute; left: 0; top: 0; text-align: center;
    height: 100px; width: 100px; line-height: 100px;
    background-color: #99e; 
    font-family: monospace; font-size: 2em; color: #444;
    border-left: 1px solid #33e; border-right: 1px solid #33e;
}

div.controls, button { 
    margin: 10px; padding: 8px; font-family: monospace; 
}
div.controls input { 
    width: 48px; padding: 2px; text-align: center; 
    font-family: monospace;
}

JavaScript

var list = document.querySelector('.cardList'), 
    cards = document.querySelectorAll('.card'), 
    start = document.getElementById('start'), 
    stop = document.getElementById('stop'), 
    reset = document.getElementById('reset'), 
    raf, init = 0, counter = 0, lastCard, currentIteration = 0, 
    settings = { 
        'width': 100, 'height': 100, 'speed': 2, 
        'iterations': 2, 'count': cards.length 
    }
;
start.addEventListener('click', startClick);
stop.addEventListener('click', stopClick);
reset.addEventListener('click', resetClick);
initialize();

function initialize() {
    [].forEach.call(cards, function(elem, idx) {
        elem.style.left = (settings.width * idx) + 'px';
    }); 
    init = -(settings.width);
    lastCard = cards[settings.count - 1];
    counter = 0; currentIteration = 0; 
    settings.speed = +(document.getElementById('speed').value);
    settings.iterations = +(document.getElementById('iter').value);
}
function startClick() { 
    initialize(); raf = window.requestAnimationFrame(keyframes); 
}
function stopClick() { window.cancelAnimationFrame(raf); }
function resetClick() { 
    window.cancelAnimationFrame(raf); 
    document.getElementById('speed').value = '2';
    document.getElementById('iter').value = '2';
    initialize(); 
}

function keyframes() {
    var currentCard, currentLeft = 0, newLeft = 0;
    [].forEach.call(cards, function(elem, idx) {
        elem.style.left = (parseInt(elem.style.left) - settings.speed) + 'px';
    }); 
    currentCard = cards[counter];
    currentLeft = parseInt(currentCard.style.left);
    if (currentLeft <= init) {
        newLeft = parseInt(lastCard.style.left) + settings.width;
        list.appendChild(currentCard);
        currentCard.style.left =  newLeft + 'px';
        lastCard = currentCard;
        counter = (counter + 1) % settings.count; 
        if ((settings.iterations > 0) && (counter >= (settings.count - 1))) { 
            currentIteration++; 
        }
    }
    if...