Перезапустите анимацию CSS с помощью javascript после завершения предыдущей

Я пытался использовать новую функцию animate() для перезапуска анимации CSS. Я использую Chrome 48, но мне интересно, возможно ли это прямо сейчас.

Анимация должна быть просто растущим и уменьшающимся квадратом. Как видите, я пробовал несколько вещей, но ничего не сработало:

var elem = document.querySelector(".box");

document.addEventListener("click", function() {
  elem.classList.add("close");
  elem.animate(); //not working
  elem.animate("grow"); //not working
  elem.animate([{
    transform: 'scale(1)'
  }, {
    transform: 'scale(0)'
  }], 1500); //not working
});
.box {
  width: 200px;
  height: 200px;
  background: #099;
  animation: grow 1s ease;
  transform-origin: 0 0;
}
.close {
  animation: grow 1s ease reverse;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<div class="box"></div>

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

Редактировать: я заметил, что это работает, когда я нажимаю, пока квадрат все еще растет!

Edit2: я обнаружил, что если вы просто добавите свойство reverse, анимация не запустится, но если вы измените время на другое значение или имя анимации, она запустится снова!


person Vandervals    schedule 04.02.2016    source источник
comment
вы можете сделать это с помощью чистого CSS, но из вашего вопроса я не уверен, что вы пытаетесь создать бесконечный цикл animation-iteration-count:infinite;   -  person Aaron    schedule 04.02.2016
comment
Нет, не бесконечно, это скорее упрощенный пример, на самом деле это должно происходить при срабатывании события. я обновил пример   -  person Vandervals    schedule 04.02.2016
comment
@Vandervals: Что именно должно произойти, когда событие сработает? Должен ли квадрат сжиматься (анимироваться), а затем снова расти после завершения сжатия (или) должен ли квадрат немедленно привязываться к scale(0), а затем снова расти?   -  person Harry    schedule 04.02.2016
comment
@ Гарри, квадрат должен увеличиваться при загрузке, а когда происходит событие, он должен уменьшаться и оставаться таким.   -  person Vandervals    schedule 04.02.2016
comment
@Vandervals: Хорошо, я могу дать решение для этого, но прежде чем я задам еще один вопрос. Если он сожмется и останется таким, то вы не сможете выполнять дальнейшие действия, потому что у него не будет высоты/ширины. Это нормально (или) это просто минимальный пример?   -  person Harry    schedule 04.02.2016
comment
@ Гарри, это всего лишь пример, реальный элемент будет меняться в зависимости от ввода фокуса и событий размытия.   -  person Vandervals    schedule 04.02.2016


Ответы (3)


То, что вы упомянули в последнем абзаце вашего вопроса, верно. После того, как анимация установлена ​​для элемента и полностью выполнена, браузер/UA продолжает запоминать ее состояние. Эта анимация не может быть перезапущена (в том же или обратном направлении), если исходная анимация не будет удалена хотя бы на долю секунды.

Для этого нам нужно ввести задержку (тайм-аут) или принудительно перерисовать, потому что без этого, если мы удалим и добавим только классы в рамках одного и того же блокирующего вызова, то UA не увидит никакой разницы. Он будет продолжать думать, что анимация никогда не удалялась.

Для повторного запуска одной и той же анимации:

Чтобы снова запустить ту же анимацию, когда в любом месте документа сделан щелчок, просто удалите существующий класс анимации и снова добавьте его после задержки, как в приведенном ниже фрагменте.

var elem = document.querySelector(".box");

document.addEventListener("click", function(e) {
  refireAnim("open");
});

function refireAnim(anim) {
  elem.classList.remove(anim);
  setTimeout(function() {
    elem.classList.add(anim);
  }, 0);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<div class="box open"></div>


Для повторного запуска одной и той же анимации в другом направлении:

Чтобы снова запустить ту же анимацию, но в другом направлении, удалите и добавьте класс анимации после задержки, как в приведенном выше фрагменте, а также измените направление.

var elem = document.querySelector(".box")
  forward = true;

document.addEventListener("click", function() {
  refireAnim("open");
  if (forward) { /* change the direction based on state */
  	elem.style["animation-direction"] = "reverse"; 
  }
  else {
  	elem.style["animation-direction"] = null;
  }  
  forward = !forward; /* change the state */
});

function refireAnim(anim) {
  elem.classList.remove(anim);
  setTimeout(function() {
    elem.classList.add(anim);
  }, 0);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease forwards;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class="box open"></div>

Или, как вы указали, принудительно перерисуйте после удаления класса, но перед его повторным добавлением. (Кажется, это позволяет избежать включения вспышки)

var elem = document.querySelector(".box")
  forward = true;

document.addEventListener("click", function() {
  refireAnim("open");
  if (forward) { /* change the direction based on state */
  	elem.style["animation-direction"] = "reverse"; 
  }
  else {
  	elem.style["animation-direction"] = null;
  }  
  forward = !forward; /* change the state */
});

function refireAnim(anim) {
  elem.classList.remove(anim);
  elem.clientHeight;
  elem.classList.add(anim);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease forwards;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js"></script>
<div class="box open"></div>


Для переключения одной анимации на другую:

Создайте два класса, один из которых предназначен для анимации открытия, а другой — для анимации закрытия, и переключайте их по щелчку, чтобы была задержка между удалением одного класса и добавлением другого.

var trigger = document.querySelector("#toggle"),
  elem = document.querySelector(".box");

trigger.addEventListener("click", function() {
  currState = elem.className.split(" ")[1];
  newState = (currState == "open") ? "close" : "open";
  switchAnim(currState, newState);
});

function switchAnim(existingAnim, newAnim) {
  elem.classList.remove(existingAnim);
  setTimeout(function() {
    elem.classList.add(newAnim);
  }, 0);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease;
}
.close {
  animation: grow 1s ease reverse forwards;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<input type="checkbox" id="toggle" />
<div class="box open"></div>

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

var trigger = document.querySelector("#toggle"),
  elem = document.querySelector(".box");

trigger.addEventListener("click", function() {
  currState = elem.className.split(" ")[1];
  newState = (currState == "open") ? "close" : "open";
  switchAnim(currState, newState);
});

function switchAnim(existingAnim, newAnim) {
  elem.classList.remove(existingAnim);
  elem.clientHeight;
  elem.classList.add(newAnim);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease;
}
.close {
  animation: grow 1s ease reverse forwards;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<input type="checkbox" id="toggle" />
<div class="box open"></div>

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

var trigger = document.querySelector("#toggle"),
  elem = document.querySelector(".box");

trigger.addEventListener("click", function() {
  elem.classList.toggle("close");
  elem.classList.toggle("open");
});


window.onload = function() {
  elem.classList.toggle("close");
  elem.classList.toggle("open");
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
  transition: all 1s;
}
.open {
  transform: scale(1);
}
.close {
  transform: scale(0);
}
<input type="checkbox" id="toggle" />
<div class="box close"></div>


Повторный запуск любой анимации, существующей в элементе в заданной точке:

Флажок переключает анимацию, но всякий раз, когда документ щелкается, любая применимая анимация в зависимости от состояния флажка будет повторно запускаться.

var trigger = document.querySelector("#toggle"),
  elem = document.querySelector(".box");

document.addEventListener("click", function(e) {
  if (e.target != trigger) {
    currState = elem.className.split(" ")[1];
    switchAnim(currState, currState);
  }
})

trigger.addEventListener("click", function() {
  if (trigger.checked)
    switchAnim("close", "open");
  else
    switchAnim("open", "close");
});

function switchAnim(existingAnim, newAnim) {
  console.log(existingAnim);
  elem.classList.remove(existingAnim);
  setTimeout(function() {
    elem.classList.add(newAnim);
  }, 0);
}
.box {
  width: 200px;
  height: 200px;
  background: #099;
  transform-origin: 0 0;
}
.open {
  animation: grow 1s ease;
}
.close {
  animation: grow 1s ease reverse forwards;
}
@keyframes grow {
  from {
    transform: scale(0);
  }
  to {
    transform: scale(1);
  }
}
<input type="checkbox" id="toggle" checked/>
<div class="box open"></div>

person Harry    schedule 04.02.2016
comment
Я знаю, что прозвучу немного придирчиво, но... Хотя пока это лучшее решение... Я просил перезапустить анимацию, а не менять одну анимацию на другую. Может быть, невозможно перезапустить его, когда он будет завершен... Я заметил, что мой код работает, если щелчок происходит, когда квадрат все еще растет! - person Vandervals; 04.02.2016
comment
Кажется, что он останавливается, когда он заканчивается. Класс, который я добавляю, переворачивает анимацию, но представьте, что я просто хочу запустить ее снова, независимо от текущей анимации. Это возможно? В aniamtion API есть методы .play() .resume(), но они, кажется, не работают... Было бы логично, если бы был способ перезапустить анимацию элемента чем-то вроде: elem.play () или что-то. - person Vandervals; 04.02.2016
comment
В чем разница с примером в вашем посте? (кроме рефакторинга) Кроме того, я проверил это, и вместо тайм-аута, который генерирует вспышку, выглядит лучше, если вы используете elem.clientHeight - person Vandervals; 04.02.2016
comment
Давайте продолжим обсуждение в чате. - person Vandervals; 04.02.2016
comment
Хороший ответ. Как побочный комментарий, в большинстве браузеров он работает нормально с тайм-аутом, равным 0. Он устанавливает функцию в очередь и разрешает обновление, что делает трюк, а не тайм-аут. - person vals; 05.02.2016
comment
@Harry Я заметил, что изменить направление сложнее, но намного проще (просто добавить класс), если вы измените время или имя анимации. - person Vandervals; 05.02.2016
comment
@Vandervals: изменение имени анимации — это почти то же самое, что изменение класса, но я подумал, что это не то, что вам нужно, основываясь на вашем предыдущем комментарии — не менять одну анимацию на другую. Если вы измените только время, оно не сожмется, а только перезапустит анимацию роста. - person Harry; 05.02.2016

  1. Не используйте settimeout, используйте значение задержки
  2. Не смешивайте анимацию CSS и анимацию JS, если вы не знаете, что делаете.
  3. Использовать значение итераций
  4. Используйте преобразование так, чтобы они образовывали петлю. Начальное значение равно конечному значению. В противном случае вы получите шаги (золотая анимация box2)

var elem1 = document.querySelector(".box1");
var elem2 = document.querySelector(".box2");

elem1.animate([{
    transform: 'scale(0)'
  }, {
    transform: 'scale(1)'
  }, {
    transform: 'scale(0)'
  }], {
    duration: 1500,               
    iterations: 9999,
    delay: 0                    
});

elem2.animate([{
    transform: 'scale(0)'
  }, {
    transform: 'scale(1)'
  }], {
    duration: 1500,               
    iterations: 9999,
    delay: 0                    
});
.box {
  width: 200px;
  height: 200px;
  background: #099;
  animation: grow 1s ease;
  transform-origin: 0 0;
}

.box2 {
  background: gold;
}
<div class="box box1"></div>
<div class="box box2"></div>

person yunzen    schedule 04.02.2016
comment
Я не вижу, чтобы эта работа применялась к моему примеру: codepen.io/vandervals/pen/VeGXJP - person Vandervals; 04.02.2016

Попробуй это....

HTML

  <body>
    <div id="square"></div>
  </body>

JS

$("document").ready(function(){
  var elem = document.querySelector("#square");


  elem.classList.add("box");


  setInterval(function(){
  elem.classList.remove("box");
   elem.classList.add("boxSh");

  },1000)

});

CSS

 .box {
      width: 200px;
      height: 200px;
      background: #099;
      animation: grow 1s ease;
      transform-origin: 0 0;
    }
    .boxSh {
      width: 200px;
      height: 200px;
      background: #099;
      animation: shrink 1s ease;
      transform-origin: 0 0;
    }
    .close {
      animation: grow 1s ease reverse;
    }
    .sh{
      animation: shrink 1s ease reverse;
    }
    @keyframes grow {
      from {
        transform: scale(0);
      }
      to {
        transform: scale(1);
      }
    }

    @keyframes shrink {

      to {
        transform: scale(0);
      }
    }

ДЕМО

person Nofi    schedule 04.02.2016
comment
петля - это не то, что я искал... Я обновил вопрос, добавив дополнительную информацию. - person Vandervals; 04.02.2016