Как запустить и остановить анимацию?
Есть код, который бесконечно анимирует картинку
const svg = document.getElementById('svg-element')
const warp = new Warp(svg)
warp.interpolate(4)
warp.transform(([ x, y ]) => [ x, y, y ])
let offset = 0
function animate()
{
warp.transform(([ x, y, oy ]) => [ x, oy + 4 * Math.sin(x / 16 + offset), oy ])
offset += 0.1
requestAnimationFrame(animate)
}
animate()
#svg-element {
margin: 20px;
overflow: visible;
width: 300px;
height: auto;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/warp.js"></script>
<svg id="svg-element" viewBox="0 0 303 251">
<circle cx="88.784" cy="141.382" r="88.784" style="fill: #00fbd1; mix-blend-mode: multiply;"/>
<path d="M278.078,26.195l-161.04,-26.195l-26.195,161.04l161.04,26.195l26.195,-161.04Z" style="fill: #d867ff; mix-blend-mode: multiply;"/>
<path d="M170.608,63.772l131.392,155.722l-213.216,30.874l81.824,-186.596Z" style="fill: #fffa2a; mix-blend-mode: multiply;"/>
</svg>
Я хочу создать команды, которые запускают код, ставят его на паузу и обнуляют анимацию.
Примерно так:
const svg = document.getElementById('svg-element')
const warp = new Warp(svg)
warp.interpolate(4)
warp.transform(([ x, y ]) => [ x, y, y ])
let offset = 0
var requestId = undefined;
function start(ani) {
if (!requestId) {
requestId = window.requestAnimationFrame(ani);
}
}
function pause(ani) {
if (requestId) {
window.cancelAnimationFrame(ani);
requestId = undefined;
}
}
function stop() {
offset = 0
}
function animate(){
warp.transform(([ x, y, oy ]) => [ x, oy + 4 * Math.sin(x / 16 + offset), oy ])
offset += 0.1
self.start = start(animate)
self.pause = pause(animate)
self.stop = stop()
}
animate.start
Как мне лучше это сделать, если анимаций будет несколько?
Писать для каждой свою функцию остановки, запуска и так далее, и вызывать start_animation_1() не имеет смысла.
Ответы (1 шт):
Ну нужна какая-то надстройка над Warp.
Проблема в том, что не получится обойтись магическими функциями, которые сами всё будут останавливать и запускать. Например, stop должен знать, какие именно значения ему нужно обнулить: Вместо одного offset, могут быть 2 или 3 значения. Т.е. при инициализации нужно передать дополнительный объект с начальными значениями.
В таких случаях можно попробовать сначала придумать, как было бы удобно использвать функцию, а потом думать, как её реализовать. Сделать, и больше не смотреть, что там происходит внутри. Немного повозился, пока получается такое:
class Anim {
constructor(params) {
/* Чтобы не пытаться запоминать, какая функция - как вызывается,
можно изначально согласовать и объявить где-то вверху:
""" Все полученные функции из params везде вызываются в контексте this """ */
this.warp = new Warp(params.elem);
params.init_fn.call(this, this.warp); // инициализация для warp
this._values = params.init_values; // Хранит параметры для loop
this._loop_fn = params.loop; // вызывается на каждом animationFrame
/***/
this._animating = false;
this._save_starting_values(this._values);
}
start() {
if (this._animating) return;
this._animating = true;
const fn = (time) => {
// Важна именно стрелочная функция: Чтобы this внутри не терялся.
if (!this._animating) return;
// Если откуда-то выставят this._animating = false, оно просто не запустится.
// Можно уже без cancelAnimation...
this._loop_fn.call(this, this._values, time);
requestAnimationFrame(fn);
};
requestAnimationFrame(fn);
}
pause() {
this._animating = false;
}
stop() {
this._animating = false; // Анимация уже не запустится
this._return_starting_values(); // начальные значения возвращаются на место
this._loop_fn.call(this, this._values, performance.now()); // перерисовать
}
/***/
_save_starting_values(_values) {
this._starting_values = {};
Object.assign(this._starting_values, _values);
}
_return_starting_values() {
Object.assign(this._values, this._starting_values);
}
}
/*** Использование */
const animation = new Anim({
elem: document.getElementById('svg-element'),
init_fn: function(warp) {
warp.interpolate(4);
warp.transform(([x, y]) => [x, y, y]);
},
init_values: {
offset: 0,
},
loop: function(values, time) {
const v = values;
this.warp.transform(([x, y, oy]) => [x, oy + 4 * Math.sin(x / 16 + v.offset), oy])
v.offset += 0.1;
},
});
animation.start();
/***/
(function demo_buttons() {
const id = (e) => document.getElementById(e);
id("start").addEventListener("click", function() {
animation.start();
});
id("pause").addEventListener("click", function() {
animation.pause();
});
id("stop").addEventListener("click", function() {
animation.stop();
});
})();
#svg-element {
margin: 20px;
overflow: visible;
width: 300px;
height: auto;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/warp.js"></script>
<button id="start">Start</button>
<button id="pause">Pause</button>
<button id="stop">Stop</button>
<hr>
<svg id="svg-element" viewBox="0 0 303 251">
<circle cx="88.784" cy="141.382" r="88.784" style="fill: #00fbd1; mix-blend-mode: multiply;"/>
<path d="M278.078,26.195l-161.04,-26.195l-26.195,161.04l161.04,26.195l26.195,-161.04Z" style="fill: #d867ff; mix-blend-mode: multiply;"/>
<path d="M170.608,63.772l131.392,155.722l-213.216,30.874l81.824,-186.596Z" style="fill: #fffa2a; mix-blend-mode: multiply;"/>
</svg>