JS: как синхронизировать время нескольких анимаций?

Когда я динамически добавляю элемент, который анимируется с помощью JS, как я могу синхронизировать их на той же временной шкале, как показано здесь: http://youtube.com/watch?v=AGiTmjFHs8M&t=9m23s ? Я видел учебник, который показал преимущество использования анимации JS по сравнению с CSS, поскольку они наследуют одну и ту же временную шкалу.

<div class="body"></div>
<button onclick="addItem()">Add</button>

function addItem() {
   let body = document.querySelector('.body');
   let newEl = document.createElement('div');
   newEl.innerText = "I am a new Item";
   newEl.animate([
      { 
          transform: 'translate(0px, 0px)',
          transform: 'translate(500px, 500px)',
      }
      ], 
      {
         duration: 2000,
         iterations: Infinity,
      });

   body.appendChild(newEl);
}

person RicardoAlvveroa    schedule 30.12.2020    source источник
comment
Я не уверен, что понимаю этот вопрос how do I get them to be in sync on the same timeline?. Не могли бы вы уточнить?   -  person codemonkey    schedule 30.12.2020
comment
@codemonkey конечно. Я видел в этом видео youtube.com/watch?v=AGiTmjFHs8M&t=9m23s в 9:23 это показывает, как анимации JS, в отличие от анимации CSS, могут быть синхронизированы на одной и той же временной шкале, поскольку все они наследуют одну и ту же временную шкалу.   -  person RicardoAlvveroa    schedule 30.12.2020
comment
.animate() — это оболочка, которая автоматически создает и запускает анимацию. Вместо этого создайте и запустите анимацию вручную. См. API веб-анимации и Анимация.   -  person Ouroborus    schedule 30.12.2020
comment
Я так понимаю, скажите мне, является ли это желаемым поведением: каждый раз, когда вы нажимаете кнопку «Добавить», вы хотите, чтобы новый элемент «Я новый элемент» синхронизировался со всеми предыдущими, что означает, что вы хотите, чтобы они накладывались друг на друга?   -  person codemonkey    schedule 30.12.2020
comment
@codemonkey да, правильно   -  person RicardoAlvveroa    schedule 30.12.2020
comment
Когда вы запускаете первую анимацию, следите за временем в переменной. Всякий раз, когда кто-то нажимает «Добавить», вычисляйте, когда будет следующая граница тика (подсказка: каждые кратные 2000 мс после начального времени начала, которое вы отслеживали), определяйте, сколько мс это будет в будущем (будет гарантировано ‹= 2000 мс) и используйте setTimeout(), чтобы фактически добавить этот элемент через столько секунд. Кроме того, вы видели: stackoverflow.com/a/57776076/378779   -  person kmoser    schedule 30.12.2020


Ответы (1)


Если все ваши объекты Animation имеют один и тот же duration и вы хотите, чтобы они начинались и заканчивались одновременно , вы можете просто установить iterationStart EffectTiming вашего новый объект Animation для ComputedTiming .progress значение уже работающего объекта.
Однако будьте осторожны, вы должны дождаться готовности нового объекта анимации, прежде чем получить вычисленное значение предыдущего объекта, иначе вы опоздаете на один кадр. Для этого вы можете просто дождаться обещания animation.ready .

Чтобы получить предыдущий объект анимации, вы можете либо сохранить его при создании через Element.animate(), или вы можете получить доступ к набору текущих анимаций в документе через document.getAnimations() и выберите его оттуда.

let i = 0;
async function addItem() {

  i++;
  const body = document.querySelector(".body");
  const newEl = document.createElement("div");
  newEl.classList.add("item");
  newEl.textContent = "I am a new Item " + i;

  // get a previous Animation if any
  const previous_animation = document.getAnimations()[0];

  // create the new Animation object
  // slightly offset on the x axis only
  // to check if they are indeed in sync
  const anim = newEl.animate([{
    transform: "translate(" + (i * 10) + "px, 0px)",
    transform: "translate(" + (i * 10 + 250) + "px, 250px)",
  }], {
    duration: 2000,
    iterations: Infinity
  });
  // when it's ready to start
  await anim.ready;

  // get the current progress of the first Animation object
  const computed_timing = previous_animation?.effect.getComputedTiming();
  if( computed_timing ) {
    // update our new Animation object to the same progress value
    anim.effect.updateTiming( {
      iterationStart: computed_timing.progress
    });
  }
  
  body.appendChild(newEl);

}
.item {
  position: absolute;
}
<div class="body"></div>
<button onclick="addItem()">Add</button>


Обратите внимание, что, как указал пользователь brianskold в комментарий ниже, startTime свойство анимации может быть установлено на более раннее время. Это приведет к тому, что анимация началась в это время.

Итак, для синхронизации двух анимаций это, вероятно, лучший способ:

let i = 0;
function addItem() {

  i++;
  const body = document.querySelector(".body");
  const newEl = document.createElement("div");
  newEl.classList.add("item");
  newEl.textContent = "I am a new Item " + i;

  // get a previous Animation if any
  const previous_animation = document.getAnimations()[0];

  // create the new Animation object
  // slightly offset on the x axis only
  // to check if they are indeed in sync
  const anim = newEl.animate([{
    transform: "translate(" + (i * 10) + "px, 0px)",
    transform: "translate(" + (i * 10 + 250) + "px, 250px)",
  }], {
    duration: 2000,
    iterations: Infinity
  });

  if( previous_animation ) {
    // set the startTime to the same
    // as the previously running's one
    // note this also forces anim.ready to resolve directly
    anim.startTime = previous_animation.startTime;
  }

  body.appendChild(newEl);

}
.item {
  position: absolute;
}
<div class="body"></div>
<button onclick="addItem()">Add</button>

person Kaiido    schedule 30.12.2020
comment
Также обратите внимание, что вы можете установить startTime новой анимации. Это позволяет вам синхронизировать новую анимацию с существующей и заставить обещание ready выполняться немедленно. На самом деле, Firefox использует эту технику для синхронизации пульсирующей анимации при загрузке вкладок: " rel="nofollow noreferrer">searchfox.org/mozilla-central/rev/ - person brianskold; 31.12.2020
comment
@brianskold О, я не правильно понял, что делает установка прошедшего startTime! Итак, если я теперь лучше понимаю, он действует так, как будто он работает с этого времени? И учитывая, что он постоянен в качестве геттера, не нужно беспокоиться, когда он действительно входит в граф, верно? (Теперь интересно, как я пропустил это, когда пример MDN это точно случай ОП ...). Спасибо за примечание, возможно, вы могли бы написать свой собственный ответ, чтобы получить здесь заслуженные воображаемые баллы, или я отредактирую свой, чтобы включить его, когда у меня будет время. - person Kaiido; 31.12.2020
comment
Не стесняйтесь редактировать свой ответ, чтобы добавить информацию о startTime. Способ работы animate() или play() заключается в том, что они асинхронны, поэтому они могут разрешить startTime только тогда, когда анимация готова к запуску, чтобы обеспечить плавный запуск анимации. Однако, когда вы пытаетесь синхронизировать анимацию, вам не нужен плавный запуск, поэтому установка startTime подходит. (Причина, по которой код Firefox, на который я ссылался, использует requestAnimationFrame, заключается просто в пакетном обновлении стиля.) - person brianskold; 01.01.2021