or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

animation-controls.mdconfiguration.mddom-utilities.mdgestures.mdindex.mdlayout-presence.mdmotion-components.mdmotion-values.mdscroll-view.md
tile.json

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 }
  }}
/>