Перемещение списка с ограниченными элементами
У меня есть 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>
|
<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'});
});
}