【问题标题】:List rotation with limited elements具有有限元素的列表轮换
【发布时间】:2015-11-04 15:18:59
【问题描述】:

我有 div container 里面有列表(卡片)。当我将它悬停时,卡片开始移动 (translateX animation)。 containerwidth300px,元素计入container:3,每个元素width:100px

所以你可以看到容器中的 3 个元素一起overflow:hidden。我想做的是,当第三个元素之后没有元素显示 translateX 动画 -100px = 100px 空白空间时,它从列表中的 1 个元素开始紧接着最后一个,没有空白空间。

目前,我不知道如何在没有重复等的情况下完成。

这是我目前拥有的: Fiddle(悬停卡片看翻译动画)

UPD 1: 以代码和数据(卡片数量,容器大小)为例,我将尝试更好地解释我想要的内容:我的目标是建立卡片列表,按下按钮后,列表将开始移动(如示例使用 translateX 动画)一段时间(例如 translateX:12491px,动画持续时间:15s;)并停止。但问题是列表中的 crad 数量将在 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;
}

【问题讨论】:

  • 我认为如果不复制第一张卡,您将无法做到这一点。
  • 您希望在动画运行时第一个元素作为第一个项目和最后一个项目出现,对吗?
  • @Src 不清楚你在问什么。您是说您希望第一张卡片在从左侧消失时看起来从右侧移入?如果是这样,那么只要您希望卡片从一侧出现而从另一侧消失,那么不复制它是不可能的。但是,如果您有第四张卡,那么 第一张消失后,您可以使用 javascript 将其移动到队列的另一端。这很简单,但目前还不清楚您要问什么。
  • 我更新了我的问题

标签: javascript html css translate-animation


【解决方案1】:

从列表中紧跟 last 的 1 个元素开始,没有 空格

这超出了 CSS,您需要 Javascript。因为,您使用 Javascript 而不是 jQuery 标记了问题,所以我的回答仅限于纯 Javascript。看,没有 JQuery ;)

我不知道如何在没有重复的情况下完成

这是一个 DIY(自己动手)的想法..

  1. 主要技巧是显示至少比您拥有的总数少一项。如果您有 3 张卡片,则只显示 2 张。如果您有 4 张卡片,则仅显示 3 张。为什么,因为您需要在卡片消失时重新定位并在最后将其包裹回去。如果您展示的卡片数量与您所拥有的卡片数量完全相同,那么您将无法打破半张卡片并将其包装起来,您将看到一些空白区域,直到第一张卡片消失为止。你明白了吗?
  2. 不要使用translate,否则在编写脚本时您最终会将事情复杂化。保持简单。
  3. 请勿为卡片使用包装纸。为什么?因为,我们将重新定位已经消失的卡片。当我们这样做时,下一张卡片将占据它的位置并立即消失,让您的事情变得更加困难。
  4. 为简单起见,请将您的卡片排列为相对于其容器的absolute 定位。首先,让所有卡片在top:0; and left: 0; 上堆叠。
  5. 接下来连接 Javascript 以根据每张卡片的 width 定位 left 属性并线性排列它们。
  6. 使用requestAnimationFrame 控制动画。
  7. 跟踪最左边的卡片及其left 位置。当它不在视野范围内时(0 减去宽度),appendChild 这张卡片将被放到它的容器中。这会将卡片移动到卡片的末尾。此外,根据列表中的最后一张卡片将 left 属性更改为它。
  8. 仅此而已。

下面是一个演示。为了方便您进行实验,我使用了一个设置对象来保留您可以轻松调整和查看的可配置属性。仔细看代码,你会发现它很容易理解。您可以将iterations 设置为0 以使动画无限。

另外,请注意,您不需要复制或伪造卡片。尝试演示并添加任意数量的卡片。

sn-p 中的内联代码 cmets,将进一步帮助您理解每一行代码并与上述步骤相关。

片段:

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>

小提琴:http://jsfiddle.net/abhitalks/1hkw1v0w/

注意:我在演示中遗漏了一些内容。特别是,虽然卡片的宽度和高度是设置对象的一部分,但目前它是固定的。您可以轻松地使用设置对象来使卡片的尺寸也可配置。


编辑:

根据 Op 的评论

如果您想更好地控制滚动距离、持续时间和计时功能(缓动),那么您可以使用库自己实现这些功能。 Robert Penner's Easing Functionsa jQuery plugin from GSGD 是几个这样好的库。虽然你可以用纯 Javascript 来实现所有这些,但如果你使用像 jQuery 这样的库会更容易。

这里要注意的是,为了有效地做到这一点,您必须复制卡片。您可以通过多次克隆整个列表来轻松做到这一点。

虽然你没有用 jQuery 标记这个问题,但这里有一个小演示(使用 jQuery 快速完成),你可以在其中配置速度和距离。

片段 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>

小提琴 2:http://jsfiddle.net/abhitalks/c50upco5/

【讨论】:

  • 干得好。这非常接近我想要实现的目标。您能否修改代码以仅设置必须经过的像素距离和此动画的时间(如果可以像在 css 中一样设置动画计时功能)而不是速度和迭代?
  • @Src:在这种情况下,复制卡片并使用库是明智的。我使用 jQuery 添加了另一个版本,也带有可配置的参数。希望这可以帮助您朝着正确的方向前进。相信通过这些示例,您可以根据需要学习和工作,并从这里开始学习。
  • 这不是我想要的 100%,但你做得很好,给了我一些有用的东西可以玩。谢谢你,这是你的 +110。
【解决方案2】:

如果你不想修改 dom 元素,你可以利用flex-item's order property;

要做到这一点,你仍然需要一点 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>

fiddle


但是如果你可以使用JS,我建议你直接操作DOM元素的顺序,取.cards的第一个子元素,并在每个动画结束时将其附加到列表的末尾;

试试这个:

var anim;

$('.cards').mouseenter(function(){
        
    anim = setInterval(function(){
        $('.cards').append($('.card').first())
    },3000)
    	
});

$('.cards').mouseleave(function(){
    clearInterval(anim)                   
});
.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 infinite;
}

/**/
.cards{
    width:400px;
    height:100%;
    display:flex;
}


@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>

如果您希望一张卡片同时出现在卡片列表的开头和结尾,您需要对元素进行深度复制/克隆;

here's an example;

【讨论】:

    【解决方案3】:

    更新 2:

    我写了一个 jquery 插件,可以按照你想要的方式运行:

    您可以添加任意数量的卡片,现在“translateX”是随机的(脚本将随机选择最后一张卡片)

    link to the demo


    更新:

    我知道,我使用了重复,但现在我的代码适用于三张卡片:

    • 我添加了三张“假”卡
    • 每张“真实”卡片都有自己的动画
    • 一旦循环结束,“假”卡片将与真实卡片重叠(“当没有元素可显示时”如您所问)

    检查sn-p:

    .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>

    上一个答案:

    这是你想要达到的目标吗?

    我不认为你可以做到没有重复...

    如果没有,您能更好地解释一下您在这里想要达到的目标吗?

    [删除的代码片段]

    【讨论】:

    • 一般来说 - 这更接近我想要实现的目标。但我想在有 6 个元素的列表中设置例如 translateX 为 15k...
    • 是的,我读得太晚了您的更新...我认为 javascript 方法可能更容易...
    • 这是我想要实现的最接近的版本。查看 UPD 2,我发现 cs:go 游戏有我想要的动画
    【解决方案4】:

    Here 与您提到的效果相同,只是对您的 CSS 进行了一些调整,并且 jQuery 提供了帮助。

    CSS

    更改 translateX 动画的选择器,以便在其直接父级悬停时应用于每个 .card 框,而不是 .cards(它是.cards)。这是因为您希望卡片向左移动,而不是移动时它们出现的窗口

    也就是说,

    .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 时,.cards 的直接父级(即.cards div)的宽度都会更新,因此.cards div 的宽度足以容纳所有.cards,并且因此,请确保在 translation 动画完成时,每张 卡片 相互推挤并保持在一条线上。

    【讨论】:

    • 这不是我想要的。请阅读 UPD 1。 translateX 可以大于 10k
    【解决方案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

    如果您使用的是鼠标悬停,您可能需要等待 transitionend 事件才能再次触发。

    【讨论】:

      【解决方案6】:

      Check out this demo

      这里我使用了 JQuery,你可以使用两个变量来配置你的动画

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

      设置变量后,在卡片的点击事件上执行以下操作:-

      1. 根据 translateX 获取所需的步骤数
      2. 循环计算步数
      3. 在每个循环(每一步)内将卡片向左移动 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'}); 
           });
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-13
        • 2013-09-28
        • 1970-01-01
        • 2021-01-13
        相关资源
        最近更新 更多