Parallax website examples, and the speeds behind them

Parallax website examples grouped by what drives the depth: scroll, pointer or a clock. The real speed ratios, the CSS, and what breaks on iPhone.

Three thin sheets of glass suspended one behind another on pure black, offset by different distances so their edges stagger into depth, the whole stack lit by a single violet glow.
The near layer travels furthest. Everything else follows from that.

A parallax website moves layers at different rates for the same input, so the page reads as having depth: the near layer travels further than the far one. The input can be the scroll, the pointer or a clock, and the good examples keep the differences small, with scroll speeds between about 1.1 and 1.5, pointer travel measured in tens of pixels, and ambient drift of around 1vh.

What separates a parallax section that feels like depth from one that feels like a slideshow is rarely the technique. It is the numbers: how much each layer moves, where each layer sits when the section is centered, whether the layers share one rhythm, and what the page does on a phone and for a reader who has asked for less motion.

The examples below come from the Thanor library, grouped by input, each with its values. None of them uses background-attachment: fixed, for a reason covered in the CSS section. The scroll mechanics they share with other effects are in scroll animation website examples, and the distance budget for any pinned section is in the scroll animation prompt.

What makes parallax read as depth#

Four rules carry nearly all of it.

  1. The near layer moves more. In a real scene, apparent motion falls with distance, so the layer closest to the reader gets the largest speed or amplitude and the background the smallest, often none.
  2. Each layer sits in its layout place when the section is centered. If a layer's offset is -(speed - 1) × (scrollY - c), where c is the scroll position at which the layer's middle crosses the window's middle, the layer is exactly where you placed it at the moment it matters, a little lower before and a little higher after. Layers never drift far from the design.
  3. Ambient layers share one period. Layers on different periods beat against each other, and a visible beat turns a drift into a wobble.
  4. Motion is one depth cue among several. Occlusion (a subject standing in front of the type), scale, blur and opacity do as much as speed, and they work for the reader who sees no motion at all.
InputNear layerMiddle layerFar layerExample
Scroll, as a speed1.33 to 1.51.1 to 1.251, moving with the pagePalenque
Pointer, as travel48 units27 units15 units, plus a camera shiftKickflip
Clock, as amplitude1.1vh with the cycle0.5vh against it0.25vh against itKairosveld

The values in that table come from the designs below; treat them as a starting range rather than a rule. Rule 2 is the one a quick version gets wrong. A layer written as translateY(scrollY * 0.5) is anchored to the top of the page, so by the third section it has drifted hundreds of pixels from where the layout put it.

Scroll parallax examples#

Palenque, a single-village mezcal, gives the layers around its traveling bottle their own speeds. The hero's giant distressed words in Bebas Neue rise at 1.22 times the scroll, read through the glass of the bottle, and a second giant headline rises at 1.5. The lime, orange and chili splashes that carry the tasting notes move at 1.33, 1.4 and 1.14, a film card at 1.22, a clay cup at 1.1, an orange fleck at 1.25 and a plate of orange slices at 1.13. Everything else, the copy, the seal, the line-up and the footer, moves with the page at 1, and that ratio of moving to still is what makes the movers read as depth. Each layer uses the centering formula above and is left alone once it is further than a window plus its own height from its center position.

The detail that makes it hold together is how it measures. Palenque reads every layer's position as a layout value summed through offsetParent, so a transform written by the loop never feeds back into the next measurement, and the loop writes nothing but transforms.

Ardente uses a quieter version on its workshop section: three photographs drift by -40, 40 and -30px across the section's run, passing their layout places at the middle, while a marquee of type slides left by 42% of its own width.

js
const pageTop = (el) => { let y = 0; for (let n = el; n; n = n.offsetParent) y += n.offsetTop; return y; };
const layers = [...document.querySelectorAll("[data-speed]")]
  .map((el) => ({ el, k: Number(el.dataset.speed) - 1, c: 0, h: 0 }));

function measure() {        // layout positions only: offsetTop ignores transforms
  for (const l of layers) {
    l.h = l.el.offsetHeight;
    l.c = pageTop(l.el) + l.h / 2 - innerHeight / 2;
  }
}
function frame() {
  const s = scrollY;
  for (const l of layers) {
    if (Math.abs(s - l.c) > innerHeight + l.h) continue;  // far away: leave it
    l.el.style.transform = `translate3d(0, ${(-l.k * (s - l.c)).toFixed(1)}px, 0)`;
  }
  requestAnimationFrame(frame);
}
addEventListener("resize", measure);
addEventListener("load", measure);
if (!matchMedia("(prefers-reduced-motion: reduce)").matches) { measure(); requestAnimationFrame(frame); }

Pointer parallax examples#

Pointer parallax answers the cursor instead of the scroll, which makes it the natural fit for a hero that should feel alive before the reader does anything else.

Kickflip, a bubblegum-pink 3D skate shop, stacks two kinds of pointer parallax. Over the WebGL room, the pointer's position from -1 to 1 across the window sets targets, and each group of the hero poster moves against it by its own travel: the chips 48 units, the notes 36, the name 27, the top line 18, the link 15. Behind them the 3D camera itself shifts up to 0.36 meters sideways and 0.18 meters vertically while staying aimed at a point 14 meters ahead, so the near and far parts of the room slide apart the way a real room does; the camera values behind scenes like this are in the 3D website guide. On touch screens a slow sweep, 0.45 sin(t / 1500ms) across and 0.3 sin(t / 2100ms) down, stands in for the pointer.

Grumps, a designer vinyl toy shop, keeps it to a few pixels. The hero toy floats 6px up and back on a 4000ms cosine, moves toward the pointer by 6px across and 4px up or down, and turns toward it in a 1400px perspective, up to 3 degrees about the vertical axis and 2 about the horizontal, while the side figures drift the other way. Move the pointer out of the window and everything levels.

Grumps chases its targets rather than jumping to them: every value closes 1 - 0.88^(dt × 60) of the gap each frame, about 12% at 60 frames a second and 95% of the way in roughly 390ms, so the motion looks the same at 60Hz and 120Hz. Without a fine pointer, every target is zero and the figures settle. Caravan applies the same idea as a bank: up to 6 degrees of tilt in a perspective(1200px) and a drift of about 13px at 1440 by 900. More pointer-driven ideas, from dragging to turning objects, are in the interactive website examples.

Parallax on a clock#

Not every parallax needs input. Kairosveld, a portfolio hero, drifts its layers on a single slow cycle: 19s, on a symmetric cubic-bezier(0.45, 0, 0.55, 1), alternating forever, starting at 1600ms. The woman in the foreground moves 1.1vh with the cycle, her headline 0.5vh against it, and a faint ghost name behind it 0.25vh against it, while the gradient and the blurred blobs behind take no part in the cycle. All three share the one period on purpose, so they never beat. On top of that the whole stage breathes, scaling from 1 to 1.035 and back over 24s, kept under 1.04 because beyond that the headline's outer letters visibly leave the frame.

Her head turn is the other half of the depth. It is a video with a real alpha channel, because, as the design's own prompt puts it, sliding a photograph sideways moves a picture, while a head that turns shows real parallax on her own face.

css
@media (prefers-reduced-motion: no-preference) {
  .figure, .headline, .ghost {
    animation: sway 19s cubic-bezier(0.45, 0, 0.55, 1) 1.6s infinite alternate;
  }
  .figure   { --a: 1.1vh; }
  .headline { --a: -0.5vh; }
  .ghost    { --a: -0.25vh; }
}
@keyframes sway {
  from { translate: calc(var(--a) * -1) 0; }
  to   { translate: var(--a) 0; }
}

The individual translate property composes with any transform already on the element, so a figure centered with transform: translateX(-50%) can sway without losing its centering, the exact failure Kairosveld's prompt warns about when the two share one property.

Three ways to build scroll parallax in CSS#

Fixed backgrounds, and why they fail on iPhone#

background-attachment: fixed pins a background to the viewport while the content scrolls over it. It is the oldest parallax there is, and it fails where many visitors are: MDN's compatibility data notes that Safari on iOS recognizes fixed but it has no effect. It also gives you two layers and no control over speed.

Perspective on a scroll container#

The classic pure-CSS method puts perspective: 1px on a scrolling container and pushes layers back with translateZ. A layer at translateZ(-1px) scrolls at half speed, and scale(2) restores its size. It works, at the cost of moving the scrolling from the document into a container, so the mobile address bar no longer collapses and anything that expects the window to scroll has to be told otherwise.

A named view timeline#

Since Firefox 159 shipped scroll-driven animations on 27 October 2026, a view timeline works in Chrome and Edge from 115, Safari from 26 and Firefox from 159 (MDN). Name the timeline on the section and let each layer read it, so the layers' own movement never changes the progress they are driven by.

css
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .depth { view-timeline: --depth block; }
    .depth .layer {
      animation: drift linear both;
      animation-timeline: --depth;
      animation-range: cover 0% cover 100%;
    }
    .depth .near { --travel: 120px; }
    .depth .far  { --travel: 40px; }
  }
}
@keyframes drift {
  from { translate: 0 var(--travel); }
  to   { translate: 0 calc(var(--travel) * -1); }
}

In a project that already uses GSAP, ScrollSmoother's data-speed attribute does the same job, covered in the GSAP ScrollTrigger guide.

What a parallax prompt has to specify#

A model asked for "a parallax effect" writes translateY(scrollY * 0.5) on a background and stops. A prompt that produces the examples above names seven things.

  1. The input: scroll, pointer or clock, and the fallback on touch screens.
  2. Every layer, back to front, with its speed, travel or amplitude.
  3. The anchor: each layer at its layout position when its middle crosses the window's middle.
  4. The measurement rule: layout positions only, measured on load, when fonts are ready and on resize, never inside the loop.
  5. Overscan: every moving layer larger than its frame by its maximum travel, so no edge ever shows.
  6. Phones: travel halved below 768px, as Onward does with its scroll-driven type.
  7. The still state: under prefers-reduced-motion: reduce, every layer at its layout position.
text
Hero depth, scroll-driven: four layers back to front. Background word
speed 1 (still), headline 1.22, bottle 1.0, splashes 1.33 and 1.4.
Offset = -(speed - 1) * (scrollY - c), c = scroll at which the layer's
middle meets the window's middle. Transforms only, positions measured
through offsetParent on load, fonts ready and resize. Splash images
oversized by their max travel. Below 768px, halve every travel.
Reduced motion: no loop, every layer in place.

Parallax mistakes to avoid#

  • Speeds far from 1. At 2 and above, layers collide with the content around them or open gaps between sections. Stay between 1.1 and 1.5.
  • Anchoring to the top of the page. scrollY * 0.5 drifts further every section; anchor each layer to its own center.
  • Relying on fixed backgrounds. They do nothing on iPhone; use transforms.
  • Animating background-position or top. Both repaint or relayout every frame. Move layers with transform or translate, which the animation budgets explain.
  • Parallax on running text. Headlines can move; paragraphs people are reading should not. The motion design guide lists what to keep still.
  • Layers on different clocks. One period for every ambient layer, or the drift turns into a wobble.
  • Ignoring the setting. WCAG 2.3.3 describes parallax as often non-essential and asks that motion triggered by interaction can be turned off. prefers-reduced-motion is that switch.

Open one example and copy its depth#

Choose the input first. A product page that people scroll suits scroll parallax anchored mid-screen, a hero that should feel alive suits pointer parallax with a touch sway, and a portrait or a poster suits a slow clock. Then give each layer its number. The rest of the cluster, from scroll-scrubbed films to text effects, is on the inspiration hub, with entrance and pointer effects collected in website animation effects.

Open Kickflip, watch the poster's layers slide apart over the 3D room in its preview, and copy its prompt into Claude or Cursor: the travel of every layer, the camera offset, the touch sweep and the reduced motion state are already written, along with the hosted images the shop uses. Kickflip is on the paid plans, listed on the pricing page. Vesperion, a creature standing in front of a giant word whose eyes follow your cursor, is free with an account and shows depth by layering alone, and the rest of the library is open to browse.

Questions this raises

What is a parallax website?

A parallax website moves layers of the page at different rates for the same input, so the near layers travel further than the far ones and the page reads as having depth. The input is usually the scroll, but it can also be the pointer, or a slow clock that drifts the layers on their own. Good parallax uses small differences, such as speeds of 1.1 to 1.5.

Does parallax scrolling work on iPhone?

Not with background-attachment fixed. MDN's compatibility data notes that Safari on iOS recognizes the value but it has no effect, so the background simply scrolls. Parallax built with transforms, driven by a requestAnimationFrame loop or a CSS view timeline, works on iPhone like anywhere else. Halve the travel on narrow screens.

Is parallax scrolling bad for accessibility?

It can be. Large motion tied to scrolling can trigger discomfort for people with vestibular disorders, and WCAG 2.3.3 describes parallax as often non-essential. Respect prefers-reduced-motion by leaving every layer at its layout position, keep the travel small, and never put parallax on running text that people need to read.

How do you make a parallax effect with CSS only?

Give the section a named view timeline and animate each layer's translate along it, with a larger travel on the near layers. Since Firefox 159 added scroll-driven animations in October 2026, this works in Chrome, Edge, Safari and Firefox. The older method, a perspective on a scroll container with translateZ on the layers, also works but moves the scrolling off the document.

Part of

Inspiration, with the prompt

Examples of the looks and effects people search for, from 3D scenes to scroll animation, each one shown working and paired with the specification that builds it.