Generated banner for Gsap Framer Scroll Animation
Generated logo for Gsap Framer Scroll Animation

Gsap Framer Scroll Animation

GitHubOfficialCreated by Utkarsh PatrikarDistributed by GitHub

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

Use this skill whenever the user wants to build scroll animations, scroll effects, parallax, scroll-triggered reveals, pinned sections, horizontal scroll, text animations, or any motion tied to scroll position — in vanilla JS, React, or Next.js. Covers GSAP ScrollTrigger (pinning, scrubbing, snapping, timelines, horizontal scroll, ScrollSmoother, matchMedia) and Framer Motion / Motion v12 (useScroll, useTransform, useSpring, whileInView, variants). Use this skill even if the user just says "animate on scroll", "fade in as I scroll", "make it scroll like Apple", "parallax effect", "sticky section", "scroll progress bar", or "entrance animation". Also triggers for Copilot prompt patterns for GSAP or Framer Motion code generation. Pairs with the premium-frontend-ui skill for creative philosophy and design-level polish.
— the description by Utkarsh Patrikar

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides technical implementation guidance for building scroll-based animations and effects. It covers the GSAP ScrollTrigger API and Framer Motion (Motion v12) hooks, offering code recipes, setup instructions, and integration patterns for vanilla JavaScript, React, and Next.js projects. It focuses on how to link motion to scroll position, manage pinning, scrubbing, and entrance animations.

What it does

  • Provides setup instructions for GSAP and Framer Motion libraries.
  • Offers code recipes for common scroll patterns like parallax, pinning, and fade-ins.
  • Explains the use of GSAP ScrollTrigger for complex timelines and horizontal scrolling.
  • Details Framer Motion hooks such as useScroll and useTransform for declarative animations.
  • Includes guidance on React integration, including the useGSAP hook and Next.js compatibility.
  • Provides reference material for configuring scroll triggers, offsets, and toggle actions.

When to use it

  • When you need to implement scroll-triggered reveals or entrance animations.
  • When building complex scroll-linked effects like parallax or pinned sections.
  • When you need to synchronize animations with the user's scroll position.
  • When choosing between GSAP for complex timelines or Framer Motion for declarative React components.
  • When you require specific code patterns for scrubbing, snapping, or horizontal scroll containers.

Advantages

  • Centralized reference for two industry-standard animation libraries.
  • Includes ready-to-use code snippets for common scroll-based motion patterns.
  • Provides specific guidance for React and Next.js integration.
  • Offers clear distinctions on when to use GSAP versus Framer Motion.
  • Includes best practices for animation cleanup and accessibility.

Usage examples

“How do I create a fade-in animation when an element enters the viewport using Framer Motion?”
The agent provides a code example using the motion.div component with whileInView and viewport settings.
“Show me how to pin a section while scrolling using GSAP.”
The agent provides a GSAP ScrollTrigger configuration snippet demonstrating the pin property and timeline setup.
“How do I link an image scale effect to the scroll position in React?”
The agent explains how to use the useScroll and useTransform hooks from Framer Motion to map scroll progress to a scale value.
“What is the correct way to register ScrollTrigger in a React project?”
The agent provides the import statements and the required gsap.registerPlugin(ScrollTrigger) call.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/github/gsap-framer-scroll-animation/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip gsap-framer-scroll-animation.zip -d .agents/skills

Documentation by Utkarsh Patrikar

GSAP & Framer Motion — Scroll Animations Skill

Production-grade scroll animations with GitHub Copilot prompts, ready-to-use code recipes, and deep API references.

Design Companion: This skill provides the technical implementation for scroll-driven motion. For the creative philosophy, design principles, and premium aesthetics that should guide how and when to animate, always cross-reference the premium-frontend-ui skill. Together they form a complete approach: premium-frontend-ui decides the what and why; this skill delivers the how.

Quick Library Selector

NeedUse
Vanilla JS, Webflow, VueGSAP
Pinning, horizontal scroll, complex timelinesGSAP
React / Next.js, declarative styleFramer Motion
whileInView entrance animationsFramer Motion
Both in same Next.js appSee notes in references

Read the relevant reference file for full recipes and Copilot prompts:

  • GSAP → references/gsap.md — ScrollTrigger API, all recipes, React integration

  • Framer Motion → references/framer.md — useScroll, useTransform, all recipes

Setup (Always Do First)

GSAP
npm install gsap
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger); // MUST call before any ScrollTrigger usage
Framer Motion (Motion v12, 2025)
npm install motion   # new package name since mid-2025
# or: npm install framer-motion  — still works, same API
import { motion, useScroll, useTransform, useSpring } from 'motion/react';
// legacy: import { motion } from 'framer-motion'  — also valid

Workflow

  1. Interpret the user's intent to identify if GSAP or Framer Motion is the best fit.

  2. Read the relevant reference document in references/ for detailed APIs and patterns.

  3. Suggest the required package installation if not already present.

  4. Implement the scaffold for the animation structure, adhering to the requested format (React components, hook requirements, or vanilla JS).

  5. Apply the correct tools (scrolling vs in-view elements) ensuring accessibility options are present and hooks don't cause infinite re-renders.

The 5 Most Common Scroll Patterns

Quick reference — full recipes with Copilot prompts are in the reference files.

1. Fade-in on enter (GSAP)
gsap.from('.card', {
  opacity: 0, y: 50, stagger: 0.15, duration: 0.8,
  scrollTrigger: { trigger: '.card', start: 'top 85%' }
});
2. Fade-in on enter (Framer Motion)
<motion.div
  initial={{ opacity: 0, y: 40 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: '-80px' }}
  transition={{ duration: 0.6 }}
/>
3. Scrub / scroll-linked (GSAP)
gsap.to('.hero-img', {
  scale: 1.3, opacity: 0, ease: 'none',
  scrollTrigger: { trigger: '.hero', start: 'top top', end: 'bottom top', scrub: true }
});
4. Scroll-linked (Framer Motion)
const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'end start'] });
const y = useTransform(scrollYProgress, [0, 1], [0, -100]);
return <motion.div style={{ y }} />;
5. Pinned timeline (GSAP)
const tl = gsap.timeline({
  scrollTrigger: { trigger: '.section', pin: true, scrub: 1, start: 'top top', end: '+=200%' }
});
tl.from('.title', { opacity: 0, y: 60 }).from('.img', { scale: 0.85 });

Critical Rules (Apply Always)

  • GSAP: always call gsap.registerPlugin(ScrollTrigger) before using it

  • GSAP scrub: always use ease: 'none' — easing feels wrong when scrub is active

  • GSAP React: use useGSAP from @gsap/react, never plain useEffect — it auto-cleans ScrollTriggers

  • GSAP debug: add markers: true during development; remove before production

  • Framer: useTransform output must go into style prop of a motion.* element, not a plain div

  • Framer Next.js: always add 'use client' at top of any file using motion hooks

  • Both: animate only transform and opacity — avoid width, height, box-shadow

  • Accessibility: always check prefers-reduced-motion — see each reference file for patterns

  • Premium polish: follow the premium-frontend-ui skill principles for motion timing, easing curves, and restraint — animation should enhance, never overwhelm

Copilot Prompting Tips

  • Give Copilot the full selector, base image, and scroll range upfront — vague prompts produce vague code

  • For GSAP, always specify: selector, start/end strings, whether you want scrub or toggleActions

  • For Framer, always specify: which hook (useScroll vs whileInView), offset values, what to transform

  • Paste the exact error message when asking /fix — Copilot fixes are dramatically better with real errors

  • Use @workspace scope in Copilot Chat so it reads your existing component structure

Reference Files

FileContents
references/gsap.mdFull ScrollTrigger API reference, 10 recipes, React (useGSAP), Lenis, matchMedia, accessibility
references/framer.mdFull useScroll / useTransform API, 8 recipes, variants, Motion v12 notes, Next.js tips
SkillRelationship
premium-frontend-uiCreative philosophy, design principles, and aesthetic guidelines — defines when and why to animate

Files

references/framer.md

Framer Motion (Motion v12) — Full Reference

Framer Motion was renamed to Motion in mid-2025. The npm package is now motion, the import path is motion/react. All APIs are identical. framer-motion still works.

Table of Contents

  1. Package & Import Paths

  2. Two Types of Scroll Animation

  3. useScroll — Options Reference

  4. useTransform — Full Reference

  5. useSpring for Smoothing

  6. Recipes with Copilot Prompts

    • Scroll progress bar

    • Reusable ScrollReveal wrapper

    • Parallax layers

    • Horizontal scroll section

    • Image reveal with clipPath

    • Scroll-linked navbar (hide/show)

    • Staggered card grid

    • 3D tilt on scroll

  7. Variants Pattern for Stagger

  8. Motion Value Events

  9. Next.js & App Router Notes

  10. Accessibility

  11. Common Copilot Pitfalls


Package & Import Paths

npm install motion          # recommended (renamed 2025)
npm install framer-motion   # still works — same API
// Recommended (Motion v12+)
import { motion, useScroll, useTransform, useSpring, useMotionValueEvent } from 'motion/react';

// Legacy — still valid
import { motion, useScroll, useTransform } from 'framer-motion';

Motion v12 new features (2025):

  • Hardware-accelerated scroll via browser ScrollTimeline API

  • useScroll and scroll() now GPU-accelerated by default

  • New color types: oklch, oklab, color-mix animatable directly

  • Full React 19 + concurrent rendering support


Two Types of Scroll Animation

Scroll-triggered (fires once when element enters viewport)
<motion.div
  initial={{ opacity: 0, y: 50 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: '-80px' }}
  transition={{ duration: 0.6, ease: [0.21, 0.47, 0.32, 0.98] }}
>
  Content
</motion.div>

viewport.margin — negative value triggers animation before element fully enters view. viewport.once — true means animate once, never reverse.

Scroll-linked (continuous, tied to scroll position)
const { scrollYProgress } = useScroll();
const opacity = useTransform(scrollYProgress, [0, 1], [0, 1]);
return <motion.div style={{ opacity }}>Content</motion.div>;

The value updates on every scroll frame — must use style prop, not animate.


useScroll — Options Reference

const {
  scrollX,          // Absolute horizontal scroll (pixels)
  scrollY,          // Absolute vertical scroll (pixels)
  scrollXProgress,  // Horizontal progress 0→1 between offsets
  scrollYProgress,  // Vertical progress 0→1 between offsets
} = useScroll({
  // Track a scrollable element instead of the viewport
  container: containerRef,

  // Track an element's position within the container
  target: targetRef,

  // Define when tracking starts and ends
  // Format: ["target position container position", "target position container position"]
  offset: ['start end', 'end start'],
  // Common offset pairs:
  // ['start end', 'end start']    = track while element is anywhere in view
  // ['start end', 'end end']      = track from element entering to bottom of page
  // ['start start', 'end start']  = track while element exits top
  // ['center center', 'end start']= track from center-center to exit

  // Update when content size changes (small perf cost, false by default)
  trackContentSize: false,
});

Offset string values:

  • start = 0 = top/left edge

  • center = 0.5 = middle

  • end = 1 = bottom/right edge

  • Numbers 0–1 also work: [0, 1] = ['start', 'end']


useTransform — Full Reference

// Map a motion value from one range to another
const y = useTransform(scrollYProgress, [0, 1], [0, -200]);

// Multi-stop interpolation
const opacity = useTransform(
  scrollYProgress,
  [0, 0.2, 0.8, 1],
  [0, 1, 1, 0]
);

// Non-numeric values (colors, strings)
const color = useTransform(
  scrollYProgress,
  [0, 0.5, 1],
  ['#6366f1', '#ec4899', '#f97316']
);

// CSS string values
const clipPath = useTransform(
  scrollYProgress,
  [0, 1],
  ['inset(0% 100% 0% 0%)', 'inset(0% 0% 0% 0%)']
);

// Disable clamping (allow values outside output range)
const y = useTransform(scrollYProgress, [0, 1], [0, -200], { clamp: false });

// Transform from multiple inputs
const combined = useTransform(
  [scrollX, scrollY],
  ([x, y]) => Math.sqrt(x * x + y * y)
);

Rule: useTransform output is a MotionValue. It must go into the style prop of a motion.* element. Plain <div style={{ y }}> will NOT work — must be <motion.div style={{ y }}>.


useSpring for Smoothing

Wrap any MotionValue in useSpring to add spring physics — great for progress bars that feel alive.

const { scrollYProgress } = useScroll();

const smooth = useSpring(scrollYProgress, {
  stiffness: 100,   // Higher = faster/snappier response
  damping: 30,      // Higher = less bounce
  restDelta: 0.001  // Precision threshold for stopping
});

return <motion.div style={{ scaleX: smooth }} />;

For a subtle lag (not physics), use useTransform with clamp: false and an eased range instead.


Recipes with Copilot Prompts

1. Scroll Progress Bar

Copilot Chat Prompt:

Framer Motion: fixed scroll progress bar at top of page.
useScroll for page scroll progress, useSpring to smooth scaleX.
stiffness 100, damping 30. Grows left to right.
'use client';
import { useScroll, useSpring, motion } from 'motion/react';

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

  return (
    <motion.div
      style={{ scaleX }}
      className="fixed top-0 left-0 right-0 h-1 bg-indigo-500 origin-left z-50"
    />
  );
}

2. Reusable ScrollReveal Wrapper

Copilot Chat Prompt:

Framer Motion: reusable ScrollReveal component that wraps children with 
fade-in-up entrance animation using whileInView. Props: delay (default 0), 
duration (default 0.6), once (default true). viewport margin -80px.
TypeScript. 'use client'.
'use client';
import { motion } from 'motion/react';

interface ScrollRevealProps {
  children: React.ReactNode;
  delay?: number;
  duration?: number;
  once?: boolean;
  className?: string;
}

export function ScrollReveal({
  children, delay = 0, duration = 0.6, once = true, className
}: ScrollRevealProps) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 40 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once, margin: '-80px' }}
      transition={{ duration, delay, ease: [0.21, 0.47, 0.32, 0.98] }}
      className={className}
    >
      {children}
    </motion.div>
  );
}

// Usage:
// <ScrollReveal delay={0.2}><h2>Section Title</h2></ScrollReveal>

3. Parallax Layers

Copilot Chat Prompt:

Framer Motion parallax section: background moves y from 0% to 30% (slow),
foreground text moves y from 50 to -50px (fast). 
Both use target ref with offset ['start end', 'end start'].
Fade out at top and bottom using opacity useTransform [0, 0.3, 0.7, 1] → [0,1,1,0].
'use client';
import { useRef } from 'react';
import { motion, useScroll, useTransform } from 'motion/react';

export function ParallaxSection() {
  const ref = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start end', 'end start'],
  });

  const backgroundY = useTransform(scrollYProgress, [0, 1], ['0%', '30%']);
  const textY        = useTransform(scrollYProgress, [0, 1], [50, -50]);
  const opacity      = useTransform(scrollYProgress, [0, 0.3, 0.7, 1], [0, 1, 1, 0]);

  return (
    <section ref={ref} className="relative h-screen overflow-hidden flex items-center justify-center">
      <motion.div
        className="absolute inset-0 bg-cover bg-center"
        style={{ backgroundImage: 'url(/hero-bg.jpg)', y: backgroundY, scale: 1.2 }}
      />
      <motion.div style={{ y: textY, opacity }} className="relative z-10 text-center text-white">
        <h2 className="text-6xl font-bold">Parallax Title</h2>
        <p className="text-xl mt-4">Scrolls at a different speed</p>
      </motion.div>
    </section>
  );
}

4. Horizontal Scroll Section

Copilot Chat Prompt:

Framer Motion horizontal scroll: 4 cards scroll horizontally as user scrolls vertically.
Outer container ref height 300vh controls speed (sticky pattern).
useScroll tracks outer container, useTransform maps scrollYProgress to x '0%' → '-75%'.
'use client';
import { useRef } from 'react';
import { motion, useScroll, useTransform } from 'motion/react';

const cards = [
  { id: 1, title: 'Card One',   color: 'bg-indigo-500' },
  { id: 2, title: 'Card Two',   color: 'bg-pink-500'   },
  { id: 3, title: 'Card Three', color: 'bg-amber-500'  },
  { id: 4, title: 'Card Four',  color: 'bg-teal-500'   },
];

export function HorizontalScroll() {
  const containerRef = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: containerRef,
    offset: ['start start', 'end end'],
  });

  const x = useTransform(scrollYProgress, [0, 1], ['0%', '-75%']);

  return (
    <div ref={containerRef} className="relative h-[300vh]">
      <div className="sticky top-0 h-screen overflow-hidden">
        <motion.div
          style={{ x, width: `${cards.length * 100}vw` }}
          className="flex gap-6 h-full items-center px-8"
        >
          {cards.map(card => (
            <div
              key={card.id}
              className={`${card.color} w-screen h-[70vh] rounded-2xl flex items-center justify-center flex-shrink-0`}
            >
              <h3 className="text-white text-4xl font-bold">{card.title}</h3>
            </div>
          ))}
        </motion.div>
      </div>
    </div>
  );
}

5. Image Reveal with clipPath

Copilot Chat Prompt:

Framer Motion: image reveals left to right as it scrolls into view.
useScroll target ref, offset ['start end', 'center center'].
useTransform clipPath from 'inset(0% 100% 0% 0%)' to 'inset(0% 0% 0% 0%)'.
Also scale from 1.15 to 1.
'use client';
import { useRef } from 'react';
import { motion, useScroll, useTransform } from 'motion/react';

export function ImageReveal({ src, alt }: { src: string; alt: string }) {
  const ref = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start end', 'center center'],
  });

  const clipPath = useTransform(
    scrollYProgress,
    [0, 1],
    ['inset(0% 100% 0% 0%)', 'inset(0% 0% 0% 0%)']
  );
  const scale = useTransform(scrollYProgress, [0, 1], [1.15, 1]);

  return (
    <div ref={ref} className="overflow-hidden rounded-xl">
      <motion.img
        src={src} alt={alt}
        style={{ clipPath, scale }}
        className="w-full h-full object-cover"
      />
    </div>
  );
}

6. Scroll-linked Navbar (Hide on Scroll Down)

Copilot Chat Prompt:

Framer Motion navbar: transparent when at top, white with shadow after 80px.
Hide by sliding up when scrolling down, reveal when scrolling up.
Use useScroll, useMotionValueEvent to detect direction.
Animate y, backgroundColor, boxShadow with motion.nav.
'use client';
import { useRef, useState } from 'react';
import { motion, useScroll, useMotionValueEvent } from 'motion/react';

export function Navbar() {
  const { scrollY } = useScroll();
  const [scrolled, setScrolled] = useState(false);
  const [hidden,   setHidden]   = useState(false);
  const prevRef = useRef(0);

  useMotionValueEvent(scrollY, 'change', latest => {
    const nextScrolled = latest > 80;
    const nextHidden = latest > prevRef.current && latest > 200;
    setScrolled(current => (current === nextScrolled ? current : nextScrolled));
    setHidden(current => (current === nextHidden ? current : nextHidden));
    prevRef.current = latest;
  });

  return (
    <motion.nav
      animate={{
        y: hidden ? -80 : 0,
        backgroundColor: scrolled ? 'rgba(255,255,255,0.95)' : 'rgba(255,255,255,0)',
        boxShadow: scrolled ? '0 1px 24px rgba(0,0,0,0.08)' : 'none',
      }}
      transition={{ duration: 0.3, ease: 'easeInOut' }}
      className="fixed top-0 left-0 right-0 z-50 backdrop-blur-sm"
    >
      {/* nav links */}
    </motion.nav>
  );
}

7. Staggered Card Grid

Copilot Chat Prompt:

Framer Motion: card grid with stagger entrance. Use variants: 
container has staggerChildren 0.1, delayChildren 0.2.
Each card: hidden (opacity 0, y 40, scale 0.96) → visible (opacity 1, y 0, scale 1).
Trigger with whileInView on the container. Once.
'use client';
import { motion } from 'motion/react';

const containerVariants = {
  hidden: {},
  visible: {
    transition: { staggerChildren: 0.1, delayChildren: 0.2 }
  }
};

const cardVariants = {
  hidden:  { opacity: 0, y: 40, scale: 0.96 },
  visible: {
    opacity: 1, y: 0, scale: 1,
    transition: { duration: 0.5, ease: [0.21, 0.47, 0.32, 0.98] }
  }
};

export function CardGrid({ cards }: { cards: { id: number; title: string }[] }) {
  return (
    <motion.div
      variants={containerVariants}
      initial="hidden"
      whileInView="visible"
      viewport={{ once: true, margin: '-50px' }}
      className="grid grid-cols-3 gap-6"
    >
      {cards.map(card => (
        <motion.div key={card.id} variants={cardVariants}
          className="bg-white rounded-xl p-6 shadow-sm border"
        >
          <h3>{card.title}</h3>
        </motion.div>
      ))}
    </motion.div>
  );
}

8. 3D Tilt on Scroll

Copilot Chat Prompt:

Framer Motion: 3D perspective card that rotates on X axis as it scrolls through view.
rotateX 15→0→-15, scale 0.9→1→0.9, opacity 0→1→0.
Target ref with offset ['start end', 'end start']. Wrap in perspective container.
'use client';
import { useRef } from 'react';
import { motion, useScroll, useTransform } from 'motion/react';

export function TiltCard({ children }: { children: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ['start end', 'end start'],
  });

  const rotateX = useTransform(scrollYProgress, [0, 0.5, 1], [15,  0, -15]);
  const scale   = useTransform(scrollYProgress, [0, 0.5, 1], [0.9, 1,  0.9]);
  const opacity = useTransform(scrollYProgress, [0, 0.2, 0.8, 1], [0, 1, 1, 0]);

  return (
    <div ref={ref} style={{ perspective: '1000px' }}>
      <motion.div
        style={{ rotateX, scale, opacity }}
        className="bg-white rounded-2xl p-8 shadow-lg"
      >
        {children}
      </motion.div>
    </div>
  );
}

Variants Pattern for Stagger

Variants propagate automatically from parent to children — you don't need to pass them down manually.

const parent = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.1,   // Delay between each child
      delayChildren: 0.2,      // Initial delay before first child
      when: 'beforeChildren',  // Parent animates before children
    }
  }
};

const child = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0, transition: { duration: 0.5 } }
};

// Children with `variants={child}` automatically get the stagger
// when the parent transitions between 'hidden' and 'visible'

Motion Value Events

import { useScroll, useMotionValueEvent } from 'motion/react';

const { scrollY } = useScroll();

// Fires on every change — use for imperative side effects
useMotionValueEvent(scrollY, 'change', latest => {
  console.log('scroll position:', latest);
});

// Detect scroll direction
const [direction, setDirection] = useState<'up' | 'down'>('down');

useMotionValueEvent(scrollY, 'change', current => {
  const diff = current - scrollY.getPrevious()!;
  setDirection(diff > 0 ? 'down' : 'up');
});

When to use useMotionValueEvent vs useTransform:

  • Use useTransform when you want a CSS value that animates smoothly (y, opacity, color)

  • Use useMotionValueEvent when you want to fire React state changes or side effects


Next.js & App Router Notes

// Every file using motion hooks must be a Client Component
'use client';

// For page-level scroll tracking in App Router, use useScroll in a layout
// that's already a client component — don't try to use it in Server Components

// If you need SSR-safe scroll animations, gate with:
import { useEffect, useState } from 'react';
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return null; // or a skeleton

Recommended pattern for Next.js App Router:

  1. Keep all motion.* components in separate 'use client' files

  2. Import them into Server Components — they'll be client-rendered automatically

  3. Use AnimatePresence at the layout level for page transitions


Accessibility

import { useReducedMotion } from 'motion/react';

export function AnimatedCard() {
  const prefersReducedMotion = useReducedMotion();

  return (
    <motion.div
      initial={{ opacity: 0, y: prefersReducedMotion ? 0 : 50 }}
      whileInView={{ opacity: 1, y: 0 }}
      transition={{ duration: prefersReducedMotion ? 0 : 0.6 }}
    >
      Content
    </motion.div>
  );
}

Or disable all scroll-linked transforms when reduced motion is preferred:

const prefersReducedMotion = useReducedMotion();
const y = useTransform(
  scrollYProgress, [0, 1],
  prefersReducedMotion ? [0, 0] : [100, -100]  // no movement if reduced motion
);

Common Copilot Pitfalls

Missing 'use client': Copilot forgets to add this for Next.js App Router files. Every file using useScroll, useTransform, motion.*, or any hook needs 'use client' at the top.

Using style prop on a plain div: Copilot sometimes writes <div style={{ y }}> where y is a MotionValue. This silently does nothing. Must be <motion.div style={{ y }}>.

Old import path: Copilot still generates from 'framer-motion' (valid, but legacy). Current canonical: from 'motion/react'.

Forgetting offset on useScroll: Without offset, scrollYProgress tracks the full page from 0 to 1 — not the element's position. Always pass target + offset for element-level tracking.

Missing ref on target: Copilot sometimes writes target: ref but forgets to attach ref to the DOM element.

const ref = useRef(null);
const { scrollYProgress } = useScroll({ target: ref }); // ← ref passed
return <div ref={ref}>...</div>;                          // ← ref attached

Using animate prop for scroll-linked values: Scroll-linked values must use style, not animate. animate runs on mount/unmount, not on scroll.

// ❌ Wrong
<motion.div animate={{ opacity }} />

// ✅ Correct
<motion.div style={{ opacity }} />

Not smoothing scroll progress: Raw scrollYProgress can feel mechanical on fine motion. Wrap in useSpring for progress bars and UI elements that need a polished feel.

Requirements and compatibility

Written for: GitHub Copilot

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Node.js

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Utkarsh Patrikar
Publisher
GitHub (Official)
Distributed by
GitHub
Source
github/awesome-copilot · skills/gsap-framer-scroll-animation · commit 797337f
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Utkarsh Patrikar or GitHub.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download gsap-framer-scroll-animation.zip