Skip to content

[css-scroll-snap-2] Better snap physics and customization? #8549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
flackr opened this issue Mar 9, 2023 · 12 comments
Open

[css-scroll-snap-2] Better snap physics and customization? #8549

flackr opened this issue Mar 9, 2023 · 12 comments

Comments

@flackr
Copy link
Contributor

flackr commented Mar 9, 2023

We've had requests from developers (e.g. airbnb, google search) to customize the fling curve of snap point scrolls. A common cause or desire is to align behavior, which itself seems partially motivated from the way that browsers haven't given special treatment to scrolling physics for certain snap use cases in the same way that the experiences being emulating have given them different physics.

scroll-snap-stop is often used as a simple form of control over the physics (forcing a stop at each item), but even when the typical interaction is to scroll one item at a time developers often want to still allow flings to cross multiple items.

I think expectations may vary based on the size of snap items and lengths of documents, e.g.:

  • For long articles with headers that may want to snap, having snap behave similar to normal scrolling likely matches expectations
  • For lists with snap points on each item, there is often an expectation that a standard length flick will move about a page of items. The experiment in crbug.com/1189696 shows that for such use cases it may be as simple as adjusting fling distances to meet expectations here. A paginated book would likely have similar expectations.
  • Scrolling within a large snap point should probably match document scrolling but may want to prefer seeing the end of the current snap point rather than jumping to the next snap point.

While some cases the browser may be able to "just do the right thing", I think there are cases that would be helpful if the developer could give the browser a hint what kind of category things fall into.

Some possible ideas:

  • Add a scroll-snap-distance: item | page | document which hints whether the UA should treat a typical fling as traversing the distance of one item, one page, or a document scroll (the current behavior). Alternately, it could take a length.
  • Add a scroll-snap-friction: # which applies some amount of friction to getting to scrolling past each subsequent snap point.
  • Something else?

I'm not sure what the exact right solution is here but there seems to be a common developer need to apply different physics to different scenarios.

@tabatkins
Copy link
Member

I'm pretty strongly against more arbitrary control over fling curves; every single site that does customized scrolling that doesn't match the my OS's default fling physics is deeply unsettling to interact with, personally. This probably applies to things like letting people choose a friction amount, especially since there's no intuition for how it works so it'll just be a random number that feels right to the dev at the time, and totally inconsistent across sites.

(In particular, I consider the Google Search request to be an active anti-goal. "Consistent experience across OSes and devices" is another way of saying "wrong physics on every device and OS (maybe matching the one OS/device the dev happens to use and like)". Users want physics to be consistent across different pages/apps on their device, because it's something you build muscle memory for; a single page being different from other pages, but consistent across different devices, has literally zero benefit for users and significant downsides, imo.)

But having a little more control over how far a fling is meant to go over stops sounds reasonable! Especially with nice semantic names that appear to be meaningful, like the item/page/document you have here.

Is this meant to interact with scroll-snap-stop? Like, item would be the default and give the current stop scroll-snap-stop:always behavior, while the other values loosen it a bit? Or were you thinking about something else?

@flackr
Copy link
Contributor Author

flackr commented Mar 9, 2023

I guess it depends what you mean by loosen it up a bit. I was thinking that it would be closer to the experiment I did in crbug.com/1189696 where the browser can determine more appropriate fling distances. The goal there was to make the average fling distance about a page but if the user flings fast they can still fling further. I was initially thinking this should just be the default but have since come to realize this behavior only makes sense for itemized list scrollers and not something like snap points added to spec sections.

That said, my exact proposal of changing the "average" (specific fling modification to be determined by the browser) distance would mean that you'd either use this new property (which is like a less strict scroll-snap-stop) or scroll-snap-stop but it would be interesting to think if there are ways to make the two properties work together in meaningful ways. E.g. maybe if this is like a stop at the next mandatory item after x pages of scrolling then we could do the velocity modifications in chrome similar to the crbug.com/1189696 when you're in such a scroller because we know that you only meaningfully need to differentiate between the next N snap points and not scroll further.

@tabatkins
Copy link
Member

Yeah, "add friction but allow it to be overcome" should, I think, be done as part of scroll-snap-stop, which is basically "infinite friction on each item". Relaxing that to be "large but not infinite friction" sounds like a useful thing in the continuum of this property.

Maybe two axises - how often we want the "large friction" bump (every item, every page, none) and then how large the friction should be (infinite, or just large but overcomeable with a large fling).

The current always value is "every item, infinite", but "every page, infinite" sounds reasonable, as do both "every item, large" and "every page, large".

@flackr
Copy link
Contributor Author

flackr commented Mar 10, 2023

So in terms of an algorithm the UA could apply, we could scan through snap points in the scroll direction and reduce fling distance in some UA-chosen way based on this value on encountering each scroll-snap-stop point.

I am a bit worried that if we don't apply the friction uniformly it will be really hard to scroll less than that distance. For example, imagine a scroller where 5 items fit per screen. If we apply more friction to every 5th item, then it will be really hard to scroll less than 5 items. This might be fine, but it feels like it would be better to express this as those other items not having any scroll-snap-stop power. Naively, I'd want the scroll-snap-stop friction of each point to add up such that it scrolls about a page worth of items for some standard length fling.

So TLDR, I'd propose the friction of each scroll-snap-stop item be based on its proportion of the developer specified target distance.

@tabatkins
Copy link
Member

Why would it be hard to scroll less than 5? What would it be different vs scrolling without any added friction at all?

I imagine it might be more difficult to scroll, say, 6 items (because you'd have to hit the momentum just right) but I think that's okay?

@flackr
Copy link
Contributor Author

flackr commented Mar 10, 2023

For illustrative purposes let's say each item is 200px on a 1000px wide scroller, and that the increased scroll friction reduces the fling distance by 4000px. This is one hypothetical way a UA may implement the friction.

This means that flinging anywhere from 801px to 5000px will land you on item 5, and to get less than item 5 you'd need to fling less than 1000px.

My alternative proposal was that having scroll-snap-stop-friction: page would result in each item applying let's say 800px of fling reduction (e.g. snap stop width / page width * 4000) so flinging 0px - 1000px lands on item 1, 1001px - 2000px lands on item 2, 2001px - 3000px lands on item 3, etc.

@tabatkins
Copy link
Member

to get less than item 5 you'd need to fling less than 1000px.

Right, but that's exactly what you'd require if we weren't applying any special friction at all, too. This is why I'm confused you're saying this is a problem caused by the friction.

@flackr
Copy link
Contributor Author

flackr commented Mar 13, 2023

The difference is that we've made a large range of flings scroll exactly 5 items. So scrolling less requires a very gentle fling compared to that which gets you into the large stop at 5. This issue came up because on horizontal mandatory snap scrollers users were flinging way too far with relatively short flings. If every item has an equivalent scroll-snap-stop applied I think it would match author intentions better to make them each have a similarly large fling range that stops on those items (i.e. effectively reducing the fling distance like my prototype patch).

@tabatkins
Copy link
Member

Okay, so with the assumption that normal fling velocities are tuned for long documents rather than "a page", I can see why it's reasonable to essentially increase the friction on each item so that a standard fling will advance a page worth (rather than multiple pages, as normal), and then it's easier to fling less and get individual items aligned.

@argyleink
Copy link
Contributor

Another factor to think about is how the scroller is only allowed to have mandatory || proximity snap points. This would be like flexbox not having align-self or place-self.

I'm wondering if something like scroll-snap-point-type: mandatory || proximity, set on an item basis, would help single scrollers be more flexible. This would help snap points be more viable on a full document, for example, as it's very annoying if the scroller is set to mandatory, the user can't place the scroller in any position they want. It would be nice if certain large landmarks could individually be mandatory, making them a bit more greedy in terms of proximity and magnetizing the scroller to it while all the other snap points are light suggestions.

@flackr
Copy link
Contributor Author

flackr commented Mar 16, 2023

I was originally confused by this - because if there exist mandatory scroll snap points then presumably you have to choose one of them to snap to. I think what you're asking for is similar to increasing the proximity snap distance or an alternative mandatory snap that only looks at on screen points so that they behave like mandatory snap points while in view but otherwise do not force the scroll back to them. You can do this by making the entire "non-mandatory" region of the scroll a big mandatory snap point itself (basically what https://scroll-snap-demo.web.app/ does) but I can see how that's a bit awkward and doing so doesn't allow you to further place proximity snap points within that giant mandatory snap area.

@tabatkins
Copy link
Member

Right, "mandatory" really means mandatory, and that only makes sense (under the current definitions) as a policy on the container. We could have had a different conception of mandatory that meant something like "this snap point must be snapped to if it's the nearest point to where the fling lands, regardless of the actual distance", but that would have been a different model. But what Adam is describing actually just sounds like "stronger proximity", which is definitely workable in the model, it just wasn't pursued for v1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants