# Implementing Scroll-Triggered Animations in Cloned Web Pages with the AI Website Cloner Template

> Learn to implement scroll-triggered animations in cloned web pages using a reusable hook and IntersectionObserver. Enhance user experience with dynamic Tailwind CSS transitions.

- Repository: [JCodesMore/ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template)
- Tags: how-to-guide
- Published: 2026-06-28

---

**You can implement scroll-triggered animations by creating a reusable `useScrollAnimation` hook that leverages the native `IntersectionObserver` API to toggle Tailwind CSS transition classes when cloned elements enter the viewport.**

The **AI Website Cloner Template** from JCodesMore provides a modern Next.js 16 and React 19 stack with Tailwind CSS v4 and shadcn/ui primitives, making it straightforward to add sophisticated scroll-triggered animations to cloned websites. Since the template isolates each page section into reusable components within `src/app` and `src/components`, you can integrate viewport detection logic without modifying the build configuration. This guide demonstrates how to implement a type-safe hook that detects scroll position and applies smooth entrance animations using either pure CSS transitions or Framer Motion.

## Creating the useScrollAnimation Hook

To detect when elements enter the viewport, create a custom hook in [`src/hooks/useScrollAnimation.ts`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/hooks/useScrollAnimation.ts) that wraps the native `IntersectionObserver` API. This hook returns a ref to attach to any DOM element and a boolean `isVisible` flag that triggers your animation classes, with an optional `once` parameter to prevent replaying animations on scroll up.

```tsx
import { useEffect, useRef, useState } from 'react'

type Options = {
  rootMargin?: string      // e.g. '0px 0px -100px 0px'
  threshold?: number       // 0 → 1
  once?: boolean           // stop observing after first entry?
}

/**
 * Returns a ref to attach to the element and a boolean `isVisible`.
 * When `isVisible` becomes true you can add any animation class.
 */
export function useScrollAnimation<T extends HTMLElement = HTMLDivElement>(
  options: Options = {}
) {
  const elementRef = useRef<T>(null)
  const [isVisible, setIsVisible] = useState(false)

  useEffect(() => {
    const node = elementRef.current
    if (!node) return

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true)
          if (options.once) observer.unobserve(node)
        } else if (!options.once) {
          setIsVisible(false)
        }
      },
      {
        root: null,
        rootMargin: options.rootMargin ?? '0px',
        threshold: options.threshold ?? 0.1,
      }
    )
    observer.observe(node)
    return () => observer.disconnect()
  }, [elementRef, options])

  return { ref: elementRef, isVisible }
}

```

## Applying Animations to Cloned Components

With the hook implemented, you can animate any cloned section by importing the `useScrollAnimation` hook and conditionally applying Tailwind classes. The template's `cn()` utility from [`src/lib/utils.ts`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/lib/utils.ts) helps compose class strings cleanly without template litter.

### Using Tailwind CSS Transitions

Attach the `ref` from the hook to a wrapper element and toggle opacity and transform classes based on the `isVisible` state. This approach requires zero JavaScript animation libraries and leverages GPU-accelerated CSS transitions.

```tsx
import { cn } from '@/lib/utils'               // utility for merging class names
import { useScrollAnimation } from '@/hooks/useScrollAnimation'

export default function FeatureCard() {
  const { ref, isVisible } = useScrollAnimation({ once: true })

  return (
    <div
      ref={ref}
      className={cn(
        'opacity-0 translate-y-8 transition-opacity transition-transform duration-700',
        isVisible && 'opacity-100 translate-y-0'
      )}
    >
      {/* ... cloned markup … */}
    </div>
  )
}

```

### Integrating Framer Motion

For more complex animations, the hook works seamlessly with Framer Motion. Install the library (`npm i framer-motion`) and pass the `isVisible` state to a `motion.div` component to control `initial` and `animate` props.

```tsx
import { motion, MotionProps } from 'framer-motion'
import { useScrollAnimation } from '@/hooks/useScrollAnimation'

export default function AnimatedImage() {
  const { ref, isVisible } = useScrollAnimation({ once: true })

  const motionProps: MotionProps = {
    initial: { opacity: 0, y: 30 },
    animate: { opacity: isVisible ? 1 : 0, y: isVisible ? 0 : 30 },
    transition: { duration: 0.6, ease: 'easeOut' },
  }

  return (
    <motion.div ref={ref} {...motionProps}>
      {/* cloned image element */}
    </motion.div>
  )
}

```

## Key Files and Architecture

Understanding the file structure in the JCodesMore/ai-website-cloner-template repository helps you place animation logic correctly:

- [`src/hooks/useScrollAnimation.ts`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/hooks/useScrollAnimation.ts) — The new hook containing the `IntersectionObserver` logic and TypeScript types.
- [`src/lib/utils.ts`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/lib/utils.ts) — Provides the `cn()` helper function used to conditionally merge Tailwind class names.
- [`src/components/ui/button.tsx`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/components/ui/button.tsx) — Example UI primitive that demonstrates how components receive and spread props, useful for testing the hook.
- [`src/app/page.tsx`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/app/page.tsx) — Main page entry point where cloned sections are assembled and where you can drop animated sections.
- [`src/app/layout.tsx`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/app/layout.tsx) — Root layout that wraps every page; suitable for global scroll context providers if needed.

## Summary

- Create a reusable `useScrollAnimation` hook in [`src/hooks/useScrollAnimation.ts`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/hooks/useScrollAnimation.ts) that wraps `IntersectionObserver` to detect viewport entry without polling.
- Use the `cn()` utility from [`src/lib/utils.ts`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/lib/utils.ts) to cleanly toggle Tailwind animation classes like `opacity-0` and `translate-y-8` based on the `isVisible` state.
- Apply the hook to any cloned component in [`src/app/page.tsx`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/app/page.tsx) or `src/components/ui/` to trigger performant scroll-triggered animations.
- Optionally integrate Framer Motion for complex motion by passing the `isVisible` boolean to `motion.div` components for declarative animation control.

## Frequently Asked Questions

### How do I prevent scroll animations from replaying when scrolling back up?

Set the `once` option to `true` when calling the hook: `useScrollAnimation({ once: true })`. This configuration calls `observer.unobserve(node)` after the first intersection, ensuring the animation only triggers once per element and preventing unwanted replay effects when users scroll back up the page.

### Can I use this approach with Framer Motion instead of Tailwind classes?

Yes. Instead of toggling CSS classes, pass the `isVisible` boolean returned by the hook to Framer Motion's `animate` prop, allowing you to specify precise `initial` and `animate` states with custom durations and easing curves for more complex motion sequences.

### Where should I place scroll-triggered animation logic in a cloned project?

Create the reusable hook in [`src/hooks/useScrollAnimation.ts`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/hooks/useScrollAnimation.ts), then import it into individual components within `src/components/` or sections in [`src/app/page.tsx`](https://github.com/JCodesMore/ai-website-cloner-template/blob/main/src/app/page.tsx). This approach maintains separation of concerns while allowing any cloned section to import animation functionality, consistent with the template's architecture of isolating reusable logic in dedicated hooks.

### Is the IntersectionObserver approach performant for pages with many animated elements?

The `IntersectionObserver` API is highly performant because it runs on the browser's main thread without polling, and the hook's `once` option automatically disconnects observers after triggering. This prevents memory leaks and ensures smooth performance even when animating dozens of cloned sections on a single page.