---
title: "How to Create a Parallax Scroll Effect in Framer"
description: "Build a real parallax scroll effect in Framer — the native Scroll Effect option, a useScroll/useTransform code override, and multi-layer depth tuning."
canonical_url: "https://framerhub.io/blog/parallax-scroll-framer"
last_updated: "2027-02-19T00:00:00.000Z"
---

Parallax scroll — background and foreground elements moving at different speeds as a visitor scrolls, creating an illusion of depth — is one of the oldest tricks in web design and still one of the most frequently implemented poorly. Done right, it's a subtle, convincing sense of layered space. Done wrong, it's a janky, stuttering mess that actively fights the browser's scroll performance. Here's the correct way to build it in Framer, at both the native and code level.

## Native Framer: Scroll Effects

Framer includes a **Scroll Effect** you can add to any layer through the interactions panel, and one of its available effects is parallax — set an offset percentage and the layer moves at a proportionally different speed than the natural scroll rate as the page scrolls past it. This is genuinely well-implemented natively (it's GPU-composited, not layout-driven) and handles the most common case — a single background layer moving slower than foreground content — with zero code.

Use the native Scroll Effect for straightforward single-layer parallax. Reach for a code override when you need multiple independently-tuned layers within one section, parallax tied to a specific scroll range rather than the whole page, or parallax combined with other scroll-driven values (rotation, scale, opacity) changing simultaneously.

## Building It With Code: useScroll + useTransform

Framer Motion's `useScroll` and `useTransform` hooks are the standard toolkit for scroll-driven animation, and they're what you want when the native Scroll Effect's single-layer, whole-page model doesn't fit your section.

```jsx
import { useRef } from "react"
import { motion, useScroll, useTransform } from "motion/react"

export function ParallaxSection(Component): ComponentType {
  return (props) => {
    const ref = useRef(null)
    const { scrollYProgress } = useScroll({
      target: ref,
      offset: ["start end", "end start"],
    })

    const backgroundY = useTransform(scrollYProgress, [0, 1], ["-15%", "15%"])
    const midgroundY = useTransform(scrollYProgress, [0, 1], ["-30%", "30%"])
    const foregroundY = useTransform(scrollYProgress, [0, 1], ["-5%", "5%"])

    return (
      <div ref={ref} style={{ position: "relative", overflow: "hidden" }}>
        <motion.div style={{ position: "absolute", inset: 0, y: backgroundY }}>
          {/* background layer */}
        </motion.div>
        <motion.div style={{ position: "absolute", inset: 0, y: midgroundY }}>
          {/* midground layer */}
        </motion.div>
        <motion.div style={{ position: "relative", y: foregroundY }}>
          <Component {...props} />
        </motion.div>
      </div>
    )
  }
}
```

The structure here matters as much as the values:

- **useScroll with a target ref and an offset range** scopes the scroll tracking to when *this specific section* enters and exits the viewport, rather than tracking the whole page's scroll position — this is what lets each section's parallax start and end cleanly at its own boundaries, rather than being driven by an arbitrary global scroll value.
- **Each layer gets its own useTransform mapping scrollYProgress (0 to 1) to a different range of movement** — the background moves the most (`-15%` to `15%`), the midground moves even more (`-30%` to `30%`), and the foreground (your actual content) moves only slightly (`-5%` to `5%`). This is the actual parallax illusion: layers you perceive as "farther away" move more relative to the scroll, and layers perceived as "closer" move less — the opposite of what intuition might suggest at first, but consistent with how real-world depth parallax behaves (distant objects appear to move faster across your field of view relative to scroll/pan speed than nearby ones, when the reference frame is the foreground you're focused on).
- **Both layers are driven by y (a transform: translateY), never top or margin.** This is the single most important performance decision in the whole implementation — `transform` is GPU-composited and doesn't trigger layout recalculation, while `top`/`margin` do, which is the root cause of nearly every janky parallax implementation.

## Tuning Layer Depth Ratios

<table>
<thead>
  <tr>
    <th>
      Layer
    </th>
    
    <th>
      Typical movement range
    </th>
    
    <th>
      Perceived depth
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Deep background
    </td>
    
    <td>
      ±20-30%
    </td>
    
    <td>
      Farthest away, moves the most
    </td>
  </tr>
  
  <tr>
    <td>
      Midground
    </td>
    
    <td>
      ±10-20%
    </td>
    
    <td>
      Middle distance
    </td>
  </tr>
  
  <tr>
    <td>
      Foreground content
    </td>
    
    <td>
      ±0-5%
    </td>
    
    <td>
      Closest, moves least (often nearly static)
    </td>
  </tr>
</tbody>
</table>

The ratio between layers — not the absolute values — is what creates the depth illusion. Two layers moving at nearly the same rate don't read as separated in space; a background moving noticeably more than the foreground content reads as convincingly layered. As a starting point, aim for roughly a 3-4x difference in movement range between your farthest-back and closest layers.

## Common Mistakes That Ruin the Illusion

- **Driving parallax with top/margin instead of transform.** As covered above, this is the root cause of most janky parallax — layout-triggering properties force the browser to recalculate the page's layout on every scroll frame, which is far more expensive than a GPU-composited transform.
- **Too many layers moving at similar speeds.** More layers doesn't automatically mean more depth — if two layers move at nearly identical rates, they read as one layer, not two. Fewer layers with clearly differentiated movement ranges beats many layers with subtle, hard-to-perceive differences.
- **Parallax that extends beyond its section's actual boundaries.** If a background layer keeps moving well past where its section visually ends, it can visibly bleed into or misalign with the next section. Scoping `useScroll` to a specific `target` ref (as in the snippet) keeps each section's parallax self-contained.
- **Ignoring how it feels on a fast scroll, not just a slow one.** Many parallax implementations are tuned and tested with slow, deliberate scrolling during development, then feel jarring or disorienting when a real visitor scrolls quickly past the section — test at realistic scroll speeds, not just careful development-time scrolling.

## Mobile: Reduce, Don't Necessarily Remove

Mobile scrolling tends to be faster, more momentum-driven, and less deliberate than desktop trackpad or mouse-wheel scrolling, which changes how parallax reads — the same movement range that feels subtle and elegant at desktop scroll speeds can feel jarring or nauseating at mobile scroll speeds. Rather than an all-or-nothing decision, many production implementations simply reduce the movement range (roughly by half) on narrower viewports rather than disabling the effect outright — check `window.innerWidth` or use a media query-driven value and scale your `useTransform` output ranges down accordingly below a chosen breakpoint.

## Performance Notes

Beyond the `transform`-vs-`layout-property` distinction covered above, a few additional performance considerations matter specifically for parallax:

- **Limit simultaneous parallax sections on one page.** Each active `useScroll` hook adds a scroll listener and recalculation on every frame — a handful of parallax sections spread across a long page is fine; a dozen simultaneously visible ones compounds real cost.
- **Avoid parallaxing very large images at full resolution.** A parallaxing background image needs to be somewhat larger than its container to avoid revealing empty edges as it shifts — don't compensate for that by loading an unnecessarily massive source image; size it to the actual maximum extent the parallax range requires and no further.
- **Test on a throttled CPU profile**, not just your development machine — parallax performance issues are often invisible on a high-end laptop and very visible on the mid-range hardware a meaningful share of visitors actually use.

## Where Parallax Works Best

- **Hero sections** with a background image or illustration moving slower than the foreground headline, adding depth to what's usually the first thing a visitor sees.
- **Storytelling or editorial scroll sequences**, where layered movement reinforces a sense of moving "through" a scene rather than just past static content.
- **Product showcase sections**, where a product image or mockup shifts subtly relative to surrounding text and decorative elements.

It's a poor fit for content-dense, information-heavy sections where the goal is fast, efficient reading rather than atmosphere — pricing tables, feature comparison grids, and dense text blocks generally shouldn't have parallax applied, since the shifting layers add visual noise to a task that benefits from stillness.

## Accessibility

As with other continuous or scroll-linked motion, check `prefers-reduced-motion` and either disable the parallax offset entirely or significantly reduce its range for visitors who've set that preference — a static, unshifted layout loses nothing content-wise, since parallax is a purely decorative depth effect layered on top of otherwise complete content.

If you're building a scroll-heavy page with several effects working together — parallax alongside reveal animations or sticky sections — our [scroll animations roundup](/blog/scroll-animations-framer) covers a broader set of techniques worth combining deliberately rather than stacking without a plan, and FramerHub's [component library](/components), available through the [Components plugin](/plugins/framer-components), includes several pre-built scroll-effect components if hand-tuning `useScroll` ranges for every section isn't the best use of your time on a given project.

## FAQ

**Does Framer have native parallax scrolling?**
Yes — Framer's Scroll Effect (available on any layer via the interactions panel) includes a parallax offset setting that moves a layer at a different speed than the page scroll, with no code required for a basic single-layer effect.

**How many parallax layers should a section have?**
2 to 4 layers is the practical range for most sections — a background layer, a midground element, and a foreground element is enough to create a convincing sense of depth. Beyond 4-5 layers, the added depth becomes hard to perceive and the performance cost keeps climbing.

**Why does my parallax effect feel janky instead of smooth?**
This is almost always because the effect is driven by a scroll event listener updating layout-affecting CSS properties (like top or margin) rather than a transform. Use useScroll and useTransform to drive a translateY/x value instead, which the browser can composite on the GPU without triggering layout recalculation.

**Should parallax be disabled on mobile?**
Often, yes, or at least reduced in intensity. Mobile scrolling is typically faster and more abrupt than desktop trackpad or wheel scrolling, and parallax layers can feel more disorienting than impressive at that speed — many production sites disable or significantly dampen parallax below a certain viewport width.

**Does parallax scrolling hurt SEO?**
Not directly — it's a visual effect that doesn't change your actual content or markup. The indirect risk is performance: a janky, poorly optimized parallax implementation can hurt Core Web Vitals scores, which are a ranking factor, so the SEO risk is really a performance risk in disguise.
