webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Unelte
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Formulare
  12. 12Date și backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animații
  16. 16Testare
  17. 17Arhitectură
Animații · Lecția 4 din 4

Exemple interesante

Text cu mască, scroll reveal în CSS, buton magnetic, contor, secțiune pinned, layoutId, View Transitions.

Actualizat

Exemple care fac un site să pară „premium”

Fiecare exemplu de mai jos e scurt, refolosibil și se bazează pe ce ai învățat în Bazele animației, Motion și GSAP. Toate trebuie să respecte prefers-reduced-motion.

1. Titlu care apare cuvânt cu cuvânt

'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>
  )
}

Trucul: fiecare cuvânt e într-un span cu overflow-hidden; cuvântul urcă din afara lui → efect de „mască”.

2. Carduri care intră la scroll, doar 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); }
}

Zero JavaScript — animația e legată direct de poziția elementului în ecran. @supports lasă browserele vechi fără efect, dar cu conținut vizibil.

3. Buton „magnetic”

Butonul se apropie ușor de cursor:

'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% din distanța față de centru
        y.set((e.clientY - (r.top + r.height / 2)) * 0.3)
      }}
      onPointerLeave={() => (x.set(0), y.set(0))}
    >
      {children}
    </motion.button>
  )
}

4. Contor care numără până la valoare

'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('ro-RO'))

  useEffect(() => {
    if (inView) animate(value, to, { duration: 1.4, ease: 'easeOut' })
  }, [inView, to, value])

  return <motion.span ref={ref}>{rounded}</motion.span>
}

5. Secțiune „pinned” cu GSAP

Imaginea stă fixă, textele se schimbă pe măsură ce dai scroll — clasicul scroll storytelling:

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. Tranziție listă → detaliu cu layoutId

// în listă
<motion.img layoutId={`photo-${p.id}`} src={p.src} />
// în modalul de detaliu
<motion.img layoutId={`photo-${p.id}`} src={p.src} className="w-full" />

Imaginea „zboară” din card în modal. Combinat cu rutele interceptate, modalul are și URL propriu.

7. Tranziții între pagini cu View Transitions

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 250ms;
}
.product-image { view-transition-name: product-hero; }
document.startViewTransition(() => updateTheDOM())

Browserul face un „snapshot” înainte și după, apoi animă diferența — inclusiv morph între elemente cu același view-transition-name.

Checklist înainte să publici

  • Rulează la 60 fps pe un telefon mediu? (DevTools → Performance, CPU throttling 4×)
  • Conținutul e vizibil și fără animație (JS dezactivat, reduced motion)?
  • Animațiile au un scop, nu doar decor?
  • Durata totală a intro-ului e sub ~1.2 s?

Pe scurt

  • Text cu mască + stagger, apariții la scroll în CSS pur, buton magnetic, contor, secțiune pinned, layoutId, View Transitions.
  • Fiecare exemplu e o combinație de: stări, easing, stagger, scroll.
  • Testează pe telefon și cu reduced motion.

Surse oficiale

Exerciții

Ți-a fost utilă pagina?

Un click — fără cont.