CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-framer-motion

A production-ready motion library for React that provides comprehensive animation and gesture APIs for creating fluid, performant interactions.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

scroll-view.mddocs/

Scroll and View Detection

Scroll tracking hooks and utilities for scroll-driven animations and viewport intersection detection.

Capabilities

Scroll Tracking Hook

Track scroll progress and position with reactive motion values.

/**
 * Track scroll progress and position
 * @param options - Scroll tracking options
 * @returns Object containing scroll motion values
 */
function useScroll(options?: UseScrollOptions): ScrollMotionValues;

interface UseScrollOptions {
  /**
   * Element to track scroll within (default: window)
   */
  container?: React.RefObject<Element>;
  
  /**
   * Target element to track relative to container
   */
  target?: React.RefObject<Element>;
  
  /**
   * Offset ranges for when to start/end progress calculation
   * Format: ["start start", "end end"] where each position can be:
   * - "start", "center", "end" (of viewport)
   * - Number (pixels from start)
   * - Percentage string (e.g., "25%")
   */
  offset?: [string, string];
  
  /**
   * Axis to track ("x" or "y", default: "y")
   */
  axis?: "x" | "y";
}

interface ScrollMotionValues {
  /**
   * Horizontal scroll position in pixels
   */
  scrollX: MotionValue<number>;
  
  /**
   * Vertical scroll position in pixels
   */
  scrollY: MotionValue<number>;
  
  /**
   * Horizontal scroll progress (0-1)
   */
  scrollXProgress: MotionValue<number>;
  
  /**
   * Vertical scroll progress (0-1)
   */
  scrollYProgress: MotionValue<number>;
}

Usage Examples:

import { motion, useScroll, useTransform } from "framer-motion";
import { useRef } from "react";

// Basic scroll tracking
function ScrollExample() {
  const { scrollY, scrollYProgress } = useScroll();
  
  const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [1, 0.5, 0]);
  const scale = useTransform(scrollYProgress, [0, 1], [1, 0.8]);
  
  return (
    <motion.div
      style={{ opacity, scale }}
      className="fixed top-0 left-0 w-full h-20 bg-blue-500"
    >
      Header (opacity: {Math.round(opacity.get() * 100)}%)
    </motion.div>
  );
}

// Element-specific scroll tracking
function ElementScrollExample() {
  const containerRef = useRef<HTMLDivElement>(null);
  const targetRef = useRef<HTMLDivElement>(null);
  
  const { scrollYProgress } = useScroll({
    target: targetRef,
    offset: ["start end", "end start"]
  });
  
  const x = useTransform(scrollYProgress, [0, 1], ["-100%", "0%"]);
  
  return (
    <div>
      <div ref={containerRef} className="h-screen overflow-y-auto">
        <div className="h-screen bg-gray-100">Scroll down</div>
        <motion.div
          ref={targetRef}
          style={{ x }}
          className="h-96 bg-blue-500"
        >
          Animates based on scroll position
        </motion.div>
        <div className="h-screen bg-gray-100">More content</div>
      </div>
    </div>
  );
}

// Container scroll tracking
function ContainerScrollExample() {
  const containerRef = useRef<HTMLDivElement>(null);
  
  const { scrollY, scrollYProgress } = useScroll({
    container: containerRef
  });
  
  const backgroundY = useTransform(scrollY, [0, 500], [0, -100]);
  
  return (
    <div ref={containerRef} className="h-96 overflow-y-auto">
      <motion.div
        style={{ y: backgroundY }}
        className="h-[200vh] bg-gradient-to-b from-blue-400 to-purple-600"
      >
        Parallax background
      </motion.div>
    </div>
  );
}

Viewport Intersection Hook

Detect when elements enter or leave the viewport.

/**
 * Detect when an element is in the viewport
 * @param ref - Reference to element to observe
 * @param options - Intersection options
 * @returns Boolean indicating if element is in view
 */
function useInView(
  ref: React.RefObject<Element>,
  options?: UseInViewOptions
): boolean;

interface UseInViewOptions {
  /**
   * Root element for intersection calculation (default: viewport)
   */
  root?: React.RefObject<Element>;
  
  /**
   * Root margin for intersection calculation (CSS margin syntax)
   */
  margin?: string;
  
  /**
   * Amount of element that must be visible (0-1 or "some"/"all")
   */
  amount?: number | "some" | "all";
  
  /**
   * Only trigger once when element comes into view
   */
  once?: boolean;
  
  /**
   * Initial state before observation starts
   */
  initial?: boolean;
}

Usage Examples:

import { motion, useInView } from "framer-motion";
import { useRef } from "react";

// Basic in-view detection
function InViewExample() {
  const ref = useRef<HTMLDivElement>(null);
  const isInView = useInView(ref, { once: true });
  
  return (
    <div>
      <div className="h-screen bg-gray-100">Scroll down</div>
      <motion.div
        ref={ref}
        initial={{ opacity: 0, y: 100 }}
        animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 100 }}
        transition={{ duration: 0.8, ease: "easeOut" }}
        className="h-96 bg-blue-500 flex items-center justify-center text-white"
      >
        Animates when in view
      </motion.div>
    </div>
  );
}

// Advanced in-view options
function AdvancedInViewExample() {
  const ref = useRef<HTMLDivElement>(null);
  const isInView = useInView(ref, {
    margin: "-100px",
    amount: 0.3,
    once: false
  });
  
  return (
    <motion.div
      ref={ref}
      animate={{
        scale: isInView ? 1 : 0.8,
        opacity: isInView ? 1 : 0.5
      }}
      transition={{ duration: 0.6 }}
      className="h-96 bg-green-500"
    >
      Scales when 30% visible (with 100px margin)
    </motion.div>
  );
}

DOM Scroll Utilities

Direct DOM scroll utilities that work without React.

/**
 * Track scroll with callback (DOM utility)
 * @param onScroll - Callback function called on scroll
 * @param options - Scroll options
 * @returns Cleanup function
 */
function scroll(
  onScroll: (info: ScrollInfo) => void,
  options?: ScrollOptions
): () => void;

/**
 * Advanced scroll tracking with detailed information
 * @param onScroll - Callback function with detailed scroll info
 * @param options - Scroll options
 * @returns Cleanup function
 */
function scrollInfo(
  onScroll: (info: DetailedScrollInfo) => void,
  options?: ScrollOptions
): () => void;

interface ScrollOptions {
  /**
   * Element to observe (default: window)
   */
  container?: Element;
  
  /**
   * Target element for relative calculations
   */
  target?: Element;
  
  /**
   * Offset configuration
   */
  offset?: [string, string];
  
  /**
   * Axis to track
   */
  axis?: "x" | "y";
}

interface ScrollInfo {
  /**
   * Current scroll position
   */
  x: number;
  y: number;
  
  /**
   * Scroll progress (0-1)
   */
  xProgress: number;
  yProgress: number;
  
  /**
   * Scroll velocity
   */
  velocity: number;
}

interface DetailedScrollInfo extends ScrollInfo {
  /**
   * Target element bounds
   */
  targetBounds: DOMRect;
  
  /**
   * Container bounds
   */
  containerBounds: DOMRect;
  
  /**
   * Intersection ratio
   */
  intersectionRatio: number;
}

Usage Examples:

import { scroll, scrollInfo } from "framer-motion/dom";
import { useEffect } from "react";

// Basic DOM scroll tracking
function DOMScrollExample() {
  useEffect(() => {
    const cleanup = scroll(
      ({ y, yProgress, velocity }) => {
        console.log("Scroll:", { y, yProgress, velocity });
        
        // Update header based on scroll
        const header = document.querySelector('.header');
        if (header) {
          header.style.transform = `translateY(${Math.min(0, -y / 2)}px)`;
          header.style.opacity = String(1 - yProgress * 0.5);
        }
      },
      { axis: "y" }
    );
    
    return cleanup;
  }, []);
  
  return (
    <div>
      <div className="header fixed top-0 w-full h-16 bg-blue-500">Header</div>
      <div className="h-[200vh] pt-16">Long scrollable content</div>
    </div>
  );
}

// Element-specific DOM scroll tracking
function ElementDOMScrollExample() {
  useEffect(() => {
    const target = document.querySelector('.target');
    
    if (target) {
      const cleanup = scrollInfo(
        ({ yProgress, intersectionRatio, velocity }) => {
          console.log("Element scroll:", { yProgress, intersectionRatio, velocity });
          
          // Animate element based on scroll
          target.style.transform = `scale(${0.8 + intersectionRatio * 0.2})`;
          target.style.opacity = String(intersectionRatio);
        },
        { 
          target: target as Element,
          offset: ["start end", "end start"]
        }
      );
      
      return cleanup;
    }
  }, []);
  
  return (
    <div>
      <div className="h-screen bg-gray-100">Scroll down</div>
      <div className="target h-96 bg-purple-500">Target element</div>
      <div className="h-screen bg-gray-100">More content</div>
    </div>
  );
}

Viewport Intersection Utility

Direct DOM intersection observer utility.

/**
 * Observe element intersection with viewport (DOM utility)
 * @param element - Element to observe or CSS selector
 * @param onStart - Callback when element enters view
 * @param options - Intersection options
 * @returns Cleanup function
 */
function inView(
  element: string | Element,
  onStart: (entry: IntersectionObserverEntry) => void | (() => void),
  options?: InViewOptions
): () => void;

interface InViewOptions {
  /**
   * Root element for intersection
   */
  root?: Element;
  
  /**
   * Root margin
   */
  margin?: string;
  
  /**
   * Intersection threshold (0-1 or array of thresholds)
   */
  amount?: number | number[] | "some" | "all";
}

Usage Example:

import { inView } from "framer-motion/dom";
import { useEffect } from "react";

function DOMInViewExample() {
  useEffect(() => {
    // Observe multiple elements
    const elements = document.querySelectorAll('.animate-on-scroll');
    const cleanups: (() => void)[] = [];
    
    elements.forEach((element, index) => {
      const cleanup = inView(
        element,
        (entry) => {
          if (entry.isIntersecting) {
            // Element entered view
            element.classList.add('animate-in');
          } else {
            // Element left view
            element.classList.remove('animate-in');
          }
        },
        {
          margin: "-50px",
          amount: 0.5
        }
      );
      
      cleanups.push(cleanup);
    });
    
    return () => {
      cleanups.forEach(cleanup => cleanup());
    };
  }, []);
  
  return (
    <div>
      {Array.from({ length: 10 }, (_, i) => (
        <div
          key={i}
          className="animate-on-scroll h-96 bg-blue-500 m-4 opacity-0 transform translate-y-10 transition-all duration-500"
          style={{
            // CSS for animate-in class
            '--animate-in': 'opacity: 1; transform: translateY(0);'
          }}
        >
          Element {i + 1}
        </div>
      ))}
    </div>
  );
}

Page Visibility Hook

Detect when the page becomes visible or hidden.

/**
 * Detect page visibility changes
 * @returns Boolean indicating if page is visible
 */
function usePageInView(): boolean;

Usage Example:

import { usePageInView } from "framer-motion";
import { useEffect } from "react";

function PageVisibilityExample() {
  const isPageVisible = usePageInView();
  
  useEffect(() => {
    if (isPageVisible) {
      console.log("Page became visible");
      // Resume animations or video playback
    } else {
      console.log("Page became hidden");
      // Pause animations or video playback
    }
  }, [isPageVisible]);
  
  return (
    <div>
      Page is {isPageVisible ? "visible" : "hidden"}
    </div>
  );
}

Scroll-Driven Animation Patterns

Parallax Effects:

const { scrollY } = useScroll();
const y1 = useTransform(scrollY, [0, 1000], [0, -200]);
const y2 = useTransform(scrollY, [0, 1000], [0, -400]);

<motion.div style={{ y: y1 }}>Slow parallax</motion.div>
<motion.div style={{ y: y2 }}>Fast parallax</motion.div>

Progress Indicators:

const { scrollYProgress } = useScroll();
const scaleX = useTransform(scrollYProgress, [0, 1], [0, 1]);

<motion.div 
  style={{ scaleX }} 
  className="fixed top-0 left-0 w-full h-1 bg-blue-500 origin-left"
/>

Scroll-Triggered Animations:

const { scrollY } = useScroll();
const isScrolled = useTransform(scrollY, [0, 100], [false, true]);

<motion.header
  animate={isScrolled.get() ? "scrolled" : "top"}
  variants={{
    top: { backgroundColor: "transparent", y: 0 },
    scrolled: { backgroundColor: "white", y: -10 }
  }}
/>

docs

animation-controls.md

configuration.md

dom-utilities.md

gestures.md

index.md

layout-presence.md

motion-components.md

motion-values.md

scroll-view.md

tile.json