Hover-эффекты — это реакция интерфейса на наведение курсора. В библиотеке Motion One они реализуются декларативно и с высокой производительностью за счёт использования Web Animations API. Основная задача — обеспечить плавную, отзывчивую и легко управляемую анимацию при изменении состояния элемента.
Ключевое преимущество Motion One — минималистичный API, позволяющий описывать сложные взаимодействия без перегруженного кода.
Самый простой способ — использовать обработчики событий
mouseenter и mouseleave:
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 })
})
Ключевые моменты:
animate() автоматически создаёт и управляет
анимациейrequestAnimationFramehover() — декларативный подходMotion One предоставляет утилиту hover(), упрощающую
работу с hover-состояниями:
import { hover, animate } from "motion"
const button = document.querySelector(".btn")
hover(button, () => {
return animate(button, { scale: 1.1 })
})
Возвращаемое значение — это функция отмены анимации, которая автоматически вызывается при уходе курсора.
Для более сложного поведения можно явно разделить состояния:
hover(button, () => {
const enterAnimation = animate(button, { scale: 1.1 })
return () => {
animate(button, { scale: 1 })
}
})
Такой подход даёт полный контроль над логикой выхода из hover-состояния.
Hover-эффекты редко ограничиваются масштабированием. Часто комбинируются:
hover(card, () => {
return animate(card, {
scale: 1.05,
rotate: "1deg",
boxShadow: "0 10px 30px rgba(0,0,0,0.2)"
}, {
duration: 0.3,
easing: "ease-out"
})
})
Популярные свойства:
transform (scale, rotate, translate)opacityfilter (blur, brightness)box-shadowПлавность анимации определяется функцией easing:
animate(element, { scale: 1.1 }, {
duration: 0.3,
easing: [0.25, 0.1, 0.25, 1]
})
Часто используемые варианты:
ease-in — ускорениеease-out — замедлениеease-in-out — комбинированный вариантКастомные cubic-bezier позволяют добиться уникального поведения.
Можно комбинировать несколько анимаций:
hover(button, () => {
return animate(button, [
{ scale: 1 },
{ scale: 1.15 },
{ scale: 1.1 }
], {
duration: 0.4
})
})
Это создаёт эффект «переразгона» — сначала сильное увеличение, затем стабилизация.
Hover-эффект родителя может управлять дочерними элементами:
const card = document.querySelector(".card")
const image = card.querySelector("img")
const title = card.querySelector("h3")
hover(card, () => {
animate(image, { scale: 1.1 })
animate(title, { y: -5 })
return () => {
animate(image, { scale: 1 })
animate(title, { y: 0 })
}
})
Так создаётся комплексный интерактивный эффект.
Если элемент содержит список, можно добавить последовательность:
import { animate, stagger } from "motion"
hover(menu, () => {
animate("li", { x: 10 }, {
delay: stagger(0.05)
})
})
Каждый элемент реагирует с небольшой задержкой, создавая «волновой» эффект.
Hover-анимации выполняются часто, поэтому важно соблюдать оптимизации:
Рекомендации:
transform и opacity вместо
layout-свойствwidth, height, top,
leftwill-change при необходимости.card {
will-change: transform;
}
Motion One автоматически управляет конфликтами, но иногда требуется ручной контроль:
let animation
hover(button, () => {
animation?.cancel()
animation = animate(button, { scale: 1.1 })
return () => {
animation?.cancel()
animate(button, { scale: 1 })
}
})
Это предотвращает накопление анимаций при быстром движении курсора.
Для более «живого» эффекта можно применять пружинную физику:
import { spring } from "motion"
hover(button, () => {
return animate(button, { scale: 1.1 }, {
easing: spring({ stiffness: 300, damping: 20 })
})
})
Параметры:
stiffness — жёсткостьdamping — затуханиеИногда выгодно комбинировать Motion One и CSS:
.button {
transition: background 0.2s;
}
.button:hover {
background: #333;
}
hover(button, () => {
return animate(button, { scale: 1.1 })
})
CSS отвечает за простые эффекты, Motion One — за сложные.
Hover недоступен на сенсорных устройствах, поэтому важно учитывать альтернативы:
focus для клавиатурыclickbutton.addEventListener("focus", () => {
animate(button, { scale: 1.1 })
})
1. Поднятие карточки
animate(card, {
y: -10,
boxShadow: "0 15px 40px rgba(0,0,0,0.2)"
})
2. Увеличение изображения
animate(img, { scale: 1.1 })
3. Подсветка кнопки
animate(button, {
backgroundColor: "#000",
color: "#fff"
})
4. Появление overlay
animate(overlay, { opacity: 1 })
Частые проблемы:
При масштабировании проекта hover-эффекты лучше организовывать:
const hoverScale = (element) => {
return hover(element, () => {
return animate(element, { scale: 1.05 }, { duration: 0.2 })
})
}
Это упрощает поддержку и делает код консистентным.
В UI-библиотеках hover-логика часто инкапсулируется:
function createHoverEffect(el) {
return hover(el, () => {
return animate(el, { scale: 1.1 })
})
}
Позволяет повторно использовать поведение без дублирования кода.
Важную роль играет длительность:
0.4s — декоративные эффекты
Слишком длинные hover-анимации ухудшают UX.
Тонкая настройка включает:
Небольшие изменения (например, scale 1.05 → 1.08) сильно влияют на восприятие.
Hover часто комбинируется с:
activefocuspressedbutton.addEventListener("mousedown", () => {
animate(button, { scale: 0.95 })
})
button.addEventListener("mouseup", () => {
animate(button, { scale: 1.1 })
})
Это создаёт полноценную интерактивную систему.
Параллакс при hover:
hover(card, (event) => {
const x = event.clientX
return animate(card, {
rotateY: x / 50
})
})
Градиентное движение:
animate(button, {
backgroundPosition: "100% 0%"
})
Типичная схема:
Такая модель обеспечивает предсказуемое и стабильное поведение интерфейса.