jQuery: анимация происходит одновременно, а не подряд с итерацией .each()

Я перебираю массив объектов класса «.mini». Для каждого объекта каждый объект будет ждать 500 миллисекунд (функция setTimeout) перед перемещением (вызов функции go). Например,

function iterate() {
    $(".mini").each(function(index)
    {
        var element = $(this);
        setTimeout(function() {go(element)}, 500);
    });
}

function go(element) {
    element.animate({left: "500px"},200);
}

Однако анимация для каждого элемента происходит одновременно (все 32 объекта .mini перемещаются одновременно), а не один за другим. Как может быть так, что первый начинает движение через 500 секунд от времени начала, второй - через 1000 от времени начала и т.д.?


person Y. Moondhra    schedule 07.05.2018    source источник
comment
[...] через 500 секунд [...], я полагаю, вы имели в виду миллисекунды, верно?   -  person Alexis Wilke    schedule 07.05.2018


Ответы (3)


Вы должны умножить 500 на index + 1, чтобы первое произошло в 500ms, второе в 1000ms и т. д.

setTimeout(function() {go(element)}, 500 * (index + 1));

Прямо сейчас вы перебираете их все и говорите каждому из них 500ms from now, do this. Итак, 500ms с этого момента каждый из них делает это.

person dave    schedule 07.05.2018
comment
Супер просто и эффективно. Другие ответы пытаются обеспечить правильное время при цепочке, но я подозреваю, что человеческий глаз не заметит миллисекунду или две перекрытия/отключения. - person random_user_name; 07.05.2018
comment
Я думаю, учитывая вопрос Как может быть так, что первый начинает движение через 500 секунд от времени начала, второй начинается через 1000 от времени начала и т. д.? - другие, на самом деле, с большей вероятностью не запустятся в нужное время (если каждый берет 1ms для выполнения своей логики и планирования следующего, то после 1000 элементов мы отстаем на секунду). - person dave; 07.05.2018
comment
Но заголовок вопроса требует, чтобы они происходили друг за другом; подразумевающее отношение. Вы правы, могут быть логические накладные расходы, но они будут происходить один за другим, а не допускать перекрытия. - person zero298; 07.05.2018
comment
это правда, есть некоторая двусмысленность относительно того, какой из них он действительно хочет. в конце концов, это не имеет большого значения, так как анимация составляет 200 мс, а задержка между ними составляет 500 мс. - person dave; 07.05.2018
comment
Я действительно смущен сейчас. Это 500-animation(200)-500-animation(200)-500-... или 500-animation(200)-300-animation(200)-300-...? - person ibrahim mahrir; 07.05.2018
comment
Вау, спасибо! Отличное решение. Просто и эффективно, согласен @cale_b - person Y. Moondhra; 07.05.2018

Используйте Promises и reduce. Подождите, чтобы начать следующую анимацию, пока не завершится предыдущая.

Если вы хотите подождать, чтобы начать начальную анимацию, оберните всю цепочку в setTimeout().

Array.from(document.querySelectorAll("ul > li")).reduce((prev, curr) => {
  return prev.then(function() {
    return new Promise((resolve, reject) => {
      $(curr).animate({
        opacity: 0.2
      }, 1000, function() {
        resolve();
      });
    });
  });
}, Promise.resolve());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
  <li>Hello</li>
  <li>Hello</li>
  <li>Hello</li>
  <li>Hello</li>
  <li>Hello</li>
  <li>Hello</li>
</ul>

person zero298    schedule 07.05.2018

Все они происходят одновременно, потому что все setTimeout вызываются внутри each, что не занимает много времени, чтобы зациклить список элементов (всего несколько миллисекунд). Если вы хотите связать их в цепочку, используйте обратный вызов complete для jQuery#animate, чтобы сигнализировать о следующей анимации для Начало:

function iterate() {
    var $list = $(".mini"),                                // get the whole list of elements
        i = 0;                                             // i is the index of the currently animated element from list

    function next() {                                      // the function that when called will get the current element from list (if exists) and starts that element's animation
        if(i < $list.length) {                             // if there is still un-animated elements in $list
            setTimeout(function() {                        // animate the current element
                go($list.eq(i), next);                     // specify that next will be called when the current element's animation is done
            }, 500);
            i++;                                           // increment i of course
        }
    }

    next();                                                // call next to start the magic
}

function go(element, complete) {                           // go will take an element to be animated, and a function that will be called when that animation is done
    element.animate({left: "500px"}, 200, complete);       // simply call animate with that additional function (see jQuery#animate docs)
}
person ibrahim mahrir    schedule 07.05.2018
comment
Правильно ли list.eq(0)? Я думаю, что вам не хватает $, и он должен использовать i, верно? так что $list.eq(i) может быть? - person Alexis Wilke; 07.05.2018
comment
Отличное решение! Благодарю вас! - person Y. Moondhra; 07.05.2018