Skip to main content
The ScrollAnimation component is a flexible React wrapper that triggers animations when elements come into view. It uses Framer Motion’s useInView hook to detect visibility and animate children accordingly.

Overview

This component is a React component (JSX) that wraps any children with scroll-triggered animation capabilities. It’s used throughout the Chitagá Tech site to create smooth entrance effects.

Source Location

~/workspace/source/src/components/ScrollAnimation.jsx

Props

children
ReactNode
required
Content to be animated when scrolled into view
variants
object
Custom Framer Motion animation variants. Supports any valid motion properties.
initial
string
default:"\"hidden\""
Initial animation state (corresponds to variant key)
animate
string
default:"\"visible\""
Target animation state when element is in view
transition
object
default:"{ duration: 0.6, ease: 'easeOut' }"
Framer Motion transition configuration
className
string
default:"\"\""
CSS class name(s) to apply to the wrapper element
delay
number
default:"0"
Delay in seconds before animation starts (added to transition)
as
string
default:"\"div\""
HTML element type to render (div, section, article, etc.)
style
object
default:"{}"
Inline styles to apply to the wrapper element

Usage

Basic Usage

import ScrollAnimation from './ScrollAnimation.jsx';

<ScrollAnimation>
  <h2>This will fade in from below</h2>
</ScrollAnimation>

Custom Variants

<ScrollAnimation
  variants={{
    hidden: { opacity: 0, scale: 0.8 },
    visible: { opacity: 1, scale: 1 }
  }}
>
  <div>This will scale up</div>
</ScrollAnimation>

With Delay

<ScrollAnimation delay={0.3}>
  <p>This appears 0.3 seconds after entering view</p>
</ScrollAnimation>

Custom Element Type

<ScrollAnimation as="section" className="my-section">
  <h2>Section Title</h2>
  <p>Section content</p>
</ScrollAnimation>

Slide from Left

<ScrollAnimation
  variants={{
    hidden: { opacity: 0, x: -50 },
    visible: { opacity: 1, x: 0 }
  }}
  delay={0.2}
>
  <div>Slides in from the left</div>
</ScrollAnimation>

With Inline Styles

<ScrollAnimation style={{ flex: 1 }}>
  <div>Content with flex: 1</div>
</ScrollAnimation>

In Astro Components

When using in Astro files, add client:load directive:
---
import ScrollAnimation from './ScrollAnimation.jsx';
---

<ScrollAnimation className="hero-content" delay={0.2} client:load as="div">
  <h1>Animated Title</h1>
  <p>Animated subtitle</p>
</ScrollAnimation>

Default Animation

If no custom variants are provided, the default animation is:
const defaultVariants = {
  hidden: { opacity: 0, y: 50 },
  visible: { opacity: 1, y: 0 }
};
This creates a fade-in from below effect.

Intersection Observer Configuration

The component uses useInView with these settings:
const isInView = useInView(ref, { 
  once: true,                      // Animation triggers only once
  amount: 0.3,                     // 30% of element must be visible
  margin: "0px 0px -100px 0px"    // Triggers 100px before entering viewport
});

Configuration Explained

  • once: true: Animation only plays the first time element enters view
  • amount: 0.3: Triggers when 30% of element is visible
  • margin: "0px 0px -100px 0px": Negative bottom margin triggers animation earlier

How It Works

  1. Component creates a ref attached to the animated element
  2. useInView hook monitors when ref element enters viewport
  3. When isInView becomes true, animation state changes from initial to animate
  4. Framer Motion interpolates between variant values
  5. Custom delay is added to the transition

Common Animation Patterns

Fade In (Default)

<ScrollAnimation>
  {children}
</ScrollAnimation>

Slide Up

<ScrollAnimation variants={{ hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0 } }}>
  {children}
</ScrollAnimation>

Slide from Right

<ScrollAnimation variants={{ hidden: { opacity: 0, x: 50 }, visible: { opacity: 1, x: 0 } }}>
  {children}
</ScrollAnimation>

Scale Up

<ScrollAnimation variants={{ hidden: { opacity: 0, scale: 0.8 }, visible: { opacity: 1, scale: 1 } }}>
  {children}
</ScrollAnimation>

Rotate In

<ScrollAnimation variants={{ hidden: { opacity: 0, rotate: -10 }, visible: { opacity: 1, rotate: 0 } }}>
  {children}
</ScrollAnimation>

Custom Transitions

You can customize the transition timing:
<ScrollAnimation
  transition={{ duration: 1.2, ease: "easeInOut" }}
  delay={0.5}
>
  {children}
</ScrollAnimation>

Available Easing Functions

  • "linear"
  • "easeIn"
  • "easeOut" (default)
  • "easeInOut"
  • "circIn", "circOut", "circInOut"
  • "backIn", "backOut", "backInOut"
  • Custom cubic bezier: [0.17, 0.67, 0.83, 0.67]

Staggered Animations

Create staggered effects by using incremental delays:
<ScrollAnimation delay={0.2}>
  <Card1 />
</ScrollAnimation>

<ScrollAnimation delay={0.4}>
  <Card2 />
</ScrollAnimation>

<ScrollAnimation delay={0.6}>
  <Card3 />
</ScrollAnimation>

Performance Notes

  • Uses Framer Motion’s optimized animation engine
  • once: true prevents re-triggering, improving performance
  • Intersection Observer is efficient for scroll detection
  • Animations use CSS transforms (GPU-accelerated)

Browser Support

Requires:
  • Modern browsers with Intersection Observer API support
  • React 16.8+ (for hooks)
  • Framer Motion library

Dependencies

{
  "framer-motion": "^10.x.x",
  "react": "^18.x.x"
}
Imports used:
import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';

TypeScript Version

For TypeScript projects, you can add type definitions:
import { motion, useInView, Variants } from 'framer-motion';
import { useRef, ReactNode, CSSProperties } from 'react';

interface ScrollAnimationProps {
  children: ReactNode;
  variants?: Variants;
  initial?: string;
  animate?: string;
  transition?: any;
  className?: string;
  delay?: number;
  as?: keyof JSX.IntrinsicElements;
  style?: CSSProperties;
}

const ScrollAnimation = ({
  children,
  variants = {},
  initial = "hidden",
  animate = "visible",
  transition = { duration: 0.6, ease: "easeOut" },
  className = "",
  delay = 0,
  as = "div",
  style = {}
}: ScrollAnimationProps) => {
  // ... implementation
};

Advanced Example

Combining multiple features:
<ScrollAnimation
  as="article"
  className="feature-card"
  style={{ padding: '2rem' }}
  variants={{
    hidden: { 
      opacity: 0, 
      y: 50, 
      scale: 0.95,
      rotate: -5 
    },
    visible: { 
      opacity: 1, 
      y: 0, 
      scale: 1,
      rotate: 0
    }
  }}
  transition={{ 
    duration: 0.8, 
    ease: [0.17, 0.67, 0.83, 0.67],
    scale: { duration: 0.5 }
  }}
  delay={0.3}
>
  <h3>Feature Title</h3>
  <p>Feature description with complex animation</p>
</ScrollAnimation>

Accessibility

  • Animations respect prefers-reduced-motion if configured in Framer Motion
  • Content is accessible even if JavaScript is disabled (appears immediately)
  • No impact on screen readers (animations are visual only)
To respect reduced motion preferences:
import { useReducedMotion } from 'framer-motion';

const shouldReduceMotion = useReducedMotion();
const transition = shouldReduceMotion 
  ? { duration: 0 } 
  : { duration: 0.6, ease: "easeOut" };

Build docs developers (and LLMs) love