The way that background-attachment: fixed
is commonly used is as a way to prevent a background image from moving relative to the viewport whenever the document is scrolled.
Note: For the purposes of this explanation, the viewport can be thought of as being equivalent to the browser window, although this isn't strictly the case.
CSS does this by basically taking the element's background image and attaching it to the viewport instead of to the element itself.
Since the viewport doesn't change its position when the user scrolls, the image will be statically positioned relative to the window. So far, so good.
Where we get into trouble with attachment is when we try to combine it with background-size: cover
(or contain
). Because background-attachment
has already sent the background image to the viewport, any background position changes made through the CSS become relative to the viewport.
This is normally fine, but it means that when you try to use either a percentage value or a predefined size operator like cover
, the background will also be sized to the viewport.
While writing up this summary, I discovered that this behavior is mentioned in the MDN documentation. The only problem is that it's just two sentences jammed in the middle of the percentage paragraph in the Values section of the background-size page. Yikes.
I've created an interactive demo to show the results of this behavior. To view it, click here.
The demo will display four panels, each with a different combination of sizing and attachment. Move your mouse over each of these panels to see how the background image is positioned in the container, and what's been hidden.
Fun fact: I made over 300 revisions to this demo before I was comfortable calling it done :P
Conclusion
In one of your comments below, you said (emphasis mine):
Cover was the culprit - but I have a little idea why. All the images
are landscape. The elements have min-height. I expected that the cover
will fix the width to the viewport, and that contain would fix the
height to the element height. Instead I see the cover zooming absurdly
(not matching any of the dimensions) and contain matches the width
(which is what I wanted). But why?
CSS often subverts expectations, and this is no different. For the cases below, assume we're using background-repeat: no-repeat
.
cover
scales the background image so it fits the element's largest dimension exactly and overflows the smaller one. This will generally cause it to be much larger than the element, showing only a portion of the image.
contain
sizes the background image so it fits the element's smallest dimension and leaves blank space on either side of the image in the larger dimension.
But when you use background-attachment: fixed
...
- When you use
cover
, what you're actually seeing is the image being scaled to match the height of the viewport, since the height is smaller. With a landscape image, the height of the image will be scaled to the height of the viewport, which is why it appears so large.
- When you use
contain
, the image is scaled to match the width of the viewport. If your element takes up the full width of the viewport, this will cover the element, cutting off the image's height, if necessary.
If you want to size the image using element-relative cover
or contain
, your two options, essentially, are to remove background-attachment: fixed
, or to resize the source image so that your background-size
declaration isn't necessary. Unfortunately, no CSS solution currently exists to enable attachment and keyword-based sizing at the same time.