Retour au catalogue

Timeline Draw Path

Timeline verticale ou un path SVG sinueux se dessine progressivement au scroll (motion.path + pathLength lie a useScroll). Les jalons-cercles s'illuminent quand la ligne les atteint, les blocs texte font un reveal directionnel (x offset). Ambiance premium minimaliste, zero couleur tape-a-l-oeil.

timelinecomplex Both Responsive a11y
elegantminimaleditorialsaasagencyuniversalstackedcentered
Theme

Cómo crear una timeline React cuyo trazado SVG se dibuja al hacer scroll

Una timeline SVG React que se dibuja al hacer scroll funciona vinculando la motion value pathLength de Framer Motion con la progresión de useScroll mediante un suavizado useSpring. El trazado se dibuja a medida que la sección entra en el viewport, y cada hito crece justo cuando el trazo animado alcanza su posición en el camino.

  • Stack: React + Framer Motion 11 + tokens CSS inline, ~105 líneas, cero dependencias adicionales.
  • API clave: useScroll, useSpring, useTransform, motion.path pathLength, useInView.
  • El trazado SVG se calcula en el momento del render mediante un generador de curvas de Bézier cúbicas (buildPath) que alterna los desplazamientos izquierda/derecha para producir la forma ondulada.
  • Accesible: el SVG es aria-hidden, el contenido textual vive en elementos DOM estándar.
  • Los bloques de contenido alternan izquierda/derecha con un reveal direccional en el eje x ; funciona en móvil porque el scroll está disponible en todos los dispositivos.

Timeline Draw Path es una sección React de timeline vertical donde un trazo SVG ondulado se dibuja de arriba abajo mientras el usuario se desplaza por la página. Los círculos de los hitos aparecen cuando la línea animada los alcanza, y cada bloque de contenido se desliza desde su lado alternante. El resultado da la sensación de que la historia se escribe en tiempo real, lo que lo convierte en una excelente opción para roadmaps de producto, historiales de empresa o secuencias de lanzamiento de funcionalidades.

Anatomía

Un bloque de encabezado centrado (eyebrow, h2, subtítulo) se sitúa encima del cuerpo de la timeline. Este cuerpo es un contenedor relativo que aloja dos cosas a la vez: un rail SVG posicionado en absoluto y centrado horizontalmente, y una columna de filas de contenido con padding. Cada fila es un contenedor flex cuya altura coincide con la constante fija STEP_H (180px), con la tarjeta de texto empujada a la izquierda o a la derecha según el índice del paso. El SVG contiene un trazado guía gris estático, el motion.path coloreado con el acento animado por encima, y el grupo MilestoneDot para cada paso.

Cómo funciona

useScroll observa el elemento de la sección desde que su parte superior alcanza el 75% del viewport hasta que su parte inferior está al 30%. La progresión bruta se pasa a useSpring (stiffness 80, damping 22) para eliminar las sacudidas y añadir inercia. useTransform mapea el valor suavizado del spring [0, 1] directamente sobre la motion value pathLength de Framer Motion. En el lado del SVG, un motion.path recibe este valor en su style pathLength para que Framer Motion se encargue del cálculo de stroke-dashoffset. Cada MilestoneDot mapea su propia posición (idx / total) en una ventana [t-0.04, t+0.06] sobre el mismo spring para escalar de 0 a 1, de modo que los hitos aparecen justo cuando llega el trazo. Los bloques de texto usan useInView con once:true para un reveal direccional de una sola pasada.

Cómo crearlo en React

  1. Generar el trazado SVG ondulado

    Escribe una función buildPath que parta del punto medio del SVG y añada una curva de Bézier cúbica por paso. Alterna el desplazamiento horizontal del punto de control (+8 y luego -8) entre los pasos pares e impares para crear la onda. Termina con una línea vertical hasta la parte inferior del último paso.

    const PX = 40; // SVG midpoint x
    const STEP_H = 180;
    
    function buildPath(n: number): string {
      if (n === 0) return "";
      let d = `M ${PX} 0`;
      for (let i = 0; i < n; i++) {
        const cy = i * STEP_H + STEP_H * 0.1;
        const py = i > 0 ? (i - 1) * STEP_H + STEP_H * 0.1 : 0;
        const mid = (py + cy) / 2;
        const off = i % 2 === 0 ? 8 : -8;
        d += ` C ${PX + off} ${mid}, ${PX - off} ${mid}, ${PX} ${cy}`;
      }
      return d + ` L ${PX} ${n * STEP_H}`;
    }
  2. Vincular la progresión del scroll a pathLength

    Asocia una ref al contenedor de la timeline y pásala a useScroll con offset ['start 0.75', 'end 0.3']. Suaviza la progresión bruta con useSpring, luego mapéala a [0, 1] con useTransform. Pasa el resultado como prop de style pathLength en un motion.path.

    const sectionRef = useRef<HTMLDivElement>(null);
    const { scrollYProgress } = useScroll({
      target: sectionRef,
      offset: ["start 0.75", "end 0.3"],
    });
    const smooth = useSpring(scrollYProgress, { stiffness: 80, damping: 22 });
    const pathLength = useTransform(smooth, [0, 1], [0, 1]);
    
    // In JSX:
    <motion.path d={svgPath} stroke="var(--color-accent)" style={{ pathLength }} />
  3. Animar los hitos desde el mismo spring

    Para cada hito en el índice idx de un total de pasos, calcula la posición normalizada t = idx / total. Usa useTransform sobre el spring suavizado compartido con una pequeña ventana alrededor de t para variar scale y opacity de 0 a 1. El hito aparece justo cuando llega el trazo.

    function MilestoneDot({ progress, idx, total }) {
      const t = idx / total;
      const dotScale = useTransform(progress, [t - 0.04, t + 0.06], [0, 1]);
      const dotOpacity = useTransform(progress, [t - 0.04, t + 0.06], [0, 1]);
      // render motion.circle with style={{ scale: dotScale, opacity: dotOpacity }}
    }
  4. Revelar los bloques de contenido en dirección alternante

    Envuelve cada tarjeta de texto en un motion.div con initial={{ opacity: 0, x: isLeft ? 32 : -32 }}. Usa useInView con once:true sobre la ref de la fila y cambia a animate={{ opacity: 1, x: 0 }} cuando entra en el viewport. Esto refleja la alternancia izquierda/derecha de la onda del trazado.

    const ref = useRef<HTMLDivElement>(null);
    const isInView = useInView(ref, { once: true, margin: "-12% 0px" });
    const isLeft = index % 2 === 0;
    
    <motion.div
      initial={{ opacity: 0, x: isLeft ? 32 : -32, y: 6 }}
      animate={isInView ? { opacity: 1, x: 0, y: 0 } : {}}
      transition={{ duration: 0.65, ease: [0.16, 1, 0.3, 1] }}
    />

Cuándo usarlo

Usa este componente cuando tengas una narración lineal con 4 a 6 hitos claramente fechados: historial de empresa, roadmap de producto, flujos de onboarding o changelogs de versiones. La metáfora del trazado dibujado señala la progresión de forma intuitiva. Evítalo para cuadrículas de funcionalidades sin orden, datos profundamente anidados, o páginas donde cada usuario aterriza a mitad del scroll (la animación requiere una entrada desde arriba para leerse correctamente). Evítalo también cuando el contenido tiene menos de 3 pasos ; la onda no tiene espacio para respirar.

Usado por

  • Stripe, Utiliza revelaciones progresivas de trazado al hacer scroll en sus páginas de storytelling de producto para guiar a los lectores por los hitos de infraestructura.
  • Linear, Emplea animaciones SVG vinculadas al scroll en sus secciones de changelog y roadmap para comunicar dinamismo.
  • Framer, Muestra animaciones de dibujo de trazado en su propio sitio de marketing como demostración directa de sus capacidades de animación.
  • Vercel, Utiliza conectores pilotados por el scroll entre los bloques de funcionalidades en sus páginas de producto de infraestructura para narrar una historia de despliegue.

FAQ

¿Por qué el trazado se dibuja como una onda de Bézier cúbica en lugar de una línea recta?

La curva alternante da a la línea un movimiento orgánico y separa visualmente los bloques de contenido de la izquierda y la derecha sin necesidad de una cuadrícula de dos columnas. Una línea vertical recta también funciona, pero se lee como un simple separador en lugar de como un hilo narrativo.

¿Puedo cambiar el número de pasos sin tocar el SVG?

Sí. La función buildPath recibe el número de pasos como argumento y la altura del SVG se deriva de steps.length * STEP_H. Pasa más o menos pasos en la prop steps y el rail se recalcula automáticamente.

La animación empieza a la mitad cuando los usuarios llegan a un enlace de ancla en lo profundo de la página. ¿Cómo lo soluciono?

useScroll calcula la progresión desde la posición de scroll actual en el montaje. Si la página se carga ya desplazada, el trazado aparecerá parcialmente dibujado. Una solución consiste en restablecer scrollYProgress a 0 en un useEffect al montar, o condicionar la sección a un punto de entrada que devuelva al inicio de la página.

¿Cómo adapto la maquetación para una vista móvil vertical donde la alternancia izquierda/derecha se colapsa?

En pantallas estrechas, sobrescribe la justificación flex para usar siempre flex-start y elimina el desplazamiento x horizontal para que todas las tarjetas se apilen de un solo lado. El rail SVG permanece centrado ; solo cambia la alineación de la columna de contenido. Basta con una propiedad CSS personalizada o una comprobación del ancho de ventana en el componente.

"use client";

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

export interface TimelineStep {
  id: string; date: string; title: string; description: string; tag?: string;
}

interface Props {
  eyebrow?: string; title?: string; subtitle?: string; steps: TimelineStep[];
}

const EASE = [0.16, 1, 0.3, 1] as const;
const STEP_H = 180;
const PX = 40; // SVG midpoint x

function buildPath(n: number): string {
  if (n === 0) return "";
  let d = `M ${PX} 0`;
  for (let i = 0; i < n; i++) {
    const cy = i * STEP_H + STEP_H * 0.1;

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Reseñas

Timeline SVG React animada al hacer scroll, Código +