Before this new CSS I’m about to introduce existed, locking an element into the viewport on scroll required rigging up some JavaScript. As you may know, JavaScript has a well-earned reputation to be tricky when paired with scrolling behavior.
The new CSS Scroll Snap Points spec promises to help, allowing for this kind of behavior using very few lines of CSS.
As happens with very new web tech, this spec has changed over time. There is “old” and “new” properties and values. It’s promising though, as support has shot up quickly. I’ll teach you how to get the widest support in this in-between stage.
Polyfilled Demo
The demo below has horizontal scrolling. It’s responsive: each “panel” is the width and height of the viewport (thanks to vh
and vw
units).
It uses a polyfill, but in order to use it (and support is still low enough that I suggest you do), you have to support the “old” values, which is why I’ll cover them, too.
See the Pen Simple Responsive Scroll Snap Point Demo by CSS-Tricks (@css-tricks) on CodePen.
- If you’re looking in Firefox: it has the best current support, so you can mostly clearly see how the native behavior looks and feels.
- If you’re looking Chrome or Opera: don’t have any support, so any behavior you notice in those browsers can be attributed to the polyfill entirely.
- If you’re looking in Edge or IE: it probably won’t work at all. These browsers have partial support, but apparently not enough to make this work.
- If you’re looking on a mobile device: iOS 9 supports it (tested on an iPhone 6), but I’ve seen the easing behavior act pretty weird. No Chrome/Android support, but the polyfill kicks in and handles it pretty well (tested on an Android Nexus 6).
Note I’m using Autoprefixer in the Pen to automatically give me all the necessary vendor-prefixed properties.
Here’s the code used to make the magic:
.scroller {
scroll-snap-type: x mandatory;
/* older spec implementation */
scroll-snap-destination: 0% 100%;
scroll-snap-points-x: repeat(100%);
}
.page {
scroll-snap-align: start;
}
Pretty slim! Let’s break down these properties one by one.
Current CSS Scroll Snap Properties
scroll-snap-type
scroll-snap-type: none | mandatory | proximity;
A mandatory
value is what you might think it would mean: that the element must come to rest on a snap point even when there are no active scrolling actions taken. If the content is somehow modified or updated, the page finds the snap point again.
The proximity
value is close to mandatory
, but less strict. If the browser changes in size or content are added, it may or may not find the snap point again, depending on how close to a snap point it is.
From what I’ve seen playing around with this, mandatory
is more commonly supported in browsers at this time with more consistent behavior.
scroll-snap-align
scroll-snap-align: [none | start | end | center] [none | start | end | center];
This property refers to how an element’s scroll snap margin aligns with its parent scroll container. It uses two values, x
and y
, and if you only use one value it will be read as shorthand and repeated for both values (sort of like padding where padding: 10px;
equals padding: 10px 10px 10px 10px;
). This property isn’t animatable.
scroll-snap-paddingscroll-padding
Heads up, scroll-snap-padding
has been renamed to scroll-padding
.
scroll-snap-padding: <length> | <percentage>;
scroll-padding: <length> | <percentage>;
This property relates to the scroll container in the visual viewport. It works much like normal padding, with the same kind of value order. For example, scroll-padding: 75px 0 0;
would be top padding of 75px
and all others 0
. This property is animatable, so if you need to move scroll snap align, this would be a good way to do so.
Older CSS Scroll Snap Properties
As mentioned, the spec has been changing rapidly in the past year and there are already properties that are considered outdated, though are still good to know from a legacy support standpoint.
scroll-snap-points
scroll-snap-points-<x or y>: none | repeat(<length>);
scroll-snap-point
addresses the axis that is the direction of the scroll. In the first Pen we saw, this property is set on the x
axis. Here, we have it on the y
axis (since it’s a vertical scroll) using scroll-snap-points-y: repeat(100%);
The percentages refer to the padding box of whatever you’ve defined as the scroll container.
See the Pen Simple Scroll Snap Points by Sarah Drasner (@sdras) on CodePen.
scroll-snap-destination
This property and scroll-snap-coordinate
are very similar as far as values go. Where scroll-snap-destination
refers to the parent element, scroll-snap-coordinate
refers to the element itself. You might only need scroll-snap-destination
to be specified if the snapping point is specified purely by the element rather than the container it sits in.
scroll-snap-destination: <position>;
This property allows you to specify at what point in the viewport the scroll should snap. For instance, say you want to cheat out your content by 100px so that two one panel is teased to one side of the other. The diagram below shows how it the scroll snap destination will allow you to easily adjust this parameter.
When defined as a percentage, the point is relative to the width and height of the scroll container.
scroll-snap-coordinate
Scroll-snap-coordinate: none | <position>;
This property allows you to specify where the scroll should snap to an element. The position amount refers to the element’s border box. You might not need it unless you’re doing something pretty fancy. scroll-snap-coordinate
is the only value that can apply to all elements on the page, all other scroll snap properties apply only to scroll containers.
These last two properties, scroll-snap-destination
and scroll-snap-coordinate
are animatable properties, while scroll-snap-type
and scroll-snap-points
—
More Resources
- CSS Scroll Snap Points Module Level 1 working draft
- MDN reference
- WebKit blog post
- CodePen collection
Browser Support
This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
Desktop
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
69 | 68 | 11* | 79 | 11 |
Mobile / Tablet
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
123 | 124 | 123 | 11.0-11.2 |
Conclusion
At the time of this post, the spec was updated about a month ago and is still moving. Support isn’t ubiquitous yet, but even if you have to account for the older values that the polyfill requires, the code to make this work is small and sweet. It’s incredibly fun to see what’s coming up in CSS, and if you run experiments, by all means, let the spec authors know what you’re doing and what you find – it does affect what they work on. I’m still on the fence about using something like this in production yet. It would depend on how willing you are to iterate on that code if things change around in the future. In the meantime, though, the polyfill performs pretty well and it’s exciting to see this kind of thing land in CSS.
Hi.
As cool as it is, I don’t think this feature “is coming”.
I thing it was removed (deprecated) before it even went in.
Please click the MDN link you provided and read the red “Deprecated” message on the top.
Hi,
I think that speaks only about the property ‘scroll-snap-points-x’ and not the full module, doesn’t it?
Please click the link to the spec we provided and see:
…which is super recent and I think is a good indicator this is moving forward.
I’ll update that MDN link to point to a more-relevant sub-feature though, as that one does look like one of the “old” properties.
I had updated it with the common link but something was reverted, fixed now! Here’s the link we meant: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap_Points.
This full spec isn’t deprecated, but rather it moved to a different implementation. In the article I cover this, and also why it’s still valuable in this interim period to look at some of these old properties- primarily because the polyfill still uses them.
The spec might still move again in the future, but luckily, even supporting the old values isn’t much code to achieve a lot.
When will developers learn to leave scrolling to the user? I instantly close out any site that scrolljacks me for any reason at all. If you want it to ‘snap’ to the next zone, provide an arrow for someone to click. Do Not Change Default Behaviors.
Example of why this functionality is absolutely terrible:
I use a vertical monitor. The content will often, if not always, rest either too high or too low. With this functionality, I can’t position it to comfortably read because a developer decided for me how far I should be able to scroll content. I either disable CSS to read their content (as if I’d bother?) or I put up with reading uncomfortably (hint: I won’t).
For anyone who wants to add this behavior to their sites: think of your users! Offer it as an alternative that users need to opt into (e.g. clicking a left/right arrow) and DON’T BREAK DEFAULTS.
Hey Nadya, absolutely agree with you, however I think it actually comes down to the developers who implement the solution not catering for anything like a portrait mode rather than just the traditional landscape.
With the right implementation it can work, however in most cases what you’re experiencing is correct. However, same can be said of any of the features of CSS.
I see both sides here.
I’d worry this argument gets a little dogmatic.
After all, what is a default scrolling behavior if not what the browser provides? And this is a scrolling behavior that the browser provides (or will).
It’s more like “you like it if it’s well done and hate it if it’s not”.
“Elastic scrolling” wasn’t always a thing. Not everybody liked it but most people do and most people just leave it alone. They don’t trot out the “never change scrolling ever!!” argument.
These demos walk the line for me, depending on the experience. The first demo, in desktop Chrome, feels kinda not great to me. It feels kinda hijacked and opinionated about where it should be scrolling. But the same demo in Firefox feels WAY nicer. And I can imagine on a well-supported mobile browser, a handful of iterations from now, this will be as smooth and nice as elastic scrolling is.
@Damian
That is part of the problem but it is one that I’m personally accustomed to. Many, many sites break for my “slightly wider than a tablet” screen size. I know this, expect this, and generally work around it (I use Stylish to change the CSS on any site I visit/read regularly)
I’ve still had the “text not quite where I’d like to read it” issue with this exact same functionality but implemented in JS rather than CSS on landscape views. A good chunk of the problem is not testing how well it works for different window sizes, but it can never account for where people prefer to read.
I like to read with text starting at about 1/5th down my monitor. Others prefer it in the dead middle. Others prefer scrolling down as they read the last line. When you dictate scrolling for these users, you no longer cater to any of them and actively break their experience.
I don’t equate deliberately taking control away from the user with failing to code for every possible screen size. About the only other breaking-UX CSS feature I can think of offhand is custom cursors. Thankfully, people stopped have mostly stopped using those.
@Chris
My browser affords to me the ability to customize most everything I wish. I disable Smooth Scrolling. I can even decide to change my scroll speed so that my browser scrolls exactly how fast I want it to. I can choose to have it accelerate or remain constant as I continue to scroll.
If I wanted these changed a different way – I’d change them myself. Not have 10 different ways of scrolling across 10 different sites because the devs thought they knew better than I.
I don’t mind that Smooth Scroll was more popular than not. As long as it is something I can disable, I don’t care if it is made the default for others. The important part is that I am in control of it.
FWIW I also back out of sites that force smooth scrolling. There is no such thing as “done well”. It’s an irritating experience and not one I care to put up with. I’m aware I’m part of a militant minority for user control.
I think the single biggest issue with scroll-jacking is that, regardless of the on-screen experience intended for the user, there will always be one major thing that no browser or OS can fix: haptic feedback. Anyone using a mouse with a scroll wheel (or a touchpad/touchscreen, or any other physical scrolling mechanism) will be scrolling, swiping, pressing, pushing, pulling in a particular direction and expecting to see real-time feedback of their actions on the screen in front of them. Scroll-jacking breaks this expected feedback behaviour by altering how our physical interactions translate on screen.
Any feature that makes the user feel like their hardware is malfunctioning is not something I’d like to see. /rant
@Chuck I can’t agree more with you. And this is not related to how good the implementation is.
@Plopz So true. Having this feature displays the usual scrollbar but not behaving the same way is the problem. Just create a new browser component for this and OK I could potentially use it.
At least in Firefox’s implementation, holding the scroll position for even a tiny bit overrides the snaps. A quick scroll-gesture will move in snaps, but if you hold position it stays put, respecting your desired scroll position.
I don’t actually know if that is part of the standard, but if it isn’t, then it should be.
I don’t think scrollbars are the right UI element to control this kind of experience. The issue is the scrollbar doesn’t change to designate what is going to happen. Perhaps if the scrollbar was redesigned to show the breakpoints. But this kind of behavior seems more suited to directional buttons (left, right arrows) or index buttons (1,2,3,4,5…).
I would like to avoid any scrolling behavior on my site at this point. Until i would see some sophisticated solutions which works in all browsers and has real use case.
Thanks,
HP
I have to admit, I’m not super psyched about this becoming a thing. It might be acceptable at one point but in the meanwhile it would probably lead to even more annoying websites.
Hi Sarah, would you like to help contribute to the polyfill?
I’ve already added a PR of my own: https://github.com/ckrack/scrollsnap-polyfill/pull/7
It would be great if we could get in contact with ckrack to poke him and see if he can review the PRs.
Hi Martin! Yeah, definitely! I’ll take a look this weekend. If it gets updated we don’t have to support the old values anymore.
Thanks
I’d almost rather see scroll disabled on these implementations with an added section navigation. I’ve always been against any sort of scroll-jacking, just at least provide a navigation so I don’t have to wait for the animation to complete or skip back a section because I scrolled too much. I want to be gliding quickly through a site most of the time. This feature is always going to depend on the desired UX, “Do I want to control the user or let them control the product? How much control should I give them?” That all being said, it’s great to see some sort of standard coming along.
What if (for whatever reason), the user wants to scroll to the point in-between snap points? Don’t jack my scroll bro!
You don’t have to use ‘mandatory’- you can also use ‘proximity’. I used mandatory for the demo because it’s better supported and shows the capabilities better.
I do see what you’re saying in general, though, and why people are critical of it. I’m critical myself. But I think it’s still important to learn the tools because you might have a client use-case someday, and knowing what you’re critical of is useful anyways, because, as demonstrated in this very comment- there’s another way of working here :)
I get 404 on every pen. Could you check them?
I don’t- can you please check your connection or in another browser?
Wow, the page goes 404 if third-party cookies are disabled or somewhat limited. What crazy system is that? I haven’t been in the industry for many years, but I thought that 404 meant “File not found” not “Man, I need cookies”. :)
There are a lot of negative comments here, but let me add a positive use case for this:
On mobile, many apps use a tab navigation model, with horizontal scrolling between panels. Think Facebook Messenger as an example. With scroll snapping, this sort of behavior can be implemented without requiring JS.
Jesus, I hope that never gets into life.