Производительность canvas

Работа с анимациями на основе <canvas> в сочетании с библиотекой ScrollMagic требует особого внимания к производительности. В отличие от DOM-анимаций, canvas не хранит состояние объектов — каждый кадр отрисовывается вручную. Это создает дополнительную нагрузку на процессор и графическую подсистему, особенно при скролл-зависимых эффектах.

Основные источники нагрузки

1. Частая перерисовка кадров При использовании ScrollMagic сцены могут обновляться на каждом событии прокрутки. Если каждый скролл вызывает полную перерисовку canvas, это приводит к падению FPS.

2. Большое количество объектов Чем больше элементов отрисовывается в canvas (частицы, изображения, текст), тем выше нагрузка.

3. Работа с изображениями Декодирование и масштабирование изображений в реальном времени сильно влияет на производительность.

4. Неправильная синхронизация с браузером Использование scroll-событий напрямую без оптимизации может вызывать избыточные вызовы рендеринга.


Связка ScrollMagic и requestAnimationFrame

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;
}

Преимущество:

  • Ограничение количества перерисовок до частоты обновления экрана
  • Сглаживание анимации

Частичная перерисовка (dirty rectangles)

Полная очистка canvas через clearRect — дорогая операция. При возможности следует перерисовывать только измененные области.

context.clearRect(x, y, width, height);

Подход эффективен, если:

  • объекты перемещаются локально
  • сцена не меняется полностью

Использование OffscreenCanvas

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);

Масштабирование и devicePixelRatio

Для четкости на 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 не хранит объекты — состояние нужно держать отдельно. Чем проще структура данных, тем быстрее рендер.

Плохо:

  • сложные классы объектов
  • глубокие вложенности

Хорошо:

  • плоские массивы
  • простые координаты и параметры

Использование слоев (multiple canvas)

Разделение сцены на несколько canvas:

  • фон (редко меняется)
  • основной слой
  • UI/текст
<canvas id="background"></canvas>
<canvas id="main"></canvas>
<canvas id="ui"></canvas>

Преимущества:

  • перерисовываются только нужные слои
  • снижение нагрузки

Оптимизация работы с контекстом

Каждый вызов методов context стоит дорого.

Рекомендации:

  • минимизировать количество save() / restore()
  • избегать частой смены fillStyle, strokeStyle
  • группировать операции

Пропуск кадров (frame skipping)

Если вычисления не успевают за скроллом, можно пропускать кадры:

let lastFrame = -1;

function render(progress) {
    const frame = Math.floor(progress * totalFrames);

    if (frame === lastFrame) return;

    lastFrame = frame;
    drawFrame(frame);
}

Профилирование

Использование инструментов разработчика:

  • Performance в браузере
  • FPS meter
  • Memory usage

Особое внимание:

  • длительность render
  • количество вызовов
  • блокировки main thread

WebGL как альтернатива

При сложных сценах canvas 2D становится узким местом. В таких случаях используется WebGL:

Преимущества:

  • использование GPU
  • высокая производительность при большом количестве объектов

Недостатки:

  • сложность разработки
  • необходимость шейдеров

Итоговые принципы оптимизации

Ключевые подходы:

  • синхронизация через requestAnimationFrame
  • минимизация перерисовок
  • кэширование ресурсов
  • контроль количества кадров
  • разделение слоев
  • перенос вычислений вне main thread

Эффективная работа canvas в ScrollMagic — это баланс между визуальной сложностью и вычислительными затратами.