Retour au catalogue

About Parallax Image

Layout 2 colonnes avec image parallax au scroll, titre serif italic, stats clés et éléments décoratifs flottants animés en boucle.

aboutmedium Both Responsive a11y
eleganteditorialminimalagencysaasportfoliouniversalsplit
Theme

How to build a scroll parallax about section in React

A scroll-parallax about section in React uses Framer Motion's useScroll and useTransform to shift the image vertically as the user scrolls past the section, creating a depth illusion. The image container clips overflow while an inner motion.div translates on a -6%/+6% range mapped to scroll progress.

  • Stack: React 18 + Framer Motion 11, ~334 lines, zero icon dependencies.
  • Core API: useScroll (target + offset), useTransform, whileInView stagger.
  • Accessible: decorative dots and lines carry aria-hidden="true"; image slot uses an aria-label.
  • Responsive: auto-fit grid collapses to a single column on narrow viewports; parallax is viewport-agnostic.
  • Stats row is optional, pass an empty array to omit it entirely.

About Parallax Image is a two-column React section designed for mission and company pages. The left column holds an image frame whose inner fill shifts slightly on scroll, giving the layout a sense of depth without any heavy 3-D library. The right column stacks a label, a mixed-weight title with an italic serif accent word, a body paragraph, and an optional row of key figures.

Anatomy

The outer section is a CSS grid with `repeat(auto-fit, minmax(300px, 1fr))`, it needs no breakpoint media queries because the columns reflow naturally. The image side (order 1) is a positioned container with seven floating decorative elements: five dots and two horizontal lines, all animated independently in infinite loops. Inside sits a rounded div with overflow:hidden that acts as the clip frame; the motion.div inside it receives the parallax translateY. The text side (order 2) contains four staggered motion elements: label, h2, paragraph, and the optional stats flex row.

How it works

Two refs drive the effect. `sectionRef` is passed to `useScroll` with `offset: ['start end', 'end start']`, this maps the scroll range from when the section's top enters the viewport to when its bottom leaves. `useTransform` converts that 0-to-1 progress into a `-6%` to `+6%` Y value on the inner image div. Because the image container clips overflow and the inner div is sized to `inset: -8%`, there is always enough image to fill the frame at both extremes. The floating dots and lines run independent Framer Motion `animate` loops with staggered delays; the text elements use `whileInView` with `once: true` so they animate in once and stay.

How to build it in React

  1. Set up the scroll-tracking ref

    Attach a ref to the outer section element and pass it to useScroll. The offset array tells Framer Motion to start tracking when the section's top enters the bottom of the viewport and stop when the section's bottom exits the top.

    const sectionRef = useRef<HTMLElement>(null);
    const { scrollYProgress } = useScroll({
      target: sectionRef,
      offset: ["start end", "end start"],
    });
  2. Map scroll progress to a Y translation

    useTransform converts the 0-1 progress value into a percentage string the browser can apply directly as a CSS transform. Keep the range small, ±6% is enough for a subtle premium feel without looking like a bug.

    const imageY = useTransform(scrollYProgress, [0, 1], ["-6%", "6%"]);
  3. Clip the frame and apply the motion value

    Wrap the image in a div with overflow:hidden and border-radius. Inside, render a motion.div with the imageY value and set inset to -8% so the oversized fill never leaves a gap at either extreme of the parallax range.

    <div style={{ overflow: "hidden", borderRadius: "var(--radius-xl)" }}>
      <motion.div style={{ y: imageY, position: "absolute", inset: "-8%" }}>
        {/* image or gradient fill */}
      </motion.div>
    </div>
  4. Stagger the text reveal with whileInView

    Each text element gets an initial x:20/opacity:0 state and animates to x:0/opacity:1 when it enters the viewport. Increment the delay by ~80ms per element so label, heading, body, and stats appear in sequence rather than all at once.

    <motion.h2
      initial={{ opacity: 0, x: 24 }}
      whileInView={{ opacity: 1, x: 0 }}
      viewport={{ once: true }}
      transition={{ duration: 0.55, delay: 0.12, ease: [0.16, 1, 0.3, 1] }}
    >

When to use it

This section fits best on agency, portfolio, and SaaS marketing pages where the brand story or mission needs visual weight. The parallax image gives the layout a premium, editorial feel without any heavy 3-D overhead. Avoid it on information-dense documentation pages or anywhere bounce rate is critical, animation adds load and distraction. Replace the placeholder gradient with a real team photo or product screenshot to get the full impact.

Used by

  • Stripe, Uses split-column layouts with subtle scroll-driven image depth on product and company pages.
  • Notion, About and team pages combine editorial typography with floating decorative elements and scroll-layered visuals.
  • Loom, Company and mission sections use two-column splits with stats rows and parallax-influenced image depth.
  • Pitch, Marketing and about pages feature serif italic accent words in headings alongside scroll-animated image panels.

FAQ

How do I swap the placeholder gradient for a real image?

Replace the gradient background on the inner motion.div with a background-image or an <img> tag. Keep the inset: -8% and overflow:hidden on the outer container so the parallax still has room to move without showing gaps.

Does the parallax effect work on mobile?

Yes, useScroll is touch-scroll aware, so the parallax works on phones and tablets. The grid collapses to one column automatically, stacking the image above the text.

Can I remove the floating decorative dots?

Remove the FloatingDot and FloatingLine elements from the image container. They are purely decorative and carry aria-hidden, so deleting them has no functional or accessibility impact.

How do I add the stats row?

Pass a non-empty stats array as a prop: each item is an object with a value string (e.g. '200+') and a label string. The row renders automatically and disappears when the array is empty.

"use client";

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

interface Stat {
  value: string;
  label: string;
}

interface AboutParallaxImageProps {
  label?: string;
  title?: string;
  titleAccent?: string;
  body?: string;
  stats?: Stat[];
  imageAlt?: string;
}

const EASE = [0.16, 1, 0.3, 1] as const;

function FloatingDot({

Code complet réservé à Pro

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

Passer en Pro, 9,99€/mois

Reviews

React About Section with Scroll Parallax Image, Tutorial