Интересные примеры
Текст с маской, появление при прокрутке на CSS, магнитная кнопка, счётчик, закреплённая секция, layoutId, View Transitions.
Обновлено
Примеры, после которых сайт выглядит «премиально»
Каждый пример ниже короткий, переиспользуемый и опирается на то, что вы изучили в Основах анимации, Motion и GSAP. Все они должны учитывать prefers-reduced-motion.
1. Заголовок, появляющийся по словам
'use client'
import { motion, stagger } from 'motion/react'
export function RevealTitle({ text }: { text: string }) {
return (
<motion.h1 initial="hidden" animate="show" variants={{ show: { transition: { delayChildren: stagger(0.05) } } }}>
{text.split(' ').map((word, i) => (
<span key={i} className="inline-block overflow-hidden pr-[0.25em]">
<motion.span className="inline-block" variants={{ hidden: { y: '110%' }, show: { y: 0, transition: { ease: [0.22, 1, 0.36, 1], duration: 0.7 } } }}>
{word}
</motion.span>
</span>
))}
</motion.h1>
)
}Трюк: каждое слово лежит в span с overflow-hidden; слово поднимается из-за его края → эффект «маски».
2. Карточки, появляющиеся при прокрутке, только CSS
@supports (animation-timeline: view()) {
.card {
animation: card-in linear both;
animation-timeline: view();
animation-range: entry 10% cover 30%;
}
}
@keyframes card-in {
from { opacity: 0; transform: translateY(40px) scale(0.96); }
}Ноль JavaScript — анимация напрямую привязана к позиции элемента на экране. @supports оставляет старые браузеры без эффекта, но с видимым контентом.
3. «Магнитная» кнопка
Кнопка слегка тянется к курсору:
'use client'
import { motion, useSpring } from 'motion/react'
export function MagneticButton({ children }: { children: React.ReactNode }) {
const x = useSpring(0, { stiffness: 200, damping: 15 })
const y = useSpring(0, { stiffness: 200, damping: 15 })
return (
<motion.button
style={{ x, y }}
onPointerMove={e => {
const r = e.currentTarget.getBoundingClientRect()
x.set((e.clientX - (r.left + r.width / 2)) * 0.3) // 30% расстояния от центра
y.set((e.clientY - (r.top + r.height / 2)) * 0.3)
}}
onPointerLeave={() => (x.set(0), y.set(0))}
>
{children}
</motion.button>
)
}4. Счётчик, досчитывающий до значения
'use client'
import { animate, useInView, useMotionValue, useTransform, motion } from 'motion/react'
import { useEffect, useRef } from 'react'
export function Counter({ to }: { to: number }) {
const ref = useRef(null)
const inView = useInView(ref, { once: true })
const value = useMotionValue(0)
const rounded = useTransform(value, v => Math.round(v).toLocaleString('ru-RU'))
useEffect(() => {
if (inView) animate(value, to, { duration: 1.4, ease: 'easeOut' })
}, [inView, to, value])
return <motion.span ref={ref}>{rounded}</motion.span>
}5. «Закреплённая» секция на GSAP
Изображение стоит на месте, а тексты сменяются по мере прокрутки — классический сторителлинг:
useGSAP(
() => {
const tl = gsap.timeline({ scrollTrigger: { trigger: '.story', start: 'top top', end: '+=2000', scrub: true, pin: true } })
tl.from('.step-1', { opacity: 0, y: 40 })
.to('.step-1', { opacity: 0, y: -40 })
.from('.step-2', { opacity: 0, y: 40 }, '<')
.to('.visual', { scale: 1.15, rotate: 3 }, 0)
},
{ scope: container },
)6. Переход список → детали через layoutId
// в списке
<motion.img layoutId={`photo-${p.id}`} src={p.src} />
// в модалке с деталями
<motion.img layoutId={`photo-${p.id}`} src={p.src} className="w-full" />Изображение «перелетает» из карточки в модалку. В сочетании с перехваченными маршрутами у модалки ещё и свой URL.
7. Переходы между страницами через View Transitions
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 250ms;
}
.product-image { view-transition-name: product-hero; }document.startViewTransition(() => updateTheDOM())Браузер делает «снимок» до и после, затем анимирует разницу — включая морфинг между элементами с одинаковым view-transition-name.
Чек-лист перед публикацией
- Работает ли на 60 fps на среднем телефоне? (DevTools → Performance, замедление CPU 4×)
- Виден ли контент и без анимации (JS отключён, reduced motion)?
- Есть ли у анимаций смысл, а не только украшение?
- Укладывается ли вся вступительная анимация в ~1.2 с?
Коротко
- Текст с маской + stagger, появление при прокрутке на чистом CSS, магнитная кнопка, счётчик, закреплённая секция,
layoutId, View Transitions. - Любой пример — сочетание состояний, easing, stagger и прокрутки.
- Проверяйте на телефоне и с reduced motion.