I was working on a client project a few days ago and wanted to create a certain effect on an <img>
. See, background images can do the effect I was looking for somewhat easily with background-attachment: fixed;
. With that in place, a background image stays in place—even when the page scrolls. It isn’t used all that often, so the effect can look unusual and striking, especially when used sparingly.
Table of Contents
- Why use <img> instead of background-image?
- 1. Using CSS background-image
- 2. Using the clip-path trick on an inline image
- Is there something better?
It took me some time to figure out how to achieve the same effect only with an inline image, rather than a CSS background image. This is a video of the effect in action:
The exact code for the above demo is available in this Git repo. Just note that it’s a Next.js project. We’ll get to a CodePen example with raw HTML in a bit.
<img>
instead of background-image
?
Why use The are a number of reasons I wanted this for my project:
- It’s easier to lazy load (e.g.
<img loading="lazy"… >
. - It provides better SEO (not to mention accessibility), thanks to
alt
text. - It’s possible to use
srcset
/sizes
to improve the loading performance. - It’s possible to use the
<picture>
tag to pick the best image size and format for the user’s browser. - It allows users to download save the image (without resorting to DevTools).
Overall, it’s better to use the image tag where you can, particularly if the image could be considered content and not decoration. So, I wound up landing on a technique that uses CSS clip-path
. We’ll get to that in a moment, right after we first look at the background-image
method for a nice side-by-side comparison of both approaches.
background-image
1. Using CSS This is the “original” way to pull off a fixed scrolling effect. Here’s the CSS:
.hero-section {
background-image: url("nice_bg_image.jpg");
background-repeat: no-repeat;
background-size: cover;
background-position: center;
background-attachment: fixed;
}
But as we just saw, this approach isn’t ideal for some situations because it relies on the CSS background-image
property to call and load the image. That means the image is technically not considered content—and thus unrecognized by screen readers. If we’re working with an image that is part of the content, then we really ought to make it accessible so it is consumed like content rather than decoration.
Otherwise, this technique works well, but only if the image spans the whole width of the viewport and/or is centered. If you have an image on the right or left side of the page like the example, you’ll run into a whole number of positioning issues because background-position
is relative to the center of the viewport.
Fixing it requires a few media queries to make sure it is positioned properly on all devices.
clip-path
trick on an inline image
2. Using the Someone on StackOverflow shared this clip-path
trick and it gets the job done well. You also get to keep using the<img>
tag, which, as we covered above, might be advantageous in some circumstances, especially where an image is part of the content rather than pure decoration.
Here’s the trick:
.image-container {
position: relative;
height: 200px;
clip-path: inset(0);
}
.image {
object-fit: cover;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
Check it out in action:
Now, before we rush out and plaster this snippet everywhere, it has its own set of downsides. For example, the code feels a bit lengthy to me for such a simple effect. But, even more important is the fact that working with clip-path
comes with some implications as well. For one, I can’t just slap a border-radius: 10px;
in there like I did in the earlier example to round the image’s corners. That won’t work—it requires making rounded corners from the clipping path itself.
Another example: I don’t know how to position the image within the clip-path
. Again, this might be a matter of knowing clip-path
really well and drawing it where you need to, or cropping the image itself ahead of time as needed.
Is there something better?
Personally, I gave up on using the fixed scrolling effect on inline images and am back to using a CSS background image—which I know is kind of limiting.
Have you ever tried pulling this off, particularly with an inline image, and managed it well? I’d love to hear!
Hi,
first approach has really ugly defect (see: https://bit.ly/32Y61HX)
second aproach is unreadable (im advanced user but really dont know what is unset(0) …)
Standard way with BG image is readable and better ;)
Inset isn’t that advanced actually.
It is a css shorthand instead of writing top, bottom, left and right.
You can read more about it here:
https://developer.mozilla.org/en-US/docs/Web/CSS/inset
We’ve had that defect happen once too while I was writing the article. My honest bet is that it may be a CodePen-related issue. I’ve never had the image warp like that in a real environment.
Btw – background-attachment:fixed doesn’t work in Safari or iOS due to a bug
You are perfectly right @Thomas. It works well on Mac Safari, but it seems it is not supported on IOS Safari. I actually didn’t know this. Thanks!
Seems like a cheap way (with some extra DOM) would be to wrap the tag in a div. Use
opacity
to hide the image without changing semantics/seo. Then set the background image on the wrapperThat didn’t occur to me. I’m wondering if there are any drawbacks to that?
You could also get it done without a wrapper by just positioning the image outside its element box with
object-position.
Switch the div for an img, keep the class and add just one line of css.
Fixed backgrounds are awesome, been using them for decades, but one thing to remember is the background image property was designed for decorative purposes only, it’s not meant to convey important information in the same way an inline image would. So you can’t really make a background image more accessible in WCAG terms, but you can certainly make it less accessible (https://www.w3.org/TR/WCAG20-TECHS/F3.html).
Although it doesn’t cover background images specifically it might be worth having a read through some of the principals at https://www.w3.org/WAI/tutorials/images/ regarding decorative images and such, helps explain some of the different reasons why some images require more informative descriptions than others.
Lately I’ve been playing around combining fixed gradient backgrounds with solid and lightly transparent content masks over different areas, the fixed effect can be awesome when done well but can be tricky getting just right. But it can also be like opening a Pandora’s Box inside a can of worms whilst you’re down a rabbit hole, once you see the possibilities you start trying to create complexity where it isn’t needed. So my only advice is be careful, sometimes the easiest way is the easiest for a reason.
You’re perfectly right that it can be a Pandora’s Box If I remember correctly, I think I ended up not using the fixed background effect for that image in the production version of the client’s website.
It’s interesting that
clip-path
even affects fixed-position elements. Bothoverflow: hidden;
andoverflow: clip;
don’t affect it. Fixed-position elements, as well as fixed-position background images, are taken out of the page flow and positioned only in relation to the viewport, so you cannot position them in relation to the parent element. You have to separately align them to the same point – either by using the center, by accounting for all possible layouts with media queries or by fetching the offset with JS.(Also your background-image approach gets some weird stretch when scrolling it on a narrow viewport – either on mobile or on the embedded pen. I don’t know why this is)
As for an alternative – I made a fork of your second approach based on
position: sticky;
, an additional overflowing container andoverflow: clip;
. You can take a look: https://codepen.io/AnisMan/full/bGojraJI have noticed the stretch issue too even before posting it, but only in one situation. We couldn’t figure out why that happens sometimes. I, personally, believe it could be an issue with CodePen? But I am really not sure.
I checked your fork out & I even played around with it. I can’t quite see how it achieves the effect I went for
(My previous reply didn’t come through it seems)
Assuming I didn’t miss your entire point somehow, I now fixed the fork to actually demonstrate the effect of a background attached to the viewport. Previously it was more like a proof of concept.
The idea is that when given enough space,
position:sticky;
behaves the same asposition:fixed;
, but you can choose directions – attaching it to the viewport vertically and to the parent element horizontally.Hi there,
Played a bit with your clip-path approach.
clip-path: inset(0 round 25px);
seems to do the job quite well in FF, Safari & Chrome.Fork of your codepen: here
Round corners trick found: here
This is actually super useful. The example and the clip-path generator. Thanks a lot!