Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List rotation with limited elements

I have div container with list (cards) inside. When I hover it, cards start to moving (translateX animation). container's width is 300px, elements count in container:3, each element width:100px.

So you can see 3 elements in container together overflow:hidden. What I want to make?, is that when there is no element to show translateX animation -100px = 100px blank space after third element, it start from 1 elements in the list immediately after last, with no blank space.

For now, I have no idea how it could be done without duplicates and etc.

Here is what I have at the moment: Fiddle (Hover cards to see translation animation)

UPD 1: The code and data (cards count, container size) was taken for example, i'll try to explain better what i want: My goal is to built list of cards and after button was pressed, the list will start moving (like in example with translateX animation) for some time (for example translateX: 12491px, animation-duration: 15s;) and stops. But problem is that amount of crads in the list would be in range of 3-40 cards (each card is 100px width & height). So, when i'll set translateX: 12491px for example, it will be out of range and after the last card in the list would appear blank space. I want first and last card to be tied somehow and after the last card immediately appears first card in the list and etc.. Maybe i am searching for solution in a wrong way, but i guess you understand the main idea.

UPD 2: I found that cs:go uses animation that i wanted to write on html\css\js. Here is video: youtube.com

html:

<div class="container">
    <div class="cards">
        <div class="card">
        1
    </div>
    <div class="card">
        2
    </div>
    <div class="card">
        3
    </div>
    </div>
</div>

css:

.container
{
    width:300px;
        height: 100px;
    border: 2px solid black;
    overflow: hidden;
}
.card
{
    float:left;
    height: 100px;
    width: 100px;
    background-color:blue;
    box-sizing: border-box;
    border: 2px solid red;
    color: white;
    font-size: 23px;
}
.cards:hover
{
    transform: translateX(-100px);
    transition-duration: 3s;
    animation-duration: 3s;
    animation-fill-mode: forwards;
}
like image 722
Src Avatar asked Oct 29 '15 10:10

Src


People also ask

How do you rotate a single linked list?

To rotate the linked list, we need to change the next pointer of kth node to NULL, the next pointer of the last node should point to previous head node, and finally, change the head to (k+1)th node. So we need to get hold of three nodes: kth node, (k+1)th node, and last node.


1 Answers

start from 1 elements in the list immediately after last, with no blank space

This is beyond CSS and you will need Javascript for that. Because, you have tagged the question with Javascript and not jQuery, my answer would be limited to pure Javascript only. Look ma, no JQuery ;)

I have no idea how it could be done without duplicates

Here is a DIY (do it yourself) idea..

  1. The main trick is to show at least one item less than the total you have. If you have 3 cards, show only 2. If you have 4 cards, show only 3. Why, because you need to re-position a card when it goes out of view and wrap it back at the end. If you show exactly the same number of cards that you have, then you cannot break half-a-card and wrap it and you will see some blank space until the first one goes out of view. You get the idea?
  2. Do not use translate or you will end up complicating things for yourself while scripting it out. Keep things simple.
  3. Do not use a wrapper for your cards. Why? Because, we will be re-positioning the cards which have gone out of view. When we do that, the next card will take up its place and immediately go out of view making things further difficult for you.
  4. To keep things simple, arrange your cards with absolute positioning relative to its container. To start with, let all cards stack up at top:0; and left: 0;.
  5. Next wire-up Javascript to position the left property based on the width of each card and arrange them linearly.
  6. Use requestAnimationFrame to control the animation.
  7. Keep track of the left-most card and its left position. When this goes out of view (which is 0 minus width), appendChild this card to its container. This will move the card to the end of cards. Also, change the left property to it based on the last card in the list.
  8. That' all there is to it.

Below is a demo. To make it easy for you to experiment, I have used a settings object to keep the configurable properties which you can easily tweak and see. Look closely at the code and you will find it simple to understand. You can set the iterations settings to 0 to make the animation infinite.

Also, note that you do not need to duplicate or fake the cards. Try the demo and add as many cards you want to.

The inline code comments in the snippet, will further help you understand each line of code and relate to the steps above.

Snippet:

var list = document.querySelector('.cardList'), // cache the container
    cards = document.querySelectorAll('.card'), // cache the list of cards
    start = document.getElementById('start'),   // buttons
    stop = document.getElementById('stop'), 
    reset = document.getElementById('reset'), 
    raf, init = 0, counter = 0, lastCard, currentIteration = 0, // general purpose variables
    settings = { // settings object to help make things configurable
        'width': 100, 'height': 100, 'speed': 2, 
        'iterations': 2, 'count': cards.length 
    }
;
start.addEventListener('click', startClick); // wire up click event on buttons
stop.addEventListener('click', stopClick);
reset.addEventListener('click', resetClick);
initialize(); // initialize to arrange the cards at start

function initialize() {
    // loop thru all cards and set the left property as per width and index position
    [].forEach.call(cards, function(elem, idx) { 
        elem.style.left = (settings.width * idx) + 'px';
    }); 
    init = -(settings.width); // initialize the view cutoff
    lastCard = cards[settings.count - 1]; // identify the last card
    counter = 0; currentIteration = 0; // reset some counters
    settings.speed = +(document.getElementById('speed').value);
    settings.iterations = +(document.getElementById('iter').value);
}
function startClick() { 
    initialize(); raf = window.requestAnimationFrame(keyframes); // start animating
}
function stopClick() { window.cancelAnimationFrame(raf); } // stop animating
function resetClick() { // stop animating and re-initialize cards to start again
    window.cancelAnimationFrame(raf); 
    document.getElementById('speed').value = '2';
    document.getElementById('iter').value = '2';
    initialize(); 
}

// actual animation function
function keyframes() {
    var currentCard, currentLeft = 0, newLeft = 0;
    // iterate all cards and decrease the left property based on speed
    [].forEach.call(cards, function(elem, idx) {
        elem.style.left = (parseInt(elem.style.left) - settings.speed) + 'px';
    }); 
    currentCard = cards[counter]; // identify left-most card
    currentLeft = parseInt(currentCard.style.left); // get its left position
    if (currentLeft <= init) { // check if it has gone out of view
        // calculate position of last card
        newLeft = parseInt(lastCard.style.left) + settings.width;
        list.appendChild(currentCard); // move the card to end of list
        currentCard.style.left = newLeft + 'px'; // change left position based on last card
        lastCard = currentCard; // set this as the last card for next iteration
        counter = (counter + 1) % settings.count; // set the next card index
        if ((settings.iterations > 0) && (counter >= (settings.count - 1))) { 
            currentIteration++; // check settings for repeat iterations
        }
    }
    if (currentIteration >= settings.iterations) { return; } // when to stop
    raf = window.requestAnimationFrame(keyframes); // request another animation frame
};
* { 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; }
<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>

Fiddle: http://jsfiddle.net/abhitalks/1hkw1v0w/

Note: I have left out a few things in the demo. Especially, although width and height of the cards is part of the settings object, but currently it left fixed. You can easily use the settings object to make the dimensions of the cards configurable as well.


Edit:

(as per Op's comment)

If you want a greater control over distance to scroll, duration and timing-functions (easing), then you could implement those yourself using a library. A couple of such good libraries are the Robert Penner's Easing Functions and a jQuery plugin from GSGD. Although you can implement all of that with pure Javascript, it would be easier if you use a library like jQuery.

Catch here is that in order to do so effectively, you must then duplicate the cards. You can do so easily by cloning the entire list a couple of times.

Although you have not tagged this question with jQuery, here is a small demo (using jQuery to get it done quickly) where you can configure the speed and the distance.

Snippet 2:

var $cardList 	= $('.cardList').first(), 
    $cards 		= $('.card'), 
    $speed 		= $('input[name=speed]'), 
    width 		= 100, 
    randomize 	= true, 
    distance 	= 20 * width 
;

for (var i = 0; i < 50; i++) {
    $cards.clone().appendTo($cardList);
}

function spin() {
    var newMargin = 0, newDistance = distance, 
        speed = +($speed.filter(':checked').val());
    if (randomize) {
        newDistance = Math.floor(Math.random() * $cards.length * 5);
		newDistance += $cards.length * 5;
        newDistance *= width;
    } 
	newMargin = -(newDistance);
    $cards.first().animate({
        marginLeft: newMargin
    }, speed);
}

$('#spin').click(function() {
    $cards.first().css('margin-left', 0);
    spin();
    return false;
});
* { box-sizing: border-box; padding: 0; margin: 0; }
.cardList { 
    height: 100px; width: 302px; position: relative;
    margin: 10px; border: 1px solid #33e; 
    overflow: hidden; white-space: nowrap; 
}
.card { 
    display: inline-block; 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;
}
.cardList::before, .cardList::after {
    content: ''; display: block; z-index: 100;
    width: 0px; height: 0px; transform: translateX(-50%);
	border-left: 8px solid transparent;
	border-right: 8px solid transparent;    
}
.cardList::before {
    position: absolute; top: 0px; left: 50%;
	border-top: 12px solid #33e;
}
.cardList::after {
    position: absolute; bottom: 0px; left: 50%;
	border-bottom: 12px 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; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="controls">
    <label>Speed: </label>
    &nbsp;|&nbsp;
    <label><input name="speed" type="radio" value='6000' />Slow</label>
    <label><input name="speed" type="radio" value='5000' checked />Medium</label>
    <label><input name="speed" type="radio" value='3000' />Fast</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="spin">Spin</button>

Fiddle 2: http://jsfiddle.net/abhitalks/c50upco5/

like image 70
Abhitalks Avatar answered Sep 28 '22 06:09

Abhitalks