TechEarl

Fixed (Parallax-Style) Background Images with background-attachment

How to pin a background image to the viewport so content scrolls over it (the cheap parallax look), the full cover/center/fixed recipe, why background-attachment is not background-position, and the iOS Safari caveat that breaks it on mobile.

Ishan Karunaratne⏱️ 6 min readUpdated
Share thisCopied
Pin a background image to the viewport with background-attachment fixed for a parallax-style hero, plus the full cover/center recipe and the iOS Safari mobile fallback.

To keep a background image pinned to the viewport while the content scrolls over it (the cheap parallax look), set background-attachment: fixed. The background stays put; everything else moves. That is the whole trick:

css
.hero {
  background-image: url("/images/mountains.jpg");
  background-attachment: fixed;
}

background-attachment: fixed positions the image relative to the viewport instead of the element. As you scroll, the element moves but the image does not, so content appears to slide over a stationary photo. It is not real parallax (nothing moves at a different rate), but it reads as parallax for one line of CSS. The default is scroll, where the image is fixed to the element and scrolls away with it.

The full full-bleed recipe

A bare fixed rarely looks right on its own. For a hero that fills the viewport and covers cleanly at any size, add three more properties: background-size: cover so the image scales to fill without distorting, background-position: center so the crop stays sensible, and background-repeat: no-repeat so you never see a tiled seam.

css
.hero {
  min-height: 100vh;
  background-image: url("/images/mountains.jpg");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  background-attachment: fixed;
}

Or, collapsed into the background shorthand (note size comes after position, separated by a slash):

css
.hero {
  min-height: 100vh;
  background: url("/images/mountains.jpg") center/cover no-repeat fixed;
}

Both are identical. The shorthand silently resets any background property you leave out to its initial value, so I keep the longhand while iterating and collapse to the shorthand once the values settle.

background-attachment is not background-position

This is the one thing the old comment threads on this topic always got wrong. Two different properties do two different jobs:

  • background-attachment controls scroll behavior: does the image move with the element, or stay fixed to the viewport. Values: scroll, fixed, local.
  • background-position controls placement: where the image sits in its painting area (center, top left, 50% 25%).

There is no background-position: fixed. Write that and the browser drops the invalid declaration, which is exactly why people who reached for it saw "nothing happened" and concluded fixed backgrounds were broken. The value you want is background-attachment: fixed: position decides where, attachment decides whether it scrolls.

The mobile caveat (this is the important one)

Here is the part that bites everyone: background-attachment: fixed does not work on iOS Safari, and is unreliable across mobile browsers generally. Safari on iPhone and iPad ignores it on purpose, not as a bug, because repainting a viewport-fixed background every scroll frame was too expensive on mobile hardware. On affected browsers the background just behaves as scroll (and historically some Android versions painted it in jumpy ways).

So a hero that looks great on desktop quietly falls back to a normal scrolling background on a phone, which is usually fine. If it is not, the standard workaround is to drop background-attachment and instead put the image on a separate element you fix with position: fixed, then layer content above it:

css
.hero {
  position: relative;
  min-height: 100vh;
}

.hero::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: url("/images/mountains.jpg") center/cover no-repeat;
}

A real position: fixed element is honored everywhere, including iOS Safari, so the pinned-background look survives on mobile. The trade-off: it sits behind all scrolling content, so this suits a single full-page backdrop rather than several stacked fixed-background sections.

If you would rather just turn the effect off on small screens, a media query is the least-effort fix:

css
@media (max-width: 768px) {
  .hero {
    background-attachment: scroll;
  }
}

That gives desktop the parallax-style hero and gives mobile a plain, well-behaved background instead of relying on the browser to silently do the right thing.

A note on performance and motion

Even where supported, fixed forces the browser to repaint the background as you scroll, which costs frames on large images or busy pages. Keep the source reasonably sized and let cover do the scaling. If you build genuine multi-speed parallax with JavaScript or transforms on top, respect users who asked for less motion:

css
@media (prefers-reduced-motion: reduce) {
  .hero {
    background-attachment: scroll;
  }
}

The fixed-background look is harmless enough that I do not always gate it behind prefers-reduced-motion, but any layered, multi-speed parallax should be.

FAQ

It pins the background image to the viewport instead of the element. As the page scrolls, the element moves but the image stays still, so the content appears to slide over a stationary photo. That is the parallax-style effect. The default value, scroll, fixes the image to the element so it scrolls away with it.

iOS Safari deliberately ignores background-attachment: fixed because repainting a viewport-fixed background on every scroll frame is too expensive on mobile. The image falls back to scrolling normally. To keep the effect on mobile, put the image on a separate element with position: fixed behind your content, or accept the scroll fallback via a media query.

It is background-attachment: fixed. There is no fixed value for background-position, so writing that does nothing (the browser drops the invalid declaration). The two properties are separate: background-position controls where the image sits, background-attachment controls whether it scrolls with the element or stays fixed to the viewport.

Use the background shorthand: background: url("...") center/cover no-repeat fixed; with a min-height on the element. That fills the area, keeps the crop centered, avoids tiling, and pins the image to the viewport, all in one line.

It can. The browser has to repaint the background as you scroll, which costs frames on large images or busy pages. Keep the source image reasonably sized and rely on cover for scaling. For layered multi-speed parallax built on top, also honor prefers-reduced-motion: reduce.

See also

  • Fixed vs sticky positioning in CSS: when to reach for position: fixed (the mobile workaround above) versus position: sticky, and how each behaves on scroll.
  • CSS filters: darken or blur a hero background with filter or a ::before overlay so overlaid text stays readable.
  • The CSS :has() selector: style a section based on what it contains, handy for toggling a fixed-background treatment by content.

Sources

Authoritative references this article was fact-checked against.

TagsCSSbackground-attachmentbackground-imageparallaxbackground-size coveriOS Safariresponsive design

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts

Complete reference for regex word boundaries: \b and \B zero-width assertions, engine-by-engine support (JS, Python, Java, PCRE, POSIX), Unicode handling, and lookaround alternatives. Worked examples for whole-word replace and search highlighting.

Regex Word Boundaries: \b, \B, and Lookaround Equivalents

Regex word boundaries (\b and \B) match positions between word and non-word characters with zero width. The full reference with engine differences, Unicode handling, lookaround alternatives, and worked examples for whole-word replace, search highlighting, and log parsing.

How the 2019 Capital One breach used SSRF to reach the AWS EC2 metadata service, steal IAM credentials, and exfiltrate 100M+ records. Why IMDSv2 exists.

The Capital One Breach: SSRF and the Cloud Metadata Service

In 2019, a misconfigured firewall let an attacker use SSRF to reach the AWS metadata service, steal the server's IAM credentials, and exfiltrate the data of over 100 million Capital One applicants. It is the canonical cloud-SSRF breach and a prominent example of the attack class IMDSv2 was designed to mitigate. A post-mortem of the attack chain.