Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Очередь анимации в цикличном requestAnimationFrame (https://javascript.ru/forum/misc/85608-ochered-animacii-v-ciklichnom-requestanimationframe.html)

Raadsert 08.11.2023 08:59

Очередь анимации в цикличном requestAnimationFrame
 
Подскажите, как можно реализовать очередь для анимации если используется цикличный requestAnimationFrame?

function animate() {
    render();
    requestAnimationFrame(animate);
}

function render() {
    // код для анимации каких ни будь элементов
}

animate()

voraa 08.11.2023 09:23

Во-первых в функции animate должна быть проверка на то, что анимация должна остановиться. Т.е. вызывать requestAnimationFrame больше не нужно, иначе получится бесконечный цикл.
Во-вторых у функции animate есть параметр - время. Его надо использовать, что бы узнавать время от начала анимации или от предыдущего вызова animate, что бы рассчитывать какие произвести изменения.

Условная схема такая
let tbeg;
let tlast;
 
function animate(t) {
	tbeg ??= t;
	tlast ?? = t
	const dtbeg = t - tbeg; // прошло от начала анимации
	const dtlast = t - tlast; // прошло от прошлой анимации
    render();  // расчет и внесение изменений с учетом dtbeg и/или dtlast
    tlast = t;
    if (какое то условие окончания) return;  // Завершить анимацию?
    requestAnimationFrame(animate);
    
}
 
function render() {
    // код для анимации каких ни будь элементов
}

animate(performance.now())


Примеры можно посмотреть тут
https://developer.mozilla.org/en-US/...AnimationFrame

Raadsert 08.11.2023 11:32

Цитата:

Сообщение от voraa (Сообщение 553917)
Во-первых в функции animate должна быть проверка на то, что анимация должна остановиться. Т.е. вызывать requestAnimationFrame больше не нужно, иначе получится бесконечный цикл.
Во-вторых у функции animate есть параметр - время. Его надо использовать, что бы узнавать время от начала анимации или от предыдущего вызова animate, что бы рассчитывать какие произвести изменения.

Условная схема такая
let tbeg;
let tlast;
 
function animate(t) {
	tbeg ??= t;
	tlast ?? = t
	const dtbeg = t - tbeg; // прошло от начала анимации
	const dtlast = t - tlast; // прошло от прошлой анимации
    render();  // расчет и внесение изменений с учетом dtbeg и/или dtlast
    tlast = t;
    if (какое то условие окончания) return;  // Завершить анимацию?
    requestAnimationFrame(animate);
    
}
 
function render() {
    // код для анимации каких ни будь элементов
}

animate(performance.now())


Примеры можно посмотреть тут
https://developer.mozilla.org/en-US/...AnimationFrame

Во первых, в Three.js анимация не останавливается, цикл отрисовки идёт бесконечно (возможно за исключением тех моментов когда окно сайта не активно). Во вторых, а как сделать то очередь из функций для анимации в таком цикле? Если к примеру, нужно чтоб после завершения одной анимации, начиналась другая. Разумеется не останавливая цикла и не создавая новый.

voraa 08.11.2023 11:44

Если вы используете Three.js, то и пользуйтесь тем, что он дает.
Цикл отрисовки вообще не останавливается. Каждые 1/60 сек (при частоте экрана 60гц) происходит перерисовка экрана с учетом тех изменений, которые были внесены (если браузер не занят орбаботкой длинной задачи).
requestAnimationFrame просто дает возможность внести изменения перед перерисовкой с учетом времени.
Он представляет базовый уровень, на основе которого можно сделать что угодно, но это именно надо делать самому.
Вызовов requestAnimationFrame может быть много (много циклов). И когда наступает время перерисовки страницы, вызываются все функции, зарегистрированные к этому моменту.

Raadsert 08.11.2023 11:59

Цитата:

Сообщение от voraa (Сообщение 553923)
Если вы используете Three.js, то и пользуйтесь тем, что он дает.
Цикл отрисовки вообще не останавливается. Каждые 1/60 сек (при частоте экрана 60гц) происходит перерисовка экрана с учетом тех изменений, которые были внесены (если браузер не занят орбаботкой длинной задачи).
requestAnimationFrame просто дает возможность внести изменения перед перерисовкой с учетом времени.
Он представляет базовый уровень, на основе которого можно сделать что угодно, но это именно надо делать самому.
Вызовов requestAnimationFrame может быть много (много циклов). И когда наступает время перерисовки страницы, вызываются все функции, зарегистрированные к этому моменту.

Но увеличение количества циклов сильно снижают производительность. Даже тот же Three.js, писался на нативном js. Вот мне и интересно, как можно реализовать очередь анимаций в постоянном цикле.

voraa 08.11.2023 12:04

Цитата:

Сообщение от Raadsert
Но увеличение количества циклов сильно снижают производительность.

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

Зачем постоянный цикл? Если нет изменений, то и вызывать ничего не надо. Когда есть какие то изменения, то для них запускается цикл requestAnimationFrame

Цитата:

Сообщение от Raadsert
Даже тот же Three.js, писался на нативном js

А на чем еще можно?

Raadsert 08.11.2023 15:45

Цитата:

Сообщение от voraa (Сообщение 553925)
Ничего они сильно не снижают. Просто перед перерисовкой будут вызываться несколько простых функций, каждая отвечающая за свое изменение анимации, вместо одной сложной, которая делает все изменения.

Зачем постоянный цикл? Если нет изменений, то и вызывать ничего не надо. Когда есть какие то изменения, то для них запускается цикл requestAnimationFrame

А на чем еще можно?

Хорошо, тогда давайте будем отталкиваться от Three.js. Не знаете как можно сделать очередь анимаций для Three.js?

MallSerg 09.11.2023 18:36

>>Хорошо, тогда давайте будем отталкиваться от Three.js. Не знаете как можно сделать очередь анимаций для Three.js?

Так же как и всегда запускать следующую анимацию после окончания предыдущей и так в порядке очереди.
Возможно у тебя возникли сложности с определением события "конец анимации"?.

Как пример простого вызова последовательных анимаций мой пост 10ти летней давности =).
https://javascript.ru/forum/misc/496...tml#post326928

voraa 10.11.2023 09:32

Цитата:

Сообщение от MallSerg
Как пример простого вызова последовательных анимаций мой пост 10ти летней давности =).
https://javascript.ru/forum/misc/496...tml#post326928

Немного упростил и осовременил (с Promise и async/await)
<div id="moved" style="position: absolute;width:30px;height: 30px;background: red;"></div>
    <br>
    <input type="button" onclick="but1()" value="кнопка 1"> <br>
    
    <script type="text/javascript">
function but1 (){
    const element = document.getElementById("moved");
    animateMove( element , 800 , 100 , 1500)
	.then ( () => animateMove( element , -750 , 10 , 200) )
	.then ( () => animateMove( element , 150 , -60 , 1000) )
	.then ( () => {    
			element.style.background = ("#" + ( Math.random() * (999-100)+100 )).substr(0,4) ;
			element.style.top = "30px";
			element.style.left = "100px";
		})
}

/*  С async/await  
async function but1 (){
    const element = document.getElementById("moved");
    await animateMove( element , 800 , 100 , 1500);
    await animateMove( element , -750 , 10 , 200);
    await animateMove( element , 150 , -60 , 1000);
    
    element.style.background = ("#" + ( Math.random() * (999-100)+100 )).substr(0,4) ;
    element.style.top = "30px";
    element.style.left = "100px";
}
*/

function animateMove ( element , x , y , time = 1000){
    const statrX = element.offsetLeft;
    const startY = element.offsetTop;
    const startTime = performance.now();
    return new Promise ( (resolve) => {
		const animate = function (timenow){
			const EA = (timenow - startTime) / time;
			if (EA > 1) {
				resolve(); 
			} else {
				element.style.left = startX + (x * EA) + "px";
				element.style.top = startY + (y * EA) + "px";
				requestAnimationFrame(animate );
			}
		};
		animate(startTime);
    })
}

</script>

Raadsert 10.11.2023 14:34

Я имею ввиду несколько другое понятие очереди. Например этот же код (я его немного исправил), если несколько раз нажимать на кнопку - анимация будет воспроизводится по новой. А вот каким образом сделать так, чтоб каждое нажатие на кнопку ставило запуск новой функции анимации в некую очередь, и анимация воспроизводилась одна после другой?

1 нажатие на кнопку => animate()
2 нажатие на кнопку => animate() animate()
3 нажатие на кнопку => animate(), animate(), animate()
(при условии что анимация вызванная первым нажатием ещё не завершилась)



<div id="moved" style="position: absolute;width:30px;height: 30px;background: red; pointer-events: none;"></div>
    <br>
    <input type="button" onclick="but1()" value="кнопка 1"> <br>

    <script type="text/javascript">
        function but1 (){
            const element = document.getElementById("moved");
            animateMove( element , 500 , 100 , 1500)
        }

        /*  С async/await
        async function but1 (){
            const element = document.getElementById("moved");
            await animateMove( element , 800 , 100 , 1500);
            await animateMove( element , -750 , 10 , 200);
            await animateMove( element , 150 , -60 , 1000);

            element.style.background = ("#" + ( Math.random() * (999-100)+100 )).substr(0,4) ;
            element.style.top = "30px";
            element.style.left = "100px";
        }
        */

        function animateMove ( element , x , y , time = 1000){
            const startX = element.offsetLeft;
            const startY = element.offsetTop;
            const startTime = performance.now();
            return new Promise ( (resolve) => {
                const animate = function (timenow){
                    const EA = (timenow - startTime) / time;
                    if (EA > 1) {
                        resolve();
                    } else {
                        element.style.translate = `${startX + (x * EA)}px ${startY + (y * EA)}px`;
                        requestAnimationFrame(animate);
                    }
                };
                animate(startTime);
            })
        }

    </script>

voraa 10.11.2023 16:14

<div id="moved" style="position: absolute;width:30px;height: 30px;background: red;"></div>
    <br>
    <input type="button" onclick="but1()" value="кнопка 1"> <br>
    
    <script type="text/javascript">
let nclick = 0;
const animparams = [
	[800 , 100 , 3000],
	[-750 , 10 , 200],
	[150 , -60 , 2000],
	[-200 , -50 , 1000]
];
let animPromise = Promise.resolve();

function but1 (){
    const element = document.getElementById("moved");
   
    animPromise = animPromise.then (() => animateMove( element , ...animparams[(nclick++) % 4]));
}



function animateMove ( element , x , y , time = 1000){
	
    const startX = element.offsetLeft;
    const startY = element.offsetTop;
    const startTime = performance.now();
    return new Promise ( (resolve) => {
		const animate = function (timenow){
			let EA = (timenow - startTime) / time;
			if (EA > 1) EA = 1;
			element.style.left = startX + (x * EA) + "px";
			element.style.top = startY + (y * EA) + "px";
			if (EA == 1) {
				resolve()
			} else {
				requestAnimationFrame(animate );
			}
		};
		animate(startTime);
    })
}

</script>

Жмите когда хотите и сколько хотите.
Можно и нормальную очередь сделать, хотя бы самую простую с помощью массива и операций push/shift. Добавлять в нее параметры следующей анимации и когда предыдущая закончится вытаскивать следующую. Ту главное принцип - ловить окончание анимации с помощью Promise.

Raadsert 11.11.2023 17:04

Интересно. А вы не знаете почему это так работает? Ведь возвращение данных в then сохраняет эти данные для следующего then, а при возврате новых данных они просто заменятся. Но если возвращать промисы, то создаётся некое подобие очереди. Как так?

voraa 11.11.2023 18:16

Цитата:

Сообщение от Raadsert
Но если возвращать промисы, то создаётся некое подобие очереди. Как так?

Именно так. Создается очередь из промисов. Когда исполняется один, начинает работать следущий (then всегда возвращает промис, но не тот же самый, к которому он применялся, а тот, который возвращает его функция)
Содается цепочка
anim1.then(()=>anim2).then(()=>anim3).... .then(()=>animN)

Raadsert 11.11.2023 20:51

Вы ещё упоминали, что у threejs есть свои средства реализации подобного. А какие? Насколько я понимаю, реализовать способ с промисами, в непрерывном цикле - не получиться.

voraa 11.11.2023 21:26

Цитата:

Сообщение от Raadsert
Вы ещё упоминали, что у threejs есть свои средства реализации подобного

Я сказал только что есть, раз они это как то реализуют. Я с ним не работал.

Raadsert 12.11.2023 00:59

А есть предположения как такое может реализоваться в непрерывном цикле?

voraa 12.11.2023 09:36

Цитата:

Сообщение от Raadsert
А есть предположения как такое может реализоваться в непрерывном цикле?

На чем? (с threejs я не работал)
Что "такое"?

Raadsert 13.11.2023 12:04

Цитата:

Сообщение от voraa (Сообщение 553997)
На чем? (с threejs я не работал)
Что "такое"?

"Такое" - очередь как в промисах.
На обычном JS, как пример ваш код для цикличной анимации:

let tbeg;
let tlast;
 
function animate(t) {
	tbeg ??= t;
	tlast ?? = t
	const dtbeg = t - tbeg; // прошло от начала анимации
	const dtlast = t - tlast; // прошло от прошлой анимации
    render();  // расчет и внесение изменений с учетом dtbeg и/или dtlast
    tlast = t;
    if (какое то условие окончания) return;  // Завершить анимацию?
    requestAnimationFrame(animate);
    
}
 
function render() {
    // код для анимации каких ни будь элементов
}

animate(performance.now())


Понятное дело мы можем запускать его только когда нужна анимация. Но давайте предположи что его нельзя прервать (так работает threejs), как бы вы тогда добавили подобную (как в промисах) очередь?

Rise 13.11.2023 15:29

Raadsert,
Используй tween/tweening, напиши свой класс или возьми готовый tween.js, там ты сможешь chain-ы делать.

voraa 13.11.2023 16:58

Я не понимаю, о чем вы спрашиваете (и не знаю, как в threejs).
Есть непрерывный цикл в браузере. Выполняется таск(обработка событий), потом микротаски, потом снова таск, снова микротоски и так далее.... Когда подходит время обновления экрана (с частотой ~16,6 ms или 8.3 ms) между микротасками и таском вклинивается рендер - обновление экрана. Это непрерывный цикл. Его нельзя прервать.
Если мне нужна анимация, я с помощью requestAnimationFrame задаю функцию, которая будет вызвана перед рендером. Эта функция снова может вызвать requestAnimationFrame, задав себя, что бы быть вызванной перед следующим рендером, если нужно. Так задается конкретная анимация.
Если мне нужно несколько одновременных анимаций, я могу несколько раз вызвать requestAnimationFrame и задать несколько функций, реализующих анимации. И все они будут вызываться перед рендером.
Если нужно ждать, когда одна анимация закончится, что бы что то сделать, например, запустить следующую, можно организовать возврат промиса.
Это основы.
Что конкретно вас интересует, я так и не могу понять.

Raadsert 13.11.2023 17:08

Цитата:

Сообщение от Rise (Сообщение 554006)
Raadsert,
Используй tween/tweening, напиши свой класс или возьми готовый tween.js, там ты сможешь chain-ы делать.

Я хочу понять как подобное сделать, а не пользоваться готовым решением.

Raadsert 14.11.2023 11:40

Цитата:

Сообщение от Rise (Сообщение 554010)
А как ты поймешь, если js не знаешь?

Может поэтому я и пытаюсь понять, а не использовать готовое решение? Нет?

voraa 14.11.2023 14:10

Цитата:

Сообщение от Raadsert
Может поэтому я и пытаюсь понять,

Ну для этого надо знать не только сам язык и основные апи, но и принципы выполнения всего этого в браузере. Обработка событий, промисов, Очередь задач, микрозадачи, рендеринг (пересчет стилей и сам вывод на экран очередного кадра)

Начните с того, что бы САМОМУ сделать простенькую одиночную анимацию. Потом параллельно запускать 2 (несколько) анимаций. Потом ожидание окончания анимации и запуск следующей... Так лучше разберетесь.

Будут КОНКРЕТНЫЕ вопросы - спрашивайте.


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