-
Notifications
You must be signed in to change notification settings - Fork 719
[css-scroll-snap-1] Compat between webkit and blink/gecko regarding "implicit" scroll boundary snap positions #4037
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
Comments
TL;DR: creating implicit scroll positions at scrollmin/scrollmax breaks a popular web-performance technique: Virtual Scrolling. If we don't standardize on "no implicit scroll positions" (the current Blink/Gecko behavior), then IMHO we'd need another way to enable Virtual Scrolling to work with scroll snapping. Details are below. The current WebKit behavior (creating implicit snap positions at scrollmin/scrollmax of the container) breaks "Virtual Scrolling" implementations like Facebook's react-window that use JavaScript to simulate a long, scrollable list of thousands of items without the overhead of creating thousands of DOM nodes. This technique has been popular for many years because it's a good way to reduce the initial render time (and client RAM footprint and server data-fetching) of long lists without the complexity of having to roll your own scrollbar UX and scroll behavior, which is really hard to get right and is expensive to maintain. Especially on touch devices. Virtual Scrolling implementations work like this:
You can see an example of Virtual Scrolling here: https://codesandbox.io/s/happy-architecture-m7pn5. The top list uses Virtual Scrolling. Unfortunately, WebKit's behavior that defines implicit snap positions at the scrollmin/scrollmax breaks these virtual list components if scroll snap is enabled. A fast (flick/momentum) scroll on Safari will scroll to the end of the content before the Virtual Scrolling script has a chance to place new Item elements at the user's new scroll position. Repro steps are here: bvaughn/react-window#290. I can think of many possible ways to address this problem:
I'm sure there are other options too. The only option that is IMHO not OK: requiring implicit scroll points at scrollmin/scrollmax (the current WebKit behavior) without some other way to ensure that Virtual Scrolling containers will work with scroll snapping enabled. What do you think? |
@justingrant but do you have any issue with webkit creating an implicit scrollmin snap position? The position where the scroll container loads in on? Do you really think that should be configurable having seen the ux when not creating that implicit position in blink/gecko? |
Hi @jonjohnjohnson - My main concern is finding a solution that won't break virtual scrolling implementations. An implicit scrollmin snap position breaks virtual scrolling lists when the user is scrolling backwards, so I wouldn't support standardizing the WebKit behavior (even just for scrollmin) unless there were some way to turn it off so that virtual scrolling would work OK. The gecko behavior is definitely weird the way it scrolls all the way to the end and snaps all the way back. That should definitely be fixed. But I think the "fix" could just match the blink behavior where scrolls are not allowed to escape the first/last snap-aligned child element. I'm not saying it's good UX to be unable to return to the initial on-loaded scroll position, but I'm also not sure that this is a common-enough use case to be worth worrying about. Other than virtual scrolling, what are common use-cases that would combine scroll snapping with needing to navigate beyond the first/last snapped element? If we think that developers making mistakes in CSS is a common reason we'd see this, then I'd also support a solution that would auto-scroll to the first snap position instead of starting from zero. This would eliminate the "can't go back home" problem and would make CSS bugs easier to find and fix because the bad UX would show up immediately on load instead of requiring user interaction. Doing this might be expensive to implement or expensive at runtime though; I'm not familiar enough to know. If there are common use-cases (not just developer errors) where users need to scroll to empty space in a container or to un-snap-aligned child elements, then I'd support an option to enable the WebKit implicit scroll positions, as long as there's a way for virtual scrolling implementations to turn them off. |
@justingrant I feel as though you don’t realize that scroll containers load in on the “scrollmin” position in the first place? So what’s a scenario you can imagine where the initial scroll position should not be reachable after scrolling, even in what I consider the uncommon case of virtual scrolling? You refer to “empty space” but there can be content in the “space” that just hasn’t declared a choosable/reachable snap alignment based upon the size of the snapport, content, and aligned edge? Have you scrolled the original posts test case?
I don’t think you’re realizing that the load in scroll position is “escaped” from what you propose, on top of being hostile towards users. |
Hi @jonjohnjohnson - Thanks for quick reply! Other than the priority of virtual scrolling, I suspect that we're pretty much in agreement on most points. Here's some responses and notes, let me know what you think.
Nope, I understand this. Every time I have to write code to set a non-zero scroll position on page load, I'm reminded of the default. ;-)
I don't know any case, including virtual scrolling, where a developer would deliberately want to make the initial position unreachable for the user. In a virtual list, users can scroll back to the initial position. The problem is a race condition: WebKit will scroll all the way back to the beginning or end of the list any time you flick/momentum-scroll because, at the time the user flicks, the list is mostly empty other than a few items under and around the scrollport. For example, imagine a user is currently viewing the 20,000th item in a virtual list and they want to go back 20 items. So they "flick right" hard enough to momentum-scroll for 20 items' width. But in the DOM there are currently only 2 items to the left of the scrollport, which means that a 20-item-width scroll will snap to the next snap point: scrollmin. Then the content starts scrolling in a blur for a few seconds, with display flashing as the virtual scrolling script madly tries to add new elements in front of the scroll position. Finally, the user ends up at the first item. Instead of scrolling 20 items, it scrolled 20,000. On blink, this works fine except momentum scroll is relatively slow and high-friction unless you add a lot more buffer items around the scrollport.
I don't have any stats on runtime usage, but here's a few data points about usage from the developer side. What level of developer usage do you think qualifies as not-uncommon?
Every major JS framework has several popular implementations of virtual scrolling because it's usually the only performant and easy-to-implement way to display long lists that are scrollable. We may not hear much about this usage because much of it is in "enterprisey" reporting or line-of-business apps. But it's fairly common, esp. in newer B2B/SaaS and IT tools that frequently have a need to show scrollable lists and reports with 1000+ rows.
Yep, understood. Was using "empty space" as a shorthand for something like "no reachable snap-aligned elements" because I was assuming that "no elements" would be the most common reason. Do you think the "no reachable snap-aligned elements near scrollmin/scrollmax" case is a common, intended case? Or is it likely to be a developer error like forgetting to snap-align some child elements?
Yep! I agree 100% with you that that test case is confusing UX on Chrome. I'm not opposed a solution that makes that case better, as long as that solution doesn't break virtual scrolling. While we're talking test cases, have you scrolled the top list on https://codesandbox.io/s/happy-architecture-m7pn5 on Safari? Let me know if you can think of another way to prevent the awful UX that happens when you flick/momentum-scroll that list on Safari.
Nope, I get it. It's weird. I suspect it's uncommon, but it's still weird to be unable to return home. I'd definitely support a solution that fixes this case as long as it won't break virtual scrolling. BTW, one other nice thing about the blink behavior is that achieving the webkit behavior is trivially easy for a developer or designer. On blink, to enable webkit-like snapping at scrollmin/scrollmax, simply put a snap-aligned element at scrollmin/scrollmax. No script required. For the reverse (preventing webkit from snapping at scrollmin/scrollmax) the only option, as far as I know, is to write a Javascript replacement for CSS scroll snapping. This is orders of magnitude harder... and hurts performance and UX too. |
Yes, in webkit as well as blink. I find the behavior for the third scroller strange in both. Both seem broken. Neither seems to tell a user that something is going all that well. Which I imagine is simply a byproduct of pigeon-holing virtualization into scroll-snapping. Since I am guessing the use of a simple As far as scroll-snapping behavior for content that is programmatically curated like what happens with virtualized scrolling, I have found that writing scroll/touch handlers on top of pointer events or things like hammer.js and the
I think with responsive scroll containers and content “no elements” wouldn’t be the common reason as much as the element that defines the nearest scroll boundary alignment is attempting to align to the opposite edge of the scroll boundary, but is too large. Also, if the first piece of content has a margin on that start boundary you’re then asking to rework the other stylings to account for |
As long as implicit snap positions at scrollmin/scrollmax can be turned off somehow via CSS or JS, I don't have a strong opinion about the default behavior. Sounds like we're not going to agree on the importance of supporting scroll snap with virtual scrolling implementations. Thanks for your thoughts here; you've helped me understand other facets of this problem.
Yep, that's what I'll have to do on my current project. Even if WebKit provides a no-implicit-edge-snapping option, it'll be too late for a Summer '19 release that I'm committed to. It'd still be good for other developers and my own future work to just be able to rely on CSS only for snapped scrolling.
Yep, agreed. Still orders of magnitude easier than building JS-based scrolling from scratch, though. But regardless, I'm supportive of the option you're looking for, and OK if it's the default as long as there's a way to opt out.
Makes sense, thanks for clarifying. Is "element too big for desired scroll snap" also a problem for elements in the middle, or only at the edges of the content? If the former, how would the developer fix it?
Yep, "strange" was the point of the third example. It's just a plain-HTML/CSS (not virtual) list meant to highlight the problem behavior, not to represent realistic HTML that anyone would want to use. Only the top list is virtual. |
Even if
The top list behaves quite normally for me in webkit, actually better than blink. In blink, I can only scroll a few past a few items at a time no matter how “hard” I attempt a scroll to move me far in either direction. In webkit, it’s affording me short scrolls to snap a few forwards or backwards as well as a “hard” scroll to reach the beginning or the end. Somehow webkit feels more intuitive for gestural (track/touch) scrolling, though I have not attempted wheel scrolling. And in webkit even if I’m scrolling quite far, I’m still able to stop mid snap anywhere want simply with a new scroll gesture and I’m not forced to resolve at the scrollmin/scrollmax. In blink it feels quite stunted that I cannot scroll far no matter how “hard” I attempt, though funnily enough, I can grab the scrollbar and go all the way from min to max with ease. Ya, if anything blink feels quite off in the top row compared to webkit (12.1.1). |
Yeah, it's the "hard scroll to reach the beginning or the end" that's the problematic UX on webkit. The second scroller (which is almost the same HTML/CSS as the top one, except it's static HTML not virtual) has IMHO expected behavior, which is that the hardest possible scroll (on my MacBook Pro trackpad) will scroll 80-100 items on both blink and webkit. But the particular project I'm working on has 10K+ items in the virtual list, so having a mild flick go all the way to the end is really unexpected for the user. The blink behavior on the top scroller is definitely slower because it won't scroll beyond the items that are already in the DOM. This behavior is fine for my current project but may not be ideal for others, but that can probably be worked around by adding more "buffer" elements on either side of the scrollport in the virtual list implementation. |
Paging in spec editors @fantasai @tabatkins I agree that this is an interop issue. Per current spec adding implicit start/end snap position is not correct. This is what Blink/Gecko implemented but safari does add implicit snap position at start/end of scrolling content. Here is some initial thoughts:
In any case, I think we should drive to a consensus here since if we don't remove the existing load at 0 behavior the issue will get harder to deal with over time. |
@majido - The approach you outline above sounds right. FWIW, it was very annoying to waste time this summer rolling my own JS scroll snapping implementation when the as-is web platform was so close to working properly for virtual lists. I look forward to being able to depend on the platform for snapping in future projects! |
Agree with Majid on all points. ^_^ In particular that if 0 isn't a valid scroll position due to snapping, it shouldn't initialize to that offset. I also don't think we should auto-add start/end snap positions. The thread has given good reasons to avoid it, and it's easy to ensure there is a useful snap point there if you need it. |
Also agree with @majido on all points. FWIW, these implicit snap points were explicitly discussed rejected, see Since the spec does not define that they exist, I think it should be sufficiently clear that, per spec, they don't exist... If that needs further clarification, we can add a note. “Note: BTW! The only snap positions that exist are the ones defined by As for the initial scroll position, the definition of I don't think there's anything left for the spec to clarify here, so afaict unless we need to change something due to Web-compat we should all be aligning implementations on #4037 (comment) @majido Can you take the lead on trying to fix the initial load position in Chrome? |
@fantasai thanks for additional background. Yes we have already started the work to implement this in Blink as part of snap-after-layout feature. Once we are have that implemented we will do the work to find out if we can also safely enable it for initial load as well. I will report back with our findings. |
To close the loop on this, our stats suggest that the initial load snapping is very safe change to make. Basically a very very small number of snap containers would scroll as a result of initial load snapping which means by making the scroller snap on initial load we should not see lots of visible behavior change. So we are proceeding with this change in Chrome M81. |
BTW here are the tests we have added to WPT for this feature as well. |
Snap-after-layout is now shipped in Chrome M81 (blog post) which also includes snap on initial load. |
Agenda+ to close out this issue as no change to the spec based on Majid's reasoning (which Tab and I fully agree with) in #4037 (comment) and subsequent implementation and compat data from Chrome. |
Is there a timeline for when this change will make it into WebKit and Safari? |
@justingrant Pretty sure “Apple does not comment on future product releases.” But you might make sure there's a bug filed against WebKit. |
I searched through WebKit's Bugzilla since 2014 and was unable to find a bug report that looks like this issue. But admittedly I may not know the right terms to find the correct match. @majido - in 2019 you helpfully opened a Safari bug about a different scroll snap issue (https://bugs.webkit.org/show_bug.cgi?id=197744#c0). Would you be willing to open one for this issue? I'd do it myself but:
I'm happy to add comments to a WebKit bug with use cases and other justification, but it'd be great to have someone more authoritative than me to kick it off. Would that work? Thanks! |
@justingrant I think you may be underestimating the weight of a bug report from a Real Live Web Developer with a Real Use Case, particularly one backed by both the spec and a Chrome implementation. ;) |
WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=216882 |
The CSS Working Group just discussed
The full IRC log of that discussion |
Uh oh!
There was an error while loading. Please reload this page.
https://drafts.csswg.org/css-scroll-snap-1/#choosing
Regarding https://codepen.io/anon/pen/XLwybE
All major vendors, of course, load in with a scroll position of
0
. However, webkit is the only implementation where once scrolled, a user is allowed to snap back to the0
scroll position, where "implicit" snap positions are created at the scroll boundaries.Aside from language like...
...the current spec mostly eludes to matching the blink/gecko behavior. If no elements create a snap position for the "scrollmin" or "scrollmax" scroll positions of their scroll container, then a user can never snap/resolve to a scroll boundary without being "bounced" back, almost with pseudo-rubberbanding visual.
https://bugs.chromium.org/p/chromium/issues/detail?id=953979
https://bugzilla.mozilla.org/show_bug.cgi?id=1545316
Are blink/gecko correct? Should the spec be made clearer? Or changed to agree with webkit?
The text was updated successfully, but these errors were encountered: