Реакция на клики

События клика являются одним из ключевых триггеров интерактивной анимации. В библиотеке Motion One реакция на клики реализуется через стандартные механизмы JavaScript (обработчики событий) в сочетании с функцией animate. Это позволяет создавать мгновенные, плавные и контролируемые визуальные отклики на действия пользователя.


Базовая структура обработки клика

Минимальный сценарий включает:

  • выбор DOM-элемента
  • навешивание обработчика click
  • запуск анимации внутри обработчика
import { animate } from "motion"

const button = document.querySelector(".button")

button.addEventListener("click", () => {
  animate(button, { scale: 1.2 }, { duration: 0.2 })
})

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

  • анимация запускается при каждом клике
  • значения могут задаваться как конечные состояния
  • Motion One автоматически интерполирует начальное значение

Анимация “нажатия” (press effect)

Один из самых распространённых паттернов — имитация физического нажатия:

button.addEventListener("click", () => {
  animate(
    button,
    { scale: [1, 0.9, 1] },
    { duration: 0.3, easing: "ease-out" }
  )
})

Особенности:

  • используется массив значений для создания ключевых кадров
  • быстрый “провал” и возврат создают ощущение клика
  • easing влияет на восприятие “упругости”

Переключение состояний (toggle)

Часто требуется не просто одноразовая анимация, а переключение между состояниями:

let active = false

button.addEventListener("click", () => {
  active = !active

  animate(button, {
    backgroundColor: active ? "#4caf50" : "#f44336",
    scale: active ? 1.2 : 1
  })
})

Здесь:

  • состояние хранится вручную
  • анимация отражает текущее логическое состояние
  • возможна комбинация нескольких свойств

Анимация нескольких элементов

Клик по одному элементу может запускать анимацию других:

const items = document.querySelectorAll(".item")

button.addEventListener("click", () => {
  animate(items, { opacity: [0, 1], y: [20, 0] }, {
    delay: (i) => i * 0.1,
    duration: 0.4
  })
})

Особенности:

  • поддержка NodeList
  • функция delay для создания каскада
  • единый вызов управляет множеством элементов

Использование разных свойств при клике

Motion One поддерживает широкий спектр CSS-свойств:

Трансформации

animate(el, { rotate: 180, scale: 1.5 })

Прозрачность

animate(el, { opacity: 0 })

Цвета

animate(el, { backgroundColor: "#000" })

Позиционирование

animate(el, { x: 100, y: -50 })

Управление временем и плавностью

Настройки времени играют ключевую роль:

animate(el, 
  { scale: 1.3 },
  {
    duration: 0.5,
    easing: "cubic-bezier(0.22, 1, 0.36, 1)"
  }
)

Параметры:

  • duration — длительность
  • delay — задержка
  • easing — кривая ускорения
  • repeat — количество повторов

Повторная анимация при каждом клике

По умолчанию Motion One всегда запускает новую анимацию, даже если предыдущая не завершена. Это важно учитывать:

button.addEventListener("click", () => {
  animate(button, { rotate: 360 }, { duration: 1 })
})

Каждый клик добавляет новую анимацию — возможен “накопительный” эффект.


Контроль анимации через объект

animate возвращает контроллер:

const animation = animate(el, { x: 200 }, { duration: 1 })

animation.pause()
animation.play()
animation.cancel()

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

let animation

button.addEventListener("click", () => {
  if (animation) {
    animation.cancel()
  }

  animation = animate(el, { x: 200 })
})

Реакция с учётом текущего состояния стилей

Можно считывать текущее значение перед анимацией:

button.addEventListener("click", () => {
  const currentScale = getComputedStyle(button).transform

  animate(button, { scale: 1.5 })
})

Это полезно при сложных сценариях, где состояние изменяется вне анимации.


Комбинирование с классами

Иногда логика разделяется:

button.addEventListener("click", () => {
  button.classList.toggle("active")

  animate(button, {
    scale: button.classList.contains("active") ? 1.2 : 1
  })
})

Анимация SVG при клике

Motion One отлично работает с SVG:

const circle = document.querySelector("circle")

circle.addEventListener("click", () => {
  animate(circle, { r: 50 }, { duration: 0.3 })
})

Цепочки анимаций

Можно запускать последовательные действия:

button.addEventListener("click", async () => {
  await animate(button, { scale: 0.8 }, { duration: 0.1 }).finished
  await animate(button, { scale: 1.2 }, { duration: 0.2 }).finished
  await animate(button, { scale: 1 }, { duration: 0.1 }).finished
})

Важно:

  • используется finished (Promise)
  • последовательность контролируется через await

Реакция на разные типы кликов

Различение типов событий:

button.addEventListener("mousedown", () => {
  animate(button, { scale: 0.9 }, { duration: 0.1 })
})

button.addEventListener("mouseup", () => {
  animate(button, { scale: 1 }, { duration: 0.1 })
})

Это даёт более “живую” реакцию, чем обычный click.


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

Чтобы избежать перегрузки:

let isAnimating = false

button.addEventListener("click", async () => {
  if (isAnimating) return

  isAnimating = true

  await animate(button, { rotate: 360 }, { duration: 1 }).finished

  isAnimating = false
})

Связка с пользовательскими данными

Клик может изменять не только UI, но и данные:

let count = 0

button.addEventListener("click", () => {
  count++

  animate(button, {
    scale: [1, 1.3, 1]
  })

  console.log(count)
})

Оптимизация производительности

Рекомендуется:

  • анимировать transform и opacity
  • избегать width, height, top, left
  • использовать короткие длительности

Пример оптимального варианта:

animate(el, { transform: "translateX(100px)" })

Частые ошибки

1. Повторное создание обработчиков

// плохо — обработчик добавляется многократно
button.oncl ick = () => { ... }

2. Отсутствие контроля состояния

Приводит к конфликтующим анимациям.

3. Анимация layout-свойств

Снижает производительность.


Расширенные паттерны

Анимация “взрыва” элементов

button.addEventListener("click", () => {
  animate(".particle", {
    x: () => Math.random() * 200 - 100,
    y: () => Math.random() * 200 - 100,
    opacity: [1, 0]
  }, {
    duration: 0.6
  })
})

Интерактивные карточки

card.addEventListener("click", () => {
  animate(card, {
    rotateY: [0, 180]
  }, {
    duration: 0.6
  })
})

Вывод структуры взаимодействия

Типичный поток:

  1. Пользователь кликает
  2. Срабатывает обработчик
  3. Обновляется состояние (при необходимости)
  4. Запускается анимация
  5. При необходимости — контроль завершения

Ключевые принципы

  • клик — это триггер, а не логика
  • анимация должна быть быстрой и отзывчивой
  • состояние и визуал должны быть синхронизированы
  • предпочтение отдаётся transform-анимациям
  • управление анимацией важно в сложных интерфейсах