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 вообще не используются за их ущербностью


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