Skip to content
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

[css-scroll-snap-1] Improve or clarify nested snap behaviors #9187

Open
flackr opened this issue Aug 14, 2023 · 16 comments
Open

[css-scroll-snap-1] Improve or clarify nested snap behaviors #9187

flackr opened this issue Aug 14, 2023 · 16 comments
Labels
css-scroll-snap-1 Current Work

Comments

@flackr
Copy link
Contributor

flackr commented Aug 14, 2023

Friction to leaving larger than viewport area

example 8 suggests that there should be some resistance when leaving a snap area larger than the viewport (e.g. from section 1 to section 2).

However, if the container is scrolled such that the area no longer fully fills the viewport in an axis, the area resists outward scrolling until it is scrolled sufficiently to trigger snapping to a different snap position.

This seems like good behavior however it's not clear that it's specified by any normative behavior and it seems too easy on all browsers today to accidentally scroll into the next section.

Prefer descendant snap point when visible

example 9 suggests that the intention is to allow scrolling freely within larger sections while still snapping when smaller nested sections are encountered.

The normative text 5.2.2.,

If the snap area is larger than the snapport in a particular axis, then any scroll position in which

  • the snap area covers the snapport, and
  • the distance between the geometrically previous and subsequent otherwise-valid snap positions in that axis is larger than size of the snapport in that axis,

is a valid snap position in that axis.

I've created a page similar to example 9 from css-scroll-snap 5.2.2. Snapping Boxes that Overflow the Scrollport, where we have nested sections within section 2 at https://flackr.github.io/web-demos/css-scroll-snap/nested/sections.html

The second requirement seems like it may not correctly capture the intention - for example when scrolling within section 2 before we reach section 2.1 example 9 would suggest we should prefer either being before section 2.1 or snapped to section 2.1, however the spec text would suggest that since the distance between the previous snap point (section 1) and the subsequent snap point (section 2.1) is larger than the size of the viewport that we still consider arbitrary scroll within section 2 (with section 2.1 in view) to be snap aligned. Browsers indeed seem to not force snapping to section 2.1 until it's very near the top.

If my understanding of the spirit of the requirement is correct, I think that this body scroll snap region example (based on a developer request in crbug.com/1420439) resolves the developer issue by having the <body> (an ancestor of the mandatory scroll snap regions) also be a snap region.

@flackr flackr added the css-scroll-snap-1 Current Work label Aug 14, 2023
@flackr
Copy link
Contributor Author

flackr commented Aug 14, 2023

For the first issue, it seems like we should spec that there should be some friction in some way. E.g. maybe you have to be closer to the next snap point before it would be preferred over the end of the previous one? This would help make it easier to scroll to the end of a box / region that is larger than the scrollport without jumping to the next snap point.

For the second part, I could see replacing the distance check with something like non-aligned scroll positions are only valid if there's not a visible snap region to snap to within the larger than the scrollport region. The goal is to conceptually divide the larger snap point into valid snap regions which are before and after inner snap points.

@flackr
Copy link
Contributor Author

flackr commented Aug 16, 2023

I made a diagram to help discuss the behaviors:

scroll-snap-ranges

I think the expected behavior given this set of snap regions is the following:

  1. User's viewport can scroll freely within range 1
  2. Scrolling your viewport past range 1 will snap the viewport to range 2 (i.e. you won't be able to stop scrolling with #b half scrolled into view)
  3. Optional: Scrolling past range 2 will then jump to range 3 (range 3 aligns the gap between range 2 and range 4 with the scroll-snap-align declared on #a (i.e. currently top aligned but could be center / bottom aligned instead).
  4. Scrolling past range 3 (or range 2 if we don't consider range 3 a valid snap point) snaps to range 4.
  5. Optional: Scrolling past range 4 scrolls to range 5 until the bottom is aligned with the bottom of the viewport.

I think that including range 3 and range 5 are a good idea to avoid accidental unreachable content within an element which was declared with scroll-snap-align. However, if the only reason for the gap between range 2 and range 4 is blank (e.g. maybe entirely due to margins on #b and #c) we probably wouldn't want to snap to the gap between them / after them. As such, maybe range 3 and 5 are conditional on whether the preceding / following snap points' margin boxes fill that gap.

@flackr flackr added the Agenda+ label Aug 16, 2023
@flackr
Copy link
Contributor Author

flackr commented Sep 27, 2023

One other edge case to consider is what happens when the nested snap areas are not the full size of the viewport. I think that in these cases we may want to scale back the avoidance area of the ancestor snap by the inverse proportion the inner snap doesn't fill the viewport. This is to avoid situations where a small element could lead to a full page scroll to snap into view.

nested-avoidance

E.g. You should be able to scroll such that x% of a scrollport is visible beyond the inner snap where x% = (100 - y)% with y% being the proportion of the scrollport the inner snap area covers. Probably with a minimum avoidance equivalent to the snappiness of proximity so that there still is a noticable snap point for the inner snap area.

I've made a rough demo trying to show what this would look like by actually splitting up the outer snap area into smaller snap areas.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-scroll-snap-1] Improve or clarify nested snap behaviors.

The full IRC log of that discussion <fantasai> flackr: We've had many devs trying to use scroll-snap reach out to us regarding differen thing sthey're trying to build
<fantasai> flackr: one common use case is nesting snap areas to have a large area you can scroll freely, and some mandatory areas
<fantasai> flackr: paginated view within larger scroller
<fantasai> flackr: the spec suggests that this should be supported
<fantasai> flackr: but normative text doesn't explicitly define the expected outcome
<fantasai> flackr: so wanted to get feedback on this proposal to add explicit text
<fantasai> flackr: when yu're in overly large snap area, should avoid inter-snap areas
<fantasai> flackr: this creates desired effect of screens within a larger snap area
<fantasai> flackr: you either fully snap into view or avoid them
<fantasai> flackr: so proposal for change
<fantasai> flackr: a few cases to discuss, what do you do if those inter-snap areas aren't screen sized
<fantasai> flackr: not want to introduce large jumps in scroll because of single-line snap area
<fantasai> flackr: but I think we can define reasonable behaviors
<fantasai> flackr: do we think this is reasonable to put in the spec?
<fantasai> flackr: I can propose spec text, and have examples and diagrams
<fantasai> Rossen_: Thanks for intro, indeed the diagrams in the issue and spec are making this very visual and easy to follow
<fantasai> Rossen_: not seeing anyone in the queue, if interested in topic engage in issue and perhaps can return later

@zcorpan
Copy link
Member

zcorpan commented Oct 12, 2023

This issue is blocking Gecko bugs 1856205, 1856206, 1856207, which are part of the Scrolling focus area in Interop 2023. cc @hiikezoe

@flackr
Copy link
Contributor Author

flackr commented Nov 7, 2023

The main resolution I'm looking for here is regarding overlapping / nested areas.

I'd like to resolve that overflowing areas where a smaller nested snap area intersects the snap port should not allow snapping to the outer / larger area. In effect, the larger area is split into regions before and after the inner area.

For inner areas smaller than the scrollport, we expand the before valid area by inner snap rect bottom (when snapped) - scrollport bottom and the after area by scrollport top - inner snap top (when snapped) (and similarly for horizontal snap). This reduces the required jump when entering smaller inner mandatory areas.

@fantasai
Copy link
Collaborator

fantasai commented Nov 7, 2023

Looking at your example in #9187 (comment) I think the most sensible behavior might be to extend the snap areas for #b and #c to extend through to the next snap position. So you'd have one snap position at the top of #a, which allows scrolling freely to the top of #b. Then another position starting at the top of #b which scrolls freely to the top of #c. And a third one from the top of #c to the bottom of #a.

@flackr
Copy link
Contributor Author

flackr commented Nov 8, 2023

Looking at your example in #9187 (comment) I think the most sensible behavior might be to extend the snap areas for #b and #c to extend through to the next snap position. So you'd have one snap position at the top of #a, which allows scrolling freely to the top of #b. Then another position starting at the top of #b which scrolls freely to the top of #c. And a third one from the top of #c to the bottom of #a.

I don't think that this will be what authors want if they have a long #a and short #b and #c (e.g. inline images or diagrams similar to my demo). It's asymmetric, coming from below there is only a snap effect on leaving the image but not entering it. Coming from above you snap on entering but not leaving. We could do this only when the gap between #b and #c is shorter than a scrollport but that would result in a strange change in behavior when content sizes are sometimes just below / just above one scrollport.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-scroll-snap-1] Improve or clarify nested snap behaviors, and agreed to the following:

  • RESOLVED: Better define this behavior when you have nested snap areas and children with interleaved content from the parent
The full IRC log of that discussion <bramus> flackr: brought up a few weeks ago and got some comments
<bramus> … situation right now is that if snap area b inside of snap area a, there is almost no effect as ??? is a valid snap position.
<astearns> s/???/anywhere in a/
<bramus> … proposed to snap to inner element (b) or snap to position that avoids showing inner elem on the outer element
<bramus> … there is a linked demo with example
<astearns> ack fantasai
<bramus> … this came up as best way to implement desired snap behavior that search was exploring, or some free scrolling on some region and than mandatory in nested sections
<bramus> fantasai: if you end up snapping to margin in between things … which would not be … its a tricky situation but I am OK …
<bramus> … I trust Rob with poking around at this.
<bramus> … main concern if we are creating snap areas in segments in between child elements (eg margin)
<bramus> flackr: agree and that is where more complicated part of proposal gets into it
<bramus> … might need some better formulation
<bramus> … there are situation where innner area should be necessary as well
<bramus> … but i have same concern
<bramus> fantasai: agree that inner area should be accessible
<bramus> astearns: at moment spec say nothing about nested snap areas?
<bramus> flackr: it says something but its not …
<bramus> fantasai: it doesnt handle the interleaved case
<bramus> … we dont say what happens when you have content in between snappable areas
<bramus> flackr: so it sounds we are generally in favor but we need to define the edge conditions?
<bramus> astearns: PROPOSED RESOLUTION: Better define this behavior when you have nested snap areas and children with interleaved content from the parent
<bramus> … comments or concerns?
<bramus> … objections?
<bramus> RESOLVED: Better define this behavior when you have nested snap areas and children with interleaved content from the parent
<bramus> astearns: given complexity of this, might be good to have explainer with these demos
<bramus> flackr: yep

@flackr
Copy link
Contributor Author

flackr commented Nov 21, 2023

I've updated the demo:
https://flackr.github.io/web-demos/css-scroll-snap/nested/9187.html

The demo html now has examples of all of:

  • Snap align header text only
  • Snap aligned sections
  • Snap aligned inner diagrams
  • Snap aligned slides with the degenerate case called out of gaps between them

Also I've implemented snapping in javascript following the various strategies using the scrollend event as a trigger:

  1. Native - uses the browser's native snapping
  2. Avoid completely - Creates areas to implicitly snap before and after inner areas
  3. Proportional to size - Similar to the above, but allows overlap with inner areas that are smaller than the viewport proportional to the gap they do not fill when snapped. This reduces the jumps when forcing snapping to these inner areas.
  4. Join with inner ranges - This implements the proposal from @fantasai to effectively extend inner ranges by the outer content until the next snap area.

All of the strategies provide a reasonable outcome, though there are some particularly interesting edge cases:

  • Strategies 2 and 3 create a situation where gaps between inner areas become snap targets (e.g. see the gaps between the slides), whereas strategy 4 completely avoids this.
  • Strategy 4 removes the snap when below inner areas which is unexpected when leaving / entering the final slide or when leaving / entering inner diagrams.

I have not (yet) implemented a strategy which switches behavior based on the size / contentfulness of the area. While this could let us have a single behavior that provides the best of both ideas 2/3 and 4, if implemented poorly (e.g. join previous area if size is less than some size) it could lead to subtle differences in behavior on the same site based on screen size. Perhaps it's possible for us to detect when the gap between two inner areas has meaningful content which would avoid the degenerate behavior of strategies 2/3.

@flackr
Copy link
Contributor Author

flackr commented Nov 23, 2023

I added a new option which is a switch between the two behaviours (2 and 4):

  1. Join with inner ranges if gap is short - This compares the size of the outer area to the next inner snap area (or end of range). If it's shorter than a scrollport, then it extends the inner area (i.e. option 4). If it's longer, then it is a uniquely snappable area.

@fantasai WDYT of going with this option 5? This is basically saying that you should have a separate snap are for the outer range if it's large enough to view without seeing any of the inner range.

It's not great to have a size threshold as it will result in a flip of behavior at a particular size but I feel like the better experience for cases like the starting slide or inner diagrams is worth trying to support. Also the spec already has size dependent behavior:

the distance between the geometrically previous and subsequent otherwise-valid snap positions in that axis is larger than size of the snapport in that axis,

This would be in line with that existing text.

@flackr flackr added the Agenda+ label Nov 23, 2023
@flackr
Copy link
Contributor Author

flackr commented Nov 27, 2023

We probably also want to join with a subsequent inner range if the first section before the nested area is short. E.g. consider the following example:

<style>
.outer, .inner {
  scroll-snap-align: center;
}
.outer {
  padding: 50px;
}
</style>
<div class=outer>
  <div class=inner>
  </div>
</div>

It's probably not desirable to snap to the 50px of .outer before .inner starts as a different snap stop point. For comparison I've added this as a sixth option to the demo with an example demonstrating it at the top of the scroller.

  1. Join with preceding or subsequent ranges if short - This is basically the same as [css-scroll-snap-1] Improve or clarify nested snap behaviors #9187 (comment) except that the start of the first inner range will subsume the preceding outer range if that outer range (before the first inner range) is shorter than one scrollport.

@fantasai
Copy link
Collaborator

@flackr Wrt your last example, suppose .outer had scroll-snap-align: start. You don't want to lose that snap position as being a snap position. If both of these have start, you'll have at least two snap positions regardless of the size of the boxes, right?

@flackr
Copy link
Contributor Author

flackr commented Nov 28, 2023

If both of these have start, you'll have at least two snap positions regardless of the size of the boxes, right?

Wouldn't having two snap positions cause the gaps issue we're trying to avoid, or are gaps at the start / end special?

If we want to preserve I could see an option 7, added to the demo:
7. Join with preceding ranges if there are later overlapping ranges and the gap is short - This is equivalent to 5 except we only consider joining with preceding inner areas if there is a subsequent inner area. This allows the extra portion of the outer area to be snappable before the first nested inner area and after the last nested area but eliminates those snaps to gaps between inner areas. You'll notice at the start and end of the demo you can snap to the gaps between the first / last slides (inner areas) and the outer areas following / preceding it.

@argyleink
Copy link
Contributor

I've been trying out each of the examples over and over, really feeling them out for UX, and find option 7 to be the most natural feeling. Snapping on gaps that are smaller than the viewport isnt desirable and I like the conditional workaround Robert has laid out. Optimizes content visibility, is minimal and takes into account multiple viewport scenarios for proper responsive handling.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-scroll-snap-1] Improve or clarify nested snap behaviors, and agreed to the following:

  • RESOLVED: Specify option 7 as normative behavior
The full IRC log of that discussion <dael> flackr: Previously discussed this. Use case is when you have nested snap points want to respect inner ones as descrete stops that are separate.
<dael> flackr: Previously suggest avoiding inner, but that mad tiny gaps. fantasai had alternate that joined inner with outer that follows. Had some concerns about asymmetrical
<dael> flackr: Comprimise is when the gap between the inner snap areas is larger than viewport it's a discrete area you can snap to. When it's smaller it joins with previous inner snap area
<TabAtkins> I haven't been able to experiment with this myself, but Adam has and likes flackr's suggestion, and it does sound pretty reasonable.
<TabAtkins> So +1
<dael> flackr: Already have a condition for length between snap areas. And I think it satisfies both use cases. Made a demo page with all the various proposals. Adam tried it out and voted his support
<dael> flackr: Hoping we can go with this.
<dael> fantasai: Compare to scrollport or snapport?
<dael> flackr: Snapport
<dael> fantasai: Sounds like a good direction. Not sure how final we'll be until there's a prototype, but happy to go in this direction. Wondering if we want to spec it out now or spec as you could do this but it's technically undefined
<dael> flackr: I'm in favor of spec out and pushing for this to be the direction. We already have browser differences. It feels like spec will bring more attention
<astearns> +1 to specifying behavior
<dael> fantasai: Yeah, I think we put algo in spec. Just if we should require or if it's an example
<dael> florian: This will be fiddly, so we shouldld w write it with intent that it's normative so we get same behavior
<dael> fantasai: Okay
<dael> flackr: +1
<dael> astearns: I didn't read all the options
<dael> flackr: I did make prototype so we can play with it
<dael> astearns: Is it option 7 or option 7 plus other things?
<dael> flackr: Just option 7
<dael> florian: I wouldn't be surprised if we have to revisit once people play with it, but until then this is areasonable to start
<dael> astearns: Prop: Specify option 7 as normative behavior
<dael> astearns: Obj?
<dael> RESOLVED: Specify option 7 as normative behavior
<dael> astearns: Thanks flackr for doing the demo and going through options

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-scroll-snap-1 Current Work
Projects
Status: Unslotted
Development

No branches or pull requests

5 participants