Анимации, запускаемые по действиям пользователя, формируют интерактивность интерфейса. Библиотека Motion One предоставляет простой и производительный API для связывания анимаций с событиями мыши: наведение, нажатие, перемещение и уход курсора.
Ключевой принцип — разделение логики событий и описания анимации. Событие инициирует анимацию, а сама анимация задаётся декларативно.
mouseenter и эффект наведенияНаведение курсора — один из наиболее распространённых сценариев. Используется для подсветки элементов, увеличения кнопок, отображения дополнительных деталей.
import { animate } from "motion";
const button = document.querySelector(".btn");
button.addEventListener("mouseenter", () => {
animate(button, { scale: 1.1 }, { duration: 0.2 });
});
button.addEventListener("mouseleave", () => {
animate(button, { scale: 1 }, { duration: 0.2 });
});
Особенности:
mouseenter срабатывает один раз при входе курсораmouseleave — при выходеmouseover)Для естественного поведения важно правильно подобрать функцию сглаживания:
animate(button,
{ scale: 1.1 },
{ duration: 0.3, easing: "ease-out" }
);
Популярные варианты:
ease-in — ускорениеease-out — замедлениеease-in-out — комбинированный вариантlinear — равномерное движениеmousedown и эффект нажатияИмитация “нажатия” усиливает ощущение физического взаимодействия:
button.addEventListener("mousedown", () => {
animate(button, { scale: 0.95 }, { duration: 0.1 });
});
button.addEventListener("mouseup", () => {
animate(button, { scale: 1.1 }, { duration: 0.1 });
});
Практика:
Состояния интерфейса могут пересекаться. Для корректной работы важно учитывать их совместное влияние:
let isHovered = false;
button.addEventListener("mouseenter", () => {
isHovered = true;
animate(button, { scale: 1.1 });
});
button.addEventListener("mouseleave", () => {
isHovered = false;
animate(button, { scale: 1 });
});
button.addEventListener("mousedown", () => {
animate(button, { scale: 0.95 });
});
button.addEventListener("mouseup", () => {
animate(button, { scale: isHovered ? 1.1 : 1 });
});
mousemove)Динамические эффекты, зависящие от положения курсора, используются для параллакса, интерактивных карточек и 3D-эффектов.
const card = document.querySelector(".card");
card.addEventListener("mousemove", (event) => {
const rect = card.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const rotateX = (y / rect.height - 0.5) * -20;
const rotateY = (x / rect.width - 0.5) * 20;
animate(card, {
transform: `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`
}, { duration: 0.2 });
});
mouseleaveВажно возвращать элемент в исходное состояние:
card.addEventListener("mouseleave", () => {
animate(card, {
transform: "rotateX(0deg) rotateY(0deg)"
}, { duration: 0.3 });
});
animate() для нескольких свойствОдновременно можно анимировать несколько параметров:
animate(card, {
scale: 1.05,
boxShadow: "0 10px 30px rgba(0,0,0,0.2)"
}, {
duration: 0.3
});
Событие mousemove вызывается десятки раз в секунду. Без
оптимизации возможны проблемы:
let ticking = false;
card.addEventListener("mousemove", (event) => {
if (!ticking) {
requestAnimationFrame(() => {
// логика анимации
ticking = false;
});
ticking = true;
}
});
Предпочтительно:
transformopacityИзбегать:
width, heighttop, leftПри большом количестве элементов целесообразно использовать делегирование:
document.addEventListener("mouseover", (event) => {
const item = event.target.closest(".item");
if (!item) return;
animate(item, { scale: 1.05 });
});
Функция animate() возвращает объект управления:
const controls = animate(button, { scale: 1.1 });
button.addEventListener("mouseleave", () => {
controls.stop();
});
Возможности:
stop() — остановкаplay() — воспроизведениеpause() — паузаАнимация дочерних элементов усиливает визуальное восприятие:
const card = document.querySelector(".card");
const title = card.querySelector(".title");
const image = card.querySelector("img");
card.addEventListener("mouseenter", () => {
animate(image, { scale: 1.1 });
animate(title, { y: -10, opacity: 1 });
});
card.addEventListener("mouseleave", () => {
animate(image, { scale: 1 });
animate(title, { y: 0, opacity: 0.8 });
});
Анимации могут управлять CSS custom properties:
animate(card, {
"--rotate": "10deg"
});
.card {
transform: rotate(var(--rotate));
}
Библиотека поддерживает пружинные анимации:
animate(button,
{ scale: 1.2 },
{ type: "spring", stiffness: 300 }
);
Параметры:
stiffness — жёсткостьdamping — затуханиеmass — массаconst card = document.querySelector(".card");
card.addEventListener("mouseenter", () => {
animate(card, {
scale: 1.05,
boxShadow: "0 20px 40px rgba(0,0,0,0.2)"
});
});
card.addEventListener("mousemove", (e) => {
const rect = card.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width - 0.5;
const y = (e.clientY - rect.top) / rect.height - 0.5;
animate(card, {
transform: `rotateX(${y * -15}deg) rotateY(${x * 15}deg)`
}, { duration: 0.1 });
});
card.addEventListener("mouseleave", () => {
animate(card, {
scale: 1,
transform: "rotateX(0deg) rotateY(0deg)",
boxShadow: "0 0 0 rgba(0,0,0,0)"
});
});
1. Конфликт анимаций Несколько вызовов
animate() могут перезаписывать друг друга.
2. Отсутствие сброса состояния Элемент остаётся в промежуточном положении.
3. Чрезмерные вычисления в mousemove
Приводят к падению FPS.
4. Использование неподходящих свойств Layout-свойства вызывают перерасчёт DOM.
Интерактивные анимации, основанные на событиях мыши, позволяют создавать живые интерфейсы, где каждый элемент реагирует на действия пользователя мгновенно и предсказуемо.