Библиотека Lottie Web использует JSON-описания анимаций, которые могут быть относительно тяжёлыми по объёму и затратными по рендерингу. При большом количестве анимаций на странице или наличии сложных сцен критичным становится контроль момента загрузки JavaScript-кода, JSON-файлов и инициализации canvas/SVG-рендера.
Lazy loading в контексте Lottie включает несколько уровней:
Каждый уровень снижает нагрузку на основной поток и уменьшает время первого отображения страницы.
Базовый сценарий — исключение Lottie из основного бандла и загрузка только при необходимости.
let lottieInstance = null;
async function loadLottie() {
if (lottieInstance) return lottieInstance;
const module = await import('lottie-web');
lottieInstance = module.default;
return lottieInstance;
}
Динамический import() позволяет:
В системах сборки (Vite, Webpack, Rollup) это приводит к созданию отдельного чанка, который подгружается по требованию.
JSON-файлы Lottie часто являются основным источником нагрузки. Их размер может варьироваться от десятков килобайт до нескольких мегабайт.
Стратегия — загрузка через fetch только при
необходимости:
async function loadAnimationData(url) {
const response = await fetch(url);
if (!response.ok) throw new Error('Animation load error');
return await response.json();
}
Оптимизация достигается за счёт:
Основной механизм ленивого запуска анимаций — наблюдение за попаданием элемента в viewport.
const observer = new IntersectionObserver(async (entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const container = entry.target;
observer.unobserve(container);
const lottie = await loadLottie();
const data = await loadAnimationData(container.dataset.src);
lottie.loadAnimation({
container,
renderer: 'svg',
loop: true,
autoplay: true,
animationData: data
});
}
}, {
threshold: 0.25
});
Ключевые параметры:
threshold регулирует момент старта загрузкиunobserve предотвращает повторную инициализациюdata-src позволяет хранить путь к JSON прямо в
DOMLazy loading не всегда означает загрузку строго в момент появления. Более эффективный вариант — ранняя загрузка до попадания в viewport.
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) return;
const rootMarginLoad = entry.rootBounds?.height || 0;
if (entry.boundingClientRect.top < rootMarginLoad * 1.5) {
preloadAnimation(entry.target.dataset.src);
}
});
}, {
rootMargin: '200px'
});
Использование rootMargin позволяет инициировать загрузку
заранее, уменьшая задержку перед стартом анимации.
Оптимальная модель Lottie lazy loading делит процесс на три независимых слоя:
Каждый слой может быть кеширован и переиспользован.
const animationCache = new Map();
async function getAnimationData(src) {
if (animationCache.has(src)) {
return animationCache.get(src);
}
const data = await loadAnimationData(src);
animationCache.set(src, data);
return data;
}
При SPA-навигации или виртуальном скролле часто возникает повторное создание анимаций. Это приводит к утечкам памяти и перегрузке CPU.
Корректное управление жизненным циклом:
const instances = new WeakMap();
function createAnimation(container, data, lottie) {
const instance = lottie.loadAnimation({
container,
renderer: 'svg',
loop: true,
autoplay: true,
animationData: data
});
instances.set(container, instance);
return instance;
}
function destroyAnimation(container) {
const instance = instances.get(container);
if (instance) {
instance.destroy();
instances.delete(container);
}
}
При наличии списков карточек с Lottie-анимациями используется стратегия частичной гидратации.
const items = document.querySelectorAll('.lottie-item');
items.forEach(item => observer.observe(item));
Дополнительная оптимизация:
В условиях ограниченной пропускной способности полезно вводить очередь загрузки.
class LoadQueue {
constructor(limit = 3) {
this.limit = limit;
this.active = 0;
this.queue = [];
}
async add(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
this.next();
});
}
async next() {
if (this.active >= this.limit || !this.queue.length) return;
const { task, resolve, reject } = this.queue.shift();
this.active++;
try {
const result = await task();
resolve(result);
} catch (e) {
reject(e);
} finally {
this.active--;
this.next();
}
}
}
Очередь ограничивает количество параллельных загрузок JSON и снижает пик нагрузки.
Некоторые анимации не требуют автоматического старта. Используется отложенный autoplay.
lottie.loadAnimation({
container,
renderer: 'svg',
loop: false,
autoplay: false,
animationData
});
Запуск контролируется отдельно:
instance.play();
Это позволяет синхронизировать анимации с пользовательскими действиями или событиями интерфейса.
SVG-режим в Lottie создаёт множество DOM-элементов. При большом количестве одновременно загруженных анимаций требуется контроль:
lottie.loadAnimation({
container,
renderer: 'canvas',
animationData
});
Canvas снижает нагрузку на DOM при высокой плотности анимаций.
При повторном входе элемента в viewport можно использовать восстановление прогресса:
const lastFrame = new Map();
function pauseAnimation(instance, id) {
lastFrame.set(id, instance.currentFrame);
instance.pause();
}
function resumeAnimation(instance, id) {
const frame = lastFrame.get(id) || 0;
instance.goToAndPlay(frame, true);
}
Это снижает необходимость полной перезагрузки JSON и пересоздания DOM.
В системах с виртуализированными списками lazy loading синхронизируется с жизненным циклом элементов:
Такой подход предотвращает накопление неиспользуемых анимаций при длинных списках.
Для повторных посещений критично использование кэша:
self.addEventListener('fetch', (event) => {
if (event.request.url.includes('.json')) {
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request))
);
}
});
Анимации, влияющие на UX (индикаторы загрузки, интерактивные элементы), загружаются с более высоким приоритетом, чем декоративные.
Стратегия:
requestIdleCallback(() => {
loadAnimationData(src);
});
При большом количестве Lottie-инстансов ключевым становится контроль памяти:
destroy()instance.destroy();
instance = null;
Полная схема выглядит как последовательность:
Каждый этап может быть независимым и кэшируемым, что позволяет масштабировать количество анимаций без деградации производительности.