Синхронизация Lottie-анимации с внешними медиа-элементами требует понимания того, что Lottie Web не работает как самостоятельная временная шкала мультимедиа в классическом смысле. Библиотека оперирует кадровой моделью, где ключевым параметром выступает текущий кадр анимации, а управление осуществляется через методы программного API.
В основе управления анимацией лежит кадровая система:
Ключевые элементы управления:
goToAndStop(frame, isFrame)play()pause()stop()playSegments(segments, forceFlag)Дополнительно используется внутренний механизм:
animation.currentFrameanimation.totalFramesanimation.frameRateЭти значения позволяют построить внешнюю временную шкалу, синхронизированную с аудио или видео.
Видео является наиболее прямым источником временной базы благодаря
свойству currentTime.
Пусть:
video.currentTime — время видео в секундахduration — длительность видеоtotalFrames — общее количество кадров LottieТогда привязка кадра:
function syncVideoToLottie(video, animation) {
const frame = (video.currentTime / video.duration) * animation.totalFrames;
animation.goToAndStop(frame, true);
}
function startSync(video, animation) {
function tick() {
if (!video.paused && !video.ended) {
const frame = (video.currentTime / video.duration) * animation.totalFrames;
animation.goToAndStop(frame, true);
}
requestAnimationFrame(tick);
}
tick();
}
В реальных условиях возникает дрейф из-за:
Добавляется порог коррекции:
const EPSILON = 0.5;
function sync(video, animation) {
const targetFrame = (video.currentTime / video.duration) * animation.totalFrames;
const delta = Math.abs(animation.currentFrame - targetFrame);
if (delta > EPSILON) {
animation.goToAndStop(targetFrame, true);
}
}
Аудио предоставляет более точную временную шкалу при использовании
AudioContext.
const audioContext = new AudioContext();
const audio = new Audio("track.mp3");
const source = audioContext.createMediaElementSource(audio);
source.connect(audioContext.destination);
function syncAudio(animation, startTime) {
const currentTime = audioContext.currentTime - startTime;
const frame = currentTime * animation.frameRate;
animation.goToAndStop(frame, true);
}
let startTime = null;
audio.addEventListener("play", () => {
startTime = audioContext.currentTime;
requestAnimationFrame(update);
});
function update() {
if (!audio.paused) {
const t = audioContext.currentTime - startTime;
const frame = t * animation.frameRate;
animation.goToAndStop(frame, true);
requestAnimationFrame(update);
}
}
Часто требуется независимая временная система, например:
function map(value, inMin, inMax, outMin, outMax) {
return outMin + (value - inMin) * (outMax - outMin) / (inMax - inMin);
}
window.addEventListener("scroll", () => {
const scrollTop = window.scrollY;
const docHeight = document.body.scrollHeight - window.innerHeight;
const progress = scrollTop / docHeight;
const frame = progress * animation.totalFrames;
animation.goToAndStop(frame, true);
});
Lottie поддерживает работу с сегментами анимации, что важно при синхронизации отдельных фаз.
animation.playSegments([0, 30], true);
function syncState(state) {
switch (state) {
case "intro":
animation.playSegments([0, 45], true);
break;
case "loop":
animation.playSegments([45, 120], true);
break;
case "outro":
animation.playSegments([120, 180], true);
break;
}
}
При наличии нескольких слоёв анимации важно использовать единый источник времени.
function syncAll(masterFrame, animations) {
animations.forEach(anim => {
const frame = (masterFrame / 100) * anim.totalFrames;
anim.goToAndStop(frame, true);
});
}
GSAP может выступать центральным контроллером времени.
const tl = gsap.timeline({ paused: true });
tl.to({}, {
duration: 5,
onUpdate: () => {
const progress = tl.progress();
const frame = progress * animation.totalFrames;
animation.goToAndStop(frame, true);
}
});
Линейное соответствие времени часто недостаточно. Применяются easing-функции:
function easeInOut(t) {
return t < 0.5
? 2 * t * t
: -1 + (4 - 2 * t) * t;
}
const progress = easeInOut(rawProgress);
const frame = progress * animation.totalFrames;
При loop-анимации возникает проблема скачков фазы.
function loopSync(time, duration, totalFrames) {
const mod = time % duration;
return (mod / duration) * totalFrames;
}
При длительных сессиях синхронизации необходимо учитывать накопление ошибки.
Подход с периодической реалигнацией:
let lastCorrection = 0;
const CORRECTION_INTERVAL = 2000;
function correctedSync(time, animation) {
if (time - lastCorrection > CORRECTION_INTERVAL) {
animation.goToAndStop(time * animation.frameRate, true);
lastCorrection = time;
}
}
Lottie может быть привязана к интерактивным состояниям интерфейса.
button.addEventListener("mouseenter", () => {
animation.playSegments([0, 20], true);
});
button.addEventListener("mouseleave", () => {
animation.playSegments([20, 40], true);
});
Единый подход к различным источникам данных заключается в использовании нормализованного значения progress:
function applyProgress(animation, progress) {
const frame = progress * animation.totalFrames;
animation.goToAndStop(frame, true);
}
При комбинировании нескольких источников требуется система приоритетов:
function resolveFrame(sources) {
return sources.user ??
sources.media ??
sources.timer ??
sources.fallback;
}
Частое обновление кадров может приводить к лишней нагрузке.
Используются техники:
let lastFrame = -1;
function update(frame) {
const rounded = Math.round(frame);
if (rounded !== lastFrame) {
animation.goToAndStop(rounded, true);
lastFrame = rounded;
}
}