Работа с анимациями на основе <canvas> в сочетании
с библиотекой ScrollMagic требует особого внимания к производительности.
В отличие от DOM-анимаций, canvas не хранит состояние объектов — каждый
кадр отрисовывается вручную. Это создает дополнительную нагрузку на
процессор и графическую подсистему, особенно при скролл-зависимых
эффектах.
1. Частая перерисовка кадров При использовании ScrollMagic сцены могут обновляться на каждом событии прокрутки. Если каждый скролл вызывает полную перерисовку canvas, это приводит к падению FPS.
2. Большое количество объектов Чем больше элементов отрисовывается в canvas (частицы, изображения, текст), тем выше нагрузка.
3. Работа с изображениями Декодирование и масштабирование изображений в реальном времени сильно влияет на производительность.
4. Неправильная синхронизация с браузером
Использование scroll-событий напрямую без оптимизации может
вызывать избыточные вызовы рендеринга.
ScrollMagic позволяет отслеживать прогресс сцены через параметр
progress. Оптимальная практика — не выполнять рендер
напрямую в обработчике ScrollMagic, а использовать
requestAnimationFrame.
let scrollProgress = 0;
let ticking = false;
scene.on("progress", function (event) {
scrollProgress = event.progress;
if (!ticking) {
requestAnimationFrame(updateCanvas);
ticking = true;
}
});
function updateCanvas() {
render(scrollProgress);
ticking = false;
}
Преимущество:
Полная очистка canvas через clearRect — дорогая
операция. При возможности следует перерисовывать только измененные
области.
context.clearRect(x, y, width, height);
Подход эффективен, если:
OffscreenCanvas позволяет переносить рендеринг в Web
Worker, разгружая основной поток.
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
Преимущества:
Ограничения:
При анимации по скроллу часто используются последовательности изображений (frame-by-frame анимация).
Ошибка: загрузка изображения при каждом кадре
Решение: предзагрузка
const images = [];
for (let i = 0; i < frameCount; i++) {
const img = new Image();
img.src = `frames/frame_${i}.jpg`;
images.push(img);
}
Далее используется уже готовый массив:
context.drawImage(images[currentFrame], 0, 0);
Для четкости на Retina-экранах используется масштабирование canvas:
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
context.scale(dpr, dpr);
Важно: увеличение разрешения увеличивает нагрузку. Баланс между качеством и производительностью обязателен.
При использовании ScrollMagic можно привязать анимацию к фиксированному количеству кадров.
const totalFrames = 100;
function render(progress) {
const frame = Math.floor(progress * totalFrames);
drawFrame(frame);
}
Плюсы:
ScrollMagic уже оптимизирует события, но при сложной логике полезно дополнительно ограничивать вызовы:
function throttle(fn, limit) {
let waiting = false;
return function () {
if (!waiting) {
fn.apply(this, arguments);
waiting = true;
setTimeout(() => waiting = false, limit);
}
};
}
Canvas не хранит объекты — состояние нужно держать отдельно. Чем проще структура данных, тем быстрее рендер.
Плохо:
Хорошо:
Разделение сцены на несколько canvas:
<canvas id="background"></canvas>
<canvas id="main"></canvas>
<canvas id="ui"></canvas>
Преимущества:
Каждый вызов методов context стоит дорого.
Рекомендации:
save() /
restore()fillStyle,
strokeStyleЕсли вычисления не успевают за скроллом, можно пропускать кадры:
let lastFrame = -1;
function render(progress) {
const frame = Math.floor(progress * totalFrames);
if (frame === lastFrame) return;
lastFrame = frame;
drawFrame(frame);
}
Использование инструментов разработчика:
Особое внимание:
renderПри сложных сценах canvas 2D становится узким местом. В таких случаях используется WebGL:
Преимущества:
Недостатки:
Ключевые подходы:
requestAnimationFrameЭффективная работа canvas в ScrollMagic — это баланс между визуальной сложностью и вычислительными затратами.