Контекст выполнения callback-функций

Система событий в Lottie строится вокруг набора callback-функций, которые привязываются к жизненному циклу анимации. Каждая из них вызывается в определённый момент времени и получает доступ к внутреннему состоянию экземпляра анимации. Ключевая особенность — поведение this внутри callback, которое напрямую зависит от способа регистрации обработчика.

Модель событий и жизненный цикл анимации

Lottie Web генерирует события, отражающие этапы работы анимации:

  • инициализация и загрузка данных
  • готовность к воспроизведению
  • обновление кадров
  • завершение цикла
  • разрушение экземпляра

Основные callback-и:

  • DOMLoaded
  • data_ready
  • enterFrame
  • loopComplete
  • complete
  • destroy
  • error

Каждый callback может быть зарегистрирован через конфигурацию или методы управления анимацией.

const anim = lottie.loadAnimation({
  container: document.getElementById('anim'),
  renderer: 'svg',
  loop: true,
  autoplay: true,
  path: 'data.json'
});

anim.addEventListener('complete', function () {
  console.log('Анимация завершена');
});

Контекст this внутри callback-функций

Поведение this в обработчиках Lottie зависит от того, как функция передана.

При использовании обычной функции function () {} контекстом становится объект анимации:

anim.addEventListener('enterFrame', function (e) {
  console.log(this.currentFrame);
});

Здесь this указывает на экземпляр анимации, содержащий:

  • currentFrame
  • totalFrames
  • play()
  • pause()
  • stop()
  • setSpeed()

Такой подход позволяет напрямую управлять анимацией внутри callback без внешних ссылок.

Потеря контекста при использовании стрелочных функций

Стрелочные функции не создают собственный this. Они наследуют его из внешней области видимости:

anim.addEventListener('loopComplete', (e) => {
  console.log(this.currentFrame);
});

В данном случае this не является экземпляром анимации. Обычно он равен window или undefined (в strict mode).

Это ключевая причина, по которой стрелочные функции нежелательны при работе с внутренними callback Lottie, если требуется доступ к API экземпляра.

Явная привязка контекста через bind

При необходимости сохранить внешний контекст используется bind:

function handler() {
  console.log(this.name);
}

const context = { name: 'animation-controller' };

anim.addEventListener('complete', handler.bind(context));

Однако важно понимать: в таком случае this внутри callback больше не относится к анимации Lottie, а указывает на пользовательский объект.

Разделение контекста: instance vs external state

Часто возникает задача одновременного доступа к:

  • экземпляру анимации
  • внешнему состоянию приложения

Оптимальная практика — хранение ссылки на анимацию в замыкании:

const anim = lottie.loadAnimation(config);

const state = {
  counter: 0
};

anim.addEventListener('enterFrame', function () {
  state.counter++;

  if (this.currentFrame > 50) {
    this.pause();
  }
});

Здесь:

  • this — Lottie instance
  • state — внешняя логика

Контексты разделены без конфликтов.

Проблема утраты контекста при передаче методов объекта

Частая ошибка возникает при использовании методов класса:

class Controller {
  constructor(anim) {
    this.anim = anim;
    this.anim.addEventListener('complete', this.onComplete);
  }

  onComplete() {
    console.log(this.anim);
  }
}

В этом случае this внутри onComplete больше не указывает на экземпляр Controller, потому что метод передан как ссылка.

Правильный вариант:

class Controller {
  constructor(anim) {
    this.anim = anim;

    this.onCompl ete = this.onComplete.bind(this);
    this.anim.addEventListener('complete', this.onComplete);
  }

  onComplete() {
    console.log(this.anim);
  }
}

Альтернатива через стрелочные методы класса

Стрелочные методы фиксируют контекст экземпляра класса:

class Controller {
  constructor(anim) {
    this.anim = anim;
    this.anim.addEventListener('complete', this.onComplete);
  }

  onCompl ete = () => {
    console.log(this.anim);
  }
}

Здесь this всегда указывает на Controller, но не на Lottie instance. Это важно учитывать при необходимости доступа к currentFrame и API анимации.

Контекст внутри enterFrame и производительность

Событие enterFrame вызывается на каждом кадре. Контекстные ошибки внутри него особенно критичны, поскольку приводят к:

  • лишним аллокациям функций
  • потере доступа к this.currentFrame
  • деградации производительности из-за лишних замыканий

Оптимальный подход — переиспользование функции:

function onFrame() {
  if (this.currentFrame > 100) {
    this.stop();
  }
}

anim.addEventListener('enterFrame', onFrame);

Создание функции внутри цикла или в рендер-цикле приводит к ненужной нагрузке.

Удаление callback и утечка контекста

Callback-и сохраняют ссылки на контексты, что может приводить к утечкам памяти при отсутствии удаления обработчиков:

function handler() {
  console.log(this.currentFrame);
}

anim.addEventListener('complete', handler);

// позже
anim.removeEventListener('complete', handler);

Если используется bind, необходимо сохранять ссылку:

this.handler = this.handler.bind(this);
anim.addEventListener('complete', this.handler);
anim.removeEventListener('complete', this.handler);

Без сохранённой ссылки удаление невозможно.

Контекст при множественных анимациях

При работе с несколькими экземплярами Lottie часто возникает путаница в this:

const anim1 = lottie.loadAnimation(config1);
const anim2 = lottie.loadAnimation(config2);

function sharedHandler() {
  console.log(this.animationName);
}

anim1.addEventListener('complete', sharedHandler);
anim2.addEventListener('complete', sharedHandler);

В каждом вызове this будет соответствовать конкретному экземпляру анимации, что позволяет использовать один обработчик для разных объектов без потери контекста.

Изоляция контекста через замыкания

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

function createHandler(name) {
  return function () {
    console.log(name);
    this.pause();
  };
}

anim.addEventListener('complete', createHandler('main-animation'));

Здесь:

  • name фиксируется в замыкании
  • this остаётся Lottie instance

Такой подход обеспечивает разделение данных и API анимации.

Контекст и интеграция с внешними фреймворками

При интеграции с UI-фреймворками контекст часто теряется из-за переинициализации компонентов. В таких случаях:

  • экземпляр анимации хранится в ref/field
  • callback-и объявляются стабильно
  • используется явное управление жизненным циклом
let anim;

function init() {
  anim = lottie.loadAnimation(config);

  anim.addEventListener('complete', function () {
    anim.goToAndPlay(0);
  });
}

Здесь обращение идёт не через this, а через замыкание переменной anim, что делает поведение предсказуемым при пересоздании компонентов.

Контекст и асинхронные callback-и

Хотя Lottie callback-и синхронны относительно рендера, их часто комбинируют с асинхронной логикой:

anim.addEventListener('complete', function () {
  setTimeout(() => {
    this.play();
  }, 1000);
});

Внутри setTimeout контекст уже не связан с Lottie. Поэтому используется промежуточная фиксация:

anim.addEventListener('complete', function () {
  const instance = this;

  setTimeout(() => {
    instance.play();
  }, 1000);
});

Контекст должен сохраняться явно при переходе в асинхронные границы.

Системное правило управления контекстом

При работе с callback-функциями Lottie устойчивое поведение достигается при соблюдении трёх принципов:

  • this используется только при прямой регистрации обработчика
  • стрелочные функции применяются только для фиксации внешнего контекста
  • экземпляр анимации сохраняется в явной переменной при асинхронных операциях

Такая модель устраняет неопределённость и обеспечивает предсказуемое поведение callback-системы во всех режимах исполнения.