webroad.online
  1. 1Веб
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Инструменты
  8. 8React
  9. 9Стейт-менеджмент
  10. 10Next.js
  11. 11Формы
  12. 12Данные и бэкенд
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Анимации
  16. 16Тестирование
  17. 17Архитектура
Анимации · Урок 4 из 4

Интересные примеры

Текст с маской, появление при прокрутке на 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.

Официальные источники

Упражнения

Была ли страница полезной?

Один клик — без регистрации.