Реалізація Framer Motion анімацій у React-застосунку

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Framer Motion анімацій у React-застосунку
Середня
~2-3 робочих дні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація Framer Motion анімацій у React-додатку

Framer Motion — декларативна бібліотека анімацій для React. На відміну від GSAP, вона інтегрована в життєвий цикл React: анімації монтування/демонтування працюють нативно, стан анімації синхронізований зі станом компонента. Це робить її переважним вибором для React-додатків, де анімації тісно пов'язані з логікою UI.

Встановлення

npm install framer-motion

Для Next.js 13+ з App Router: компоненти з motion повинні бути "use client". Framer Motion не працює в RSC.

Основні паттерни

Motion-компоненти та варіанти

Варіанти — декларативний спосіб описати стани анімації:

// components/AnimatedCard.tsx
'use client'
import { motion, Variants } from 'framer-motion'

const cardVariants: Variants = {
  hidden: {
    opacity: 0,
    y: 30,
    scale: 0.97,
  },
  visible: {
    opacity: 1,
    y: 0,
    scale: 1,
    transition: {
      duration: 0.5,
      ease: [0.25, 0.46, 0.45, 0.94], // користувацький cubic-bezier
    },
  },
  hover: {
    y: -4,
    boxShadow: '0 20px 40px rgba(0,0,0,0.12)',
    transition: { duration: 0.2, ease: 'easeOut' },
  },
  tap: {
    scale: 0.98,
    transition: { duration: 0.1 },
  },
}

interface AnimatedCardProps {
  children: React.ReactNode
  delay?: number
}

export function AnimatedCard({ children, delay = 0 }: AnimatedCardProps) {
  return (
    <motion.div
      variants={cardVariants}
      initial="hidden"
      animate="visible"
      whileHover="hover"
      whileTap="tap"
      transition={{ delay }}
      className="bg-white rounded-xl p-6 shadow-md cursor-pointer"
    >
      {children}
    </motion.div>
  )
}

Stagger-анімація дочірніх елементів

Framer Motion автоматично передає варіанти дочірнім компонентам через контекст — не потрібно передавати варіант явно:

// components/AnimatedList.tsx
'use client'
import { motion, Variants } from 'framer-motion'

const containerVariants: Variants = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.08,   // затримка між дочірніми
      delayChildren: 0.2,
    },
  },
}

const itemVariants: Variants = {
  hidden: { opacity: 0, x: -20 },
  visible: {
    opacity: 1,
    x: 0,
    transition: { duration: 0.4, ease: 'easeOut' },
  },
}

export function AnimatedList({ items }: { items: string[] }) {
  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="visible"
    >
      {items.map((item, i) => (
        <motion.li key={i} variants={itemVariants}>
          {item}
        </motion.li>
      ))}
    </motion.ul>
  )
}

AnimatePresence: анімація демонтування

Стандартний React не дозволяє анімувати компоненти при unmount — вони просто зникають. AnimatePresence розв'язує це:

// components/Modal.tsx
'use client'
import { AnimatePresence, motion } from 'framer-motion'

interface ModalProps {
  isOpen: boolean
  onClose: () => void
  children: React.ReactNode
}

const overlayVariants = {
  hidden: { opacity: 0 },
  visible: { opacity: 1 },
}

const modalVariants = {
  hidden: { opacity: 0, scale: 0.95, y: -20 },
  visible: {
    opacity: 1,
    scale: 1,
    y: 0,
    transition: { type: 'spring', stiffness: 300, damping: 30 },
  },
  exit: {
    opacity: 0,
    scale: 0.95,
    y: -10,
    transition: { duration: 0.15 },
  },
}

export function Modal({ isOpen, onClose, children }: ModalProps) {
  return (
    <AnimatePresence>
      {isOpen && (
        <>
          <motion.div
            key="overlay"
            className="fixed inset-0 bg-black/50 z-40"
            variants={overlayVariants}
            initial="hidden"
            animate="visible"
            exit="hidden"
            onClick={onClose}
          />
          <motion.div
            key="modal"
            className="fixed inset-0 flex items-center justify-center z-50 pointer-events-none"
          >
            <motion.div
              className="bg-white rounded-2xl p-8 max-w-lg w-full mx-4 pointer-events-auto"
              variants={modalVariants}
              initial="hidden"
              animate="visible"
              exit="exit"
            >
              {children}
            </motion.div>
          </motion.div>
        </>
      )}
    </AnimatePresence>
  )
}

useMotionValue та useTransform

Для анімацій, прив'язаних до руху миші або скролу:

// components/MagneticButton.tsx
'use client'
import { useRef, useState } from 'react'
import { motion, useMotionValue, useSpring, useTransform } from 'framer-motion'

export function MagneticButton({ children }: { children: React.ReactNode }) {
  const ref = useRef<HTMLButtonElement>(null)
  const x = useMotionValue(0)
  const y = useMotionValue(0)

  // Пружинна фізика для гладкості
  const springConfig = { stiffness: 200, damping: 20 }
  const springX = useSpring(x, springConfig)
  const springY = useSpring(y, springConfig)

  const handleMouseMove = (e: React.MouseEvent) => {
    const rect = ref.current!.getBoundingClientRect()
    const cx = rect.left + rect.width / 2
    const cy = rect.top + rect.height / 2
    const strength = 0.3
    x.set((e.clientX - cx) * strength)
    y.set((e.clientY - cy) * strength)
  }

  const handleMouseLeave = () => {
    x.set(0)
    y.set(0)
  }

  return (
    <motion.button
      ref={ref}
      style={{ x: springX, y: springY }}
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      className="px-6 py-3 bg-black text-white rounded-full font-medium"
    >
      {children}
    </motion.button>
  )
}

Scroll-анімації через useScroll

// components/ProgressBar.tsx
'use client'
import { useScroll, useSpring, motion } from 'framer-motion'

export function ReadingProgressBar() {
  const { scrollYProgress } = useScroll()
  const scaleX = useSpring(scrollYProgress, {
    stiffness: 100,
    damping: 30,
    restDelta: 0.001,
  })

  return (
    <motion.div
      style={{ scaleX, transformOrigin: '0%' }}
      className="fixed top-0 left-0 right-0 h-1 bg-blue-500 z-50"
    />
  )
}

Анімація секції при скролі з whileInView:

// components/FadeInSection.tsx
'use client'
import { motion } from 'framer-motion'

export function FadeInSection({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 40 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true, margin: '-100px' }}
      transition={{ duration: 0.6, ease: 'easeOut' }}
    >
      {children}
    </motion.div>
  )
}

viewport.once: true — анімація спрацьовує лише один раз. margin: '-100px' — починає анімацію за 100px до входу у viewport.

Shared Layout анімації (layoutId)

Плавні переходи між елементами через layoutId — Framer Motion відстежує DOM-позицію та інтерполює:

// components/TabsWithAnimation.tsx
'use client'
import { useState } from 'react'
import { motion } from 'framer-motion'

const tabs = ['Огляд', 'Функції', 'Ціни']

export function AnimatedTabs() {
  const [active, setActive] = useState(0)

  return (
    <div className="flex gap-1 bg-gray-100 p-1 rounded-lg w-fit">
      {tabs.map((tab, i) => (
        <button
          key={tab}
          onClick={() => setActive(i)}
          className="relative px-4 py-2 text-sm font-medium z-10"
        >
          {active === i && (
            <motion.div
              layoutId="active-tab" // унікальний ID для відстеження
              className="absolute inset-0 bg-white rounded-md shadow-sm"
              transition={{ type: 'spring', stiffness: 400, damping: 35 }}
            />
          )}
          <span className="relative">{tab}</span>
        </button>
      ))}
    </div>
  )
}

Типові терміни

Набір базових анімацій (fade-in, stagger, hover) — 1 робочий день. AnimatePresence для модалів/дравнів/рутингу + layout-анімації + scroll-ефекти — 3–4 робочих дні. Складні інтерактивні сцени з useMotionValue та фізичними пружинами — від 5 днів.