Javascript-форум (https://javascript.ru/forum/)
-   Events/DOM/Window (https://javascript.ru/forum/events/)
-   -   Как определить, что анимация закончилась? (https://javascript.ru/forum/events/46807-kak-opredelit-chto-animaciya-zakonchilas.html)

Octane 25.04.2014 01:10

Как определить, что анимация закончилась?
 
Речь о CSS свойстве animation.

Допустим, мы не успели поймать событие animationend, как тогда определить, что анимация элемента закончилась?

Проверяем, что анимация не бесконечная computedStyle.animationIterationCount, но
computedStyle.animationPlayState так и продолжает возвращать значение running, даже если анимация уже прошла.

Мы можем вычислить время анимации animationDelay + animationDuration * animationIterationCount, но не знаем, когда анимация запускалась.

Есть идеи?

melky 25.04.2014 01:22

Цитата:

Сообщение от Octane
Допустим, мы не успели поймать событие animationend, как тогда определить, что анимация элемента закончилась?

хаки и велосипеды начинаются с этой строки :) А почему нельзя поймать это событие?
или это чисто тема на рассуждение ?

Цитата:

Сообщение от Octane
computedStyle.animationPlayState так и продолжает возвращать значение running, даже если анимация уже прошла.

свойство не для того сделано, чтобы показывать, проигрывается ли анимация или нет

Цитата:

Сообщение от Octane
Есть идеи?

Можно проверить по computedStyle одного из свойств, которое участвует в анимации. Определить, какое свойство участвует, можно по keyframesRule анимации.. а это keyframesRule можно найти из animationName.

Если режим заполнения (overflow) анимации стоит "forwards" или "both" - значением вычисленного стиля элемента будет значение последнего (!) ключевого кадра.

Если режим заполнения стоит "backwards" или "none" - значением вычисленного стиля будет значение, определённое НЕ из анимации - т.е. либо из каскадного стиля (вероятнее всего), либо из чего-нибудь другого.

Последний ключевой кадр - это последний ключевой кадр (спс К.О.) Не помню, влияет ли на то, каким будет последний ключевой кадр, свойство iterationCount с нецелым значением (напр. "1.5" проиграет один проход и ещё половину следующего прохода)

А вот animationDirection, установленное не в 'normal', обращает наоборот последний ключкадр через экстраполяцию.

... не помню всех деталей. всего лишь полгода прошло)

Octane 25.04.2014 02:14

Цитата:

Сообщение от melky
чисто тема на рассуждение?

да, больше интересует возможность решения

Цитата:

Сообщение от melky
Можно проверить по computedStyle одного из свойств, которое участвует в анимации. Определить, какое свойство участвует, можно по keyframesRule анимации.. а это keyframesRule можно найти из animationName.

Проверять свойства, измененные в keyframes не прокатит, над элементом мог произойти еще и transition.

Вообще хотелось бы универсальное решение, например, задачи: добавить класс элементу, если над этим элементом не происходит анимация, при условии, что раньше об этом элементе мы ничего не знали. Или хотя бы элементарное: вызвать callback, когда все анимации закончились.

Я знаю, что из-за проблем с контролем CSS анимаций, например, в jQuery animate до сих пор никак не используются CSS transition и animation.

Например, для transition можно посчитать время и запустить callback через setTimeout, как это сделано в плагине https://github.com/ai/transition-events

Очень удобно, использовать метод addClass, возвращающий promise, которое fulfill'ется, когда все CSS переходы закончены:
addClass(element, "trans").then(doSomething)
если в качестве value будет передаваться элемент, можно строить такие очереди:
addClass(document.query(".some"), "trans1").then(function (element) {

	doSomething1();

	return addClass(element, "trans2");

}).then(function (element) {

	doSomething2();

	return addClass(element, "trans3");

}).then(function (element) {

	doSomething3();

});
Для браузеров, не поддерживающих transition, сразу выполнять действия так, как будто
transition-duration + transition-delay = 0

А вот с animation уже такое не прокатит.

melky 25.04.2014 10:28

Цитата:

Сообщение от Octane
Проверять свойства, измененные в keyframes не прокатит, над элементом мог произойти еще и transition.

это easy mode. для того, чтобы узнать, анимируется ли свойство ещё чем-нибудь, делаем requestAnimationFrame , снова получаем вычисленное значение стиля и сверяем с предыдущим.

Цитата:

Сообщение от Octane
Очень удобно, использовать метод addClass, возвращающий promise, которое fulfill'ется, когда все CSS переходы закончены:

это абстракция, и внутри promise может быть не transition, а animation. да и вообще всё, что угодно, хоть GSAP или Web Animations

Цитата:

Сообщение от Octane
Например, для transition можно посчитать время и запустить callback через setTimeout, как это сделано в плагине https://github.com/ai/transition-events

если знать, когда анимация была запущена, вычислить её продолжительность не составляет труда.
тут и для анимаций метод с таймаутом
прокатит, если её не паузить через animation-play-state

а узнать, закончился ли transition, не зная, когда он начался - опять таки, проверять по изменению свойств.

API скудное у этих стандартов, да

Цитата:

Сообщение от Octane
А вот с animation уже такое не прокатит.

из-за паузы?

melky 25.04.2014 10:29

Цитата:

Сообщение от dmitriymar
События JavaScript

я так понял, тут принципиально обработчики не используются

dmitriymar 25.04.2014 10:30

melky,
да не дочитал полностью сообщение Octane, и запостил..

Octane 25.04.2014 13:20

Цитата:

Сообщение от melky
это easy mode. для того, чтобы узнать, анимируется ли свойство ещё чем-нибудь, делаем requestAnimationFrame , снова получаем вычисленное значение стиля и сверяем с предыдущим.

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

Цитата:

Сообщение от melky
если знать, когда анимация была запущена

В этом вся и проблема. Расчет времени анимации я написал еще в первом посте, но это время бесполезно, если не знать момент запуска.

Цитата:

Сообщение от melky
из-за паузы?

transition само по себе не происходит. Если рассматривать в контексте функции addClass, то transition всегда будет начинаться при добавлении CSS класса, тоесть мы будем знать время начала (хотя может быть вариант, что какой-то переход еще не закончился).
В отличие от transition, animation может быть запущена хоть при загрузке старницы, действий никаких не требуется, время старта мы не знаем.
Пауза ладно, фиг с ней, ее хотя бы можно отследить по значению свойства, проблема начинается даже в простом случае:
.animated {
   animation: anim1 1s 1, anim2 3s 1;
   animation-delay: 0s, 5s;
}

В данном случае может сработать до 4х событий (2 раза animationstart и соответственно 2 animationend). Если анимации запускаются с разной задержкой, становится проблемно выполнить действия, когда все анимации над элементом закончатся. Даже распарсив в JavaScript свойство style.animationName и узнав количество анимаций, мы не уверены, что все анимации запустятся, браузер не известит об анимации свойства, которое и так уже находится в состоянии конечного кадра (хотя надо будет проверить, уверен что так работает для transition). Не понятно, когда и сколько событий нужно подождать, чтобы удостовериться, что анимация закончилась.

Если кому интересно, наработки здесь: getTransitionTime и getFiniteAnimationTime, add|remove|toggleClass

melky 25.04.2014 20:04

Octane, у событий анимаций есть особые свойства:

Цитата:

dictionary AnimationEventInit : EventInit {
DOMString animationName;
float elapsedTime;
DOMString pseudoElement;
}
которые позволяют отличить "просто событие конца анимации" от "анимация SUPER закончилась"

или я не понял, в чём проблема событий анимации :)

Octane 25.04.2014 23:49

elapsedTime для каждой отдельной анимации приходит, не понимаю, как это может помочь.

Давай напишем метод addClass→promise для следующего случая:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>…</title>
    <style>
        .animated {
            position: fixed;
            top: 0;
            left: 50%;
            width: 20px;
            height: 20px;
            background: #f50;
            transition: top 5s, left 1s;
        }
        .move {
            top: 0;
            left: 0;
        }
    </style>
</head>
<body>
    <div class="animated"></div>
    <script>
        function parseFloats(string) {
            return string.split(",").map(function (string) {
                return parseFloat(string) || 0;
            });
        }

        function calcTransitionTime(delay, duration) {
            var length = Math.max(duration.length, delay.length),
                i = 0, time, maxTime = 0;
            while (i < length) {
                time = (delay[i] || 0) + (duration[i] || 0);
                if (time > maxTime) {
                    maxTime = time;
                }
                i++;
            }
            return Math.ceil(maxTime * 1000);
        }

        function getTransitionTime(style) {
            return calcTransitionTime(
                parseFloats(style.transitionDelay),
                parseFloats(style.transitionDuration)
            );
        }

        function addClass(element, className) {
            return new Promise(function (resolve) {
                element.classList.add(className);
                var delay = getTransitionTime(getComputedStyle(element));
                setTimeout(function () {
                    resolve(element);
                }, delay);
            });
        }

        window.onload = function () {
            var el = document.querySelector(".animated");
            addClass(el, "move").then(function () {
                alert("done");
            });
        };
    </script>
</body>
</html>
http://learn.javascript.ru/play/mdqxkb
Здесь сработает через 1 секунду только один transitionend, но максимальное время анимации 5 секунд. Браузер не будет ничего делать для свойства top, ни событий, ни reflow. Обещание будет ждать зря 4 секунды. Есть идеи, как определить, что анимация закончилась за 1 секунду?

Изобретать свой CSS парсер как-то не хочется, потому что просто вытащить анимируемое свойство из document.styleSheets[i].cssRules[j].style[n] не получится, класс может быть «размазан» по куче cssRules.

Тоже самое и с animation:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>…</title>
    <style>
        .animated {
            position: fixed;
            top: 0;
            left: 50%;
            width: 20px;
            height: 20px;
            background: #f50;
        }
        .move {
            animation: move-top 5s, move-left 1s;
            animation-fill-mode: both;
        }
        @keyframes move-top {
            100% {
                top: 0;
            }
        }
        @keyframes move-left {
            100% {
                left: 0;
            }
        }
    </style>
</head>
<body>
    <div class="animated"></div>
    <script>
        function parseFloats(string) {
            return string.split(",").map(function (string) {
                return parseFloat(string) || 0;
            });
        }

        function calcFiniteAnimationTime(delay, duration, count) {
            var length = Math.max(duration.length, delay.length, count.length),
                i = 0, time, maxTime = 0;
            while (i < length) {
                time = (delay[i] || 0) + (duration[i] || 0) * (count[i] || 0);
                if (time > maxTime) {
                    maxTime = time;
                }
                i++;
            }
            return Math.ceil(maxTime * 1000);
        }

        function getFiniteAnimationTime(style) {
            return calcFiniteAnimationTime(
                parseFloats(style.animationDelay),
                parseFloats(style.animationDuration),
                parseFloats(style.animationIterationCount)
            );
        }

        function addClass(element, className) {
            return new Promise(function (resolve) {
                element.classList.add(className);
                var delay = getFiniteAnimationTime(getComputedStyle(element));
                setTimeout(function () {
                    resolve(element);
                }, delay);
            });
        }

        window.onload = function () {
            var el = document.querySelector(".animated");
            addClass(el, "move").then(function () {
                alert("done");
            });
        };
    </script>
</body>
</html>
http://learn.javascript.ru/play/hOiFcb
Произойдет только одна пара событий animationstart и animationend, анимация закончится за 1 секунду, но delay будет равен 5 сек.

melky 26.04.2014 00:48

По анимациям глянь :

http://codepen.io/ColCh/full/jilgs

Поизменяй animation-delay или вообще анимации в классе и увидишь, что всё ок

а по переходам я сам не придумал, как эту фигню обойти. В моём недописанном движке transition вообще не используются за их ущербностью

Octane 26.04.2014 01:36

А если поставим начальные значения top и left отличные от 0, callback выполнится через 1 секунду, а не как положено через 5.
В твоем примере resolve выполняется по первому сработавшему animationend, а я пытаюсь сделать событие allNewAnimationsEnd.
Ладно узнали мы имена новых анимаций, можно попробовать дождаться окончания их всех. НО мы не знаем какие из этих анимаций будут выполняться.

Наверное, буду пока что считать getFiniteAnimationName только для новых анимаций, а delay будет:
var delay = Math.max(
    getTransitionTime(style),
    getFiniteAnimationTime(style, newAnimationNames)
);
Интересно, значит все таки 1 animationend гарантированно срабатывает (надеюсь во всех браузерах). Я почему-то был уверен, что если еще и для left поставлю начальное значение 0, то вообще ни одно событие не сработает и callback никогда не выполнится.

melky 26.04.2014 01:46

я там код обновил, глянь его :)

Цитата:

Сообщение от Octane (Сообщение 309319)
А если поставим начальные значения top и left отличные от 0, callback выполнится через 1 секунду, а не как положено через 5.

можно пример на основе текущего codepen?

Цитата:

Сообщение от Octane (Сообщение 309319)
В твоем примере resolve выполняется по первому сработавшему animationend, а я пытаюсь сделать событие allNewAnimationsEnd.

я это дело пофиксил в последней обнове. теперь там список

Цитата:

Сообщение от Octane (Сообщение 309319)
Ладно узнали мы имена новых анимаций, можно попробовать дождаться окончания их всех. НО мы не знаем какие из этих анимаций будут выполняться.

это как? зачем применять анимацию, если она не проигрывается?

Цитата:

Сообщение от Octane (Сообщение 309319)
Интересно, значит все таки 1 animationend гарантированно срабатывает (надеюсь во всех браузерах). Я почему-то был уверен, что если еще и для left поставлю начальное значение 0, то вообще ни одно событие не сработает и callback никогда не выполнится.

там по нескольку раз срабатывает. для каждой анимации один раз

Octane 26.04.2014 02:19

Теперь наоборот ждем 5 сек, вместо 1 сек, если top=0 :)

Цитата:

Сообщение от melky
это как? зачем применять анимацию, если она не проигрывается?

Как в примере, который разбираем, одним классом добавляется 2 анимации, одна из которых выполняется.

----------------------
Цитата:

Сообщение от Octane
http://learn.javascript.ru/play/hOiFcb
Произойдет только одна пара событий animationstart и animationend

Наврал. Ненавижу группировку одинаковых сообщений в консоли!!!11 :-E Все таки события animation всегда происходят, в отличие от transition. В этом была моя основная путаница. Да, getFiniteAnimationTime тогда можно выкинуть.


Часовой пояс GMT +3, время: 19:40.