Ref и реактивность

GSAP (GreenSock Animation Platform) — это мощная библиотека для анимации в JavaScript, предоставляющая гибкие возможности управления временными шкалами и свойствами объектов. В современных фронтенд-фреймворках, таких как Vue или React, часто используется концепция реактивности и ссылок на DOM-элементы (ref), что требует особого подхода при интеграции с GSAP.


Ссылки (ref) и GSAP

ref — это объект, предоставляющий ссылку на DOM-элемент или компонент. В Vue 3 это объект, у которого значение хранится в ref.value. В React ref создается через useRef и доступен через свойство .current.

Пример использования ref с GSAP в Vue 3:

import { ref, onMounted } from 'vue';
import { gsap } from 'gsap';

export default {
  setup() {
    const box = ref(null);

    onMounted(() => {
      gsap.to(box.value, { x: 200, duration: 2 });
    });

    return { box };
  }
}

В этом примере box — это ссылка на DOM-элемент <div>. GSAP работает напрямую с box.value, так как это реальный DOM.

Особенности работы с ref:

  • GSAP требует DOM-узлы, а не сами реактивные объекты.
  • При работе с Vue или React необходимо использовать onMounted или useEffect, чтобы DOM-элемент уже существовал.
  • Любое обновление реактивного значения ref не автоматически обновляет GSAP-анимацию — требуется инициализация анимации после изменения DOM.

Интеграция GSAP с реактивными данными

GSAP анимации часто зависят от состояния приложения. Использование реактивных данных позволяет синхронизировать визуальные эффекты с изменениями состояния.

Пример реактивной анимации в Vue 3:

import { ref, watch, onMounted } from 'vue';
import { gsap } from 'gsap';

export default {
  setup() {
    const isActive = ref(false);
    const box = ref(null);

    onMounted(() => {
      watch(isActive, (newVal) => {
        if (newVal) {
          gsap.to(box.value, { scale: 1.5, duration: 0.5 });
        } else {
          gsap.to(box.value, { scale: 1, duration: 0.5 });
        }
      });
    });

    return { isActive, box };
  }
}

Ключевые моменты:

  • Анимация запускается только при изменении реактивного значения.
  • GSAP управляет текущим состоянием свойства, что позволяет избегать конфликтов между реактивностью и анимацией.
  • Если необходимо плавное управление состоянием, можно использовать gsap.timeline(), чтобы комбинировать несколько эффектов.

Использование gsap.timeline с ref

gsap.timeline() позволяет создавать последовательности анимаций, которые реагируют на события или изменения состояния.

import { ref, onMounted } from 'vue';
import { gsap } from 'gsap';

export default {
  setup() {
    const box = ref(null);

    onMounted(() => {
      const tl = gsap.timeline({ defaults: { duration: 1 } });
      tl.to(box.value, { x: 100 })
        .to(box.value, { y: 50 })
        .to(box.value, { rotation: 45 });
    });

    return { box };
  }
}

Преимущества использования timeline:

  • Позволяет строить сложные последовательности анимаций с точным контролем времени.
  • Легко интегрируется с реактивными переменными, управляя запуском и паузой анимаций через изменение состояния.

Реактивные компоненты и перерендер DOM

При работе с реактивными компонентами важно учитывать, что DOM может меняться после перерендеринга, а GSAP хранит ссылки на старые узлы.

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

  • Использовать onMounted или nextTick в Vue для доступа к DOM после рендера.
  • В React — использовать useLayoutEffect, чтобы гарантировать наличие DOM перед анимацией.
  • Для динамически создаваемых элементов можно использовать watchEffect или наблюдатели, чтобы запускать анимации на новые элементы.

Совмещение реактивности и GSAP с массивами

Для списка элементов, создаваемых через v-for или map, важно правильно обращаться к каждому ref:

import { ref, onMounted } from 'vue';
import { gsap } from 'gsap';

export default {
  setup() {
    const boxes = ref([]);

    onMounted(() => {
      boxes.value.forEach((el, i) => {
        gsap.to(el, { y: i * 20, duration: 0.5 });
      });
    });

    return { boxes };
  }
}

Замечания:

  • boxes должен содержать массив DOM-элементов.
  • Анимации можно комбинировать через stagger для удобного эффекта последовательного появления:
gsap.to(boxes.value, { y: 50, duration: 1, stagger: 0.2 });

Поддержка реактивности при повторном изменении состояния

Для динамических интерфейсов часто возникает ситуация, когда ref указывает на элемент, который перерисовывается. GSAP не будет автоматически отслеживать новый DOM. В таких случаях используют:

  • watch или watchEffect для обновления анимации.
  • key для принудительного пересоздания элемента в Vue.
  • useEffect с зависимостями в React.

Пример Vue с повторным рендером:

watch(items, () => {
  gsap.fromTo(
    boxes.value,
    { opacity: 0 },
    { opacity: 1, duration: 0.5, stagger: 0.1 }
  );
});

Эта практика обеспечивает синхронизацию состояния и анимаций без конфликтов.


Итоги работы с ref и реактивностью

  • ref предоставляет доступ к реальным DOM-узлам для GSAP.
  • Анимации необходимо инициализировать после рендера DOM (onMounted, useLayoutEffect).
  • Реактивные данные можно связывать с GSAP через watch, watchEffect или useEffect.
  • Для массивов элементов удобнее использовать stagger или циклы по ref-массива.
  • Для динамического интерфейса важно отслеживать изменения DOM и обновлять анимации, чтобы не потерять ссылки на новые элементы.

GSAP и реактивные фреймворки отлично дополняют друг друга, если соблюдаются правила работы с ref и реактивными данными.