Перемещение списка с ограниченными элементами

У меня есть div container со списком (картами) внутри. Когда я нахожу его, карты начинают двигаться (translateX animation). container width 300px, элементы подсчитываются в container:3, каждый элемент width:100px.

Итак, вы можете видеть 3 элемента в контейнере вместе overflow:hidden. Что я хочу сделать?, то есть, когда нет элемента для отображения translateX animation -100px = 100px пустого пространства после третьего элемента, он начинается с 1 элемента в списке сразу после последнего, без пробелов пространство.

Пока я не знаю, как это можно сделать без дубликатов и т.д.

Вот что я имею в данный момент: Fiddle (Наведите на карту, чтобы увидеть анимацию перевода)

UPD 1: Например, код и данные (количество карт, размер контейнера) были взяты, я попытаюсь объяснить, что я хочу: моя цель - создать список карточек и после нажатия кнопки список начнет двигаться (как в примере с анимацией translateX) в течение некоторого времени (например, translateX: 12491px, продолжительность анимации: 15 с;) и останавливается. Но проблема в том, что количество крадов в списке будет в диапазоне 3-40 карт (каждая карта имеет ширину и высоту 100 пикселей). Поэтому, когда я установлю translateX: 12491px, например, он будет за пределами допустимого диапазона и после того, как последняя карта в списке будет выглядеть пустым. Я хочу, чтобы первая и последняя карта была привязана каким-то образом, и после того, как последняя карта сразу же отобразит первую карту в списке и т.д. Может быть, я искал решение неправильно, но, думаю, вы понимаете основную идею.

UPD 2: Я обнаружил, что cs: go использует анимацию, которую я хотел написать на html\css\js. Вот видео: 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;
}

Ответы

Ответ 1

начать с 1 элемента в списке сразу после последнего, без пустое пространство

Это выше CSS, и для этого вам понадобится Javascript. Потому что вы отметили вопрос с помощью Javascript, а не jQuery, мой ответ будет ограничен только Javascript. Посмотрите ma, нет JQuery;)

Я понятия не имею, как это можно сделать без дубликатов

Вот идея DIY (сделай сам)..

  • Основной трюк состоит в том, чтобы показать хотя бы один элемент меньше, чем у вас. Если у вас 3 карты, покажите только 2. Если у вас 4 карты, покажите только 3. Почему, потому что вам нужно переставить карточку, когда она выходит из поля зрения, и заверните ее в конце. Если вы показываете точно такое же количество карт, что и у вас, вы не можете сломать полукарту и обернуть ее, и вы увидите пустое место, пока первый не исчезнет из поля зрения. У вас есть идея?
  • Не используйте translate или вы в конечном итоге усложняете что-то для себя при написании скрипта. Держите вещи простыми.
  • Не используйте обертку для своих карт. Зачем? Потому что мы будем переставлять карты, которые вышли из поля зрения. Когда мы это сделаем, следующая карта займет свое место и сразу же выйдет из поля зрения, что еще больше осложнит вам.
  • Чтобы все было просто, расположите свои карты с позиционированием absolute относительно его контейнера. Для начала, пусть все карты складываются в top:0; and left: 0;.
  • Следующий подключаемый Javascript, чтобы разместить свойство left на основе width каждой карты и упорядочить их линейно.
  • Используйте requestAnimationFrame для управления анимацией.
  • Следите за самой левой картой и ее позицией left. Когда это выходит из поля зрения (это 0 минус ширина), appendChild эту карту в контейнер. Это переместит карту в конец карточек. Кроме того, измените свойство left на него на основе последней карты в списке.
  • Это все, что есть.

Ниже приведена демонстрация. Чтобы вам было легко экспериментировать, я использовал объект настроек, чтобы сохранить настраиваемые свойства, которые вы можете легко настроить и посмотреть. Посмотрите внимательно на код, и вы найдете его простым в понимании. Вы можете установить для параметров iterations значение 0, чтобы сделать анимацию бесконечной.

Также обратите внимание, что вам не нужно дублировать или подделывать карты. Попробуйте демонстрацию и добавьте столько карт, которые вы хотите.

Встроенные комментарии кода в фрагменте, помогут вам понять каждую строку кода и относятся к приведенным выше шагам.

Отрывок:

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>

Ответ 2

Обновление 2:

Я написал плагин jquery, который может действовать так, как вы хотите:

вы можете добавить столько карт, сколько хотите, прямо сейчас "translateX" является случайным (script будет выбирать случайную окончательную карту)

ссылка на демонстрацию


Update:

Я знаю, я использовал дубликаты, но теперь мой код работает на трех картах:

  • Я добавил три "поддельные" карты.
  • Каждая "настоящая" карта имеет свою собственную анимацию.
  • "поддельные" карты будут перекрываться реальными, как только их цикл закончен ( "когда нет элемента для показа", как вы просили)

проверьте фрагмент:

.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 {
  width: 600px;
}
.container:hover .card1{
  animation: 1600ms slide1 infinite linear;
}
.container:hover .card2{
  animation: 1600ms slide2 infinite linear;
}
.container:hover .card3{
  animation: 1600ms slide3 infinite linear;
}
.fakecard{z-index:-1000;}
.container:hover .fakecard{
  animation: 1600ms fakeslide infinite linear;
}

@keyframes slide1 {
  0% { transform: translateX(0px); }
  33% { transform: translateX(-100px); }
  33.1% { transform: translateX(+200px); }
  100% { transform: translateX(0px); }
}
@keyframes slide2 {
  0% { transform: translateX(0px); }
  66% { transform: translateX(-200px); }
  66.1% { transform: translateX(100px); }
  100% { transform: translateX(0px); }
}
@keyframes slide3 {
  0% { transform: translateX(0px); }
  99% { transform: translateX(-300px); }
  99.1% { transform: translateX(+300px); }
  100% { transform: translateX(0px); }
}
@keyframes fakeslide {
  0% { transform: translateX(0px); }
  99% { transform: translateX(-300px); }
  99.1% { transform: translateX(+300px); }
  100% { transform: translateX(0px); }
}
<div class="container">
  <div class="cards">
    <div class="card card1">
      1
    </div>
    <div class="card card2">
      2
    </div>
    <div class="card card3">
      3
    </div>
    <div class="card fakecard">
      1 (fake)
    </div>
    <div class="card fakecard">
      2 (fake)
    </div>
    <div class="card fakecard">
      3 (fake)
    </div>
  </div>
</div>

Ответ 3

Если вы не хотите изменять элементы dom, вы можете использовать свойство flex-item order;

для этого вам все равно понадобится немного JS, чтобы добавить это свойство после окончания анимации;

Я также изменил анимацию вместо перехода, чтобы автоматически сбрасывать свойство преобразования в конце анимации.

$('.cards').mouseenter(function() {
  setTimeout(function() {
    $('.card').first().css("order", "2");
  }, 3000);
});

$('.cards').mouseleave(function() {
  $('.card').first().css("order", "-1");
});
.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;
  flex: 0 0 25%;
}
.cards:hover {
  animation: trans 3s;
}
/**/

.cards {
  width: 400px;
  height: 100%;
  display: flex;
  transition: transform 3s;
}
@keyframes trans {
  0% {
    transform: translateX(0)
  }

  100% {
    transform: translateX(-100px)
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>

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

Ответ 4

Здесь - тот же эффект, о котором вы говорили, с небольшой настройкой на вашем CSS и полезной рукой из JQuery.

CSS

Измените селектор для анимации translateX, применяемый в каждом из полей .card, когда их непосредственный родитель зависает, а не .cards (который является непосредственным родителем .card s). Это потому, что вы хотите, чтобы карты двигались влево, а не в окно, через которое они появляются при движении.

То есть

.cards:hover .card {   
    transform: translateX(-100px);
    transition-duration: 1.5s;
    animation-duration: 1.5s;
    animation-fill-mode: forwards;
}

JQuery

var $container = $('.container');
var cardWidth = 100;

$container.on('mouseenter', function (e) {
    e.preventDefault();
    var $card0Clone = $('.card').eq(0).clone();    // clone of the first .card element
    $('.cards').append($card0Clone);
    updateWidth();
});
$container.on('mouseleave', function (e) {
    e.preventDefault();
    var $cards = $('.card');
    $cards.eq(0).remove();    // remove the last .card element
});

function updateWidth() {
    $('.cards').width(($('.card').length) * cardWidth);  // no of cards in the queue times the width of each card would result in a container fit enough for all of them
}

Обозначенный код

По мере перемещения указателя мыши создается клон первой карты и добавляется к концу коллекции карт. Кроме того, когда вы выталкиваете мышь из области зависания, исходный .card (который был клонирован ранее) будет удален из головы очереди - следовательно, создавая циклический эффект.

Реальный трюк, однако, с функцией updateWidth. Каждый раз, когда мышь вводится в .container, изменяется ширина исходного родителя .card s (т.е. .cards div), так что .cards div достаточно широк, чтобы соответствовать всем .card s, и поэтому убедитесь, что каждая из карт нажимается друг на друга и остается в одной строке во время выполнения анимации трансляции.

Ответ 5

Вот простой метод, который манипулирует Dom для создания желаемого эффекта.

JavaScript:

document.querySelector('.cards').addEventListener('mousedown', function(e) {
if (e.clientX < (this.offsetWidth >> 1)) {
    this.appendChild(this.removeChild(this.firstElementChild));
} else {
    this.insertBefore(this.lastElementChild, this.firstElementChild);
}}); 

то в css используйте селектор nth-of-type для размещения элементов по мере необходимости. Вот ваш fiddle

Если вы используете мышь, вам может потребоваться дождаться события перехода до повторного запуска.

Ответ 6

Проверьте эту демонстрацию

Здесь я использовал JQuery, вы можете настроить анимацию, используя две переменные

var translateX = 1000; //adjust the whole distance to translate
var stepSpeed = 100;   //adjust the speed of each step transition in milliseconds

После установки ваших переменных в событии кликов на картах сделайте следующее: -

  • Получить количество необходимых шагов на основе translateX
  • Петля для количества шагов
  • Внутри каждого цикла (каждый шаг) переместите карты на 1 шаг влево, затем поместите первую карту в конец карт, чтобы сформировать подключенный цикл, затем верните карты в исходное положение.

Вот код:

var stepsNumber = translateX/100;
for(var i=0; i< stepsNumber; i++)
{
    $('.cards').animate({'left' : -100}, stepSpeed,function(){
         $('.cards div:last').after($('.cards div:first'));
         $('.cards').css({'left' : '0px'}); 
     });
}