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] Proposing :snapped: exposing private snapped item browser state for developers and designers #6985

Open
argyleink opened this issue Jan 25, 2022 · 11 comments

Comments

@argyleink
Copy link
Contributor

📄 Explainer

Currently, IntersectionObserver and or hacks (like making gap 100vmin in a frame so JS can check to see which element is still within the viewport..) are ways to try and derive this, but it's not usually performant, timed properly or accurate. This proposal seeks to remove the hacks and inaccurate algorithms. The browser has an item target it snapped to, developers should have access to that element.


For example, it's nice to outline the snapped element to help reinforce the state of the scroller:

:snapped-inline {
  outline: 3px solid hotpink;
}

or to lift a snapped header when snapped:

section:snapped-y > header {
  box-shadow: 0 .5em 1em .5em lch(5% 5% 200);
}

There's a lot more use cases and details about the proposal (cyclical resolution, full set of pseudo selectors, etc) in the Explainer

Related: #5979

Would love to present the details of the explainer to the CSSWG for feedback 🙂

@frivoal
Copy link
Collaborator

frivoal commented Jan 31, 2022

I think there's no question that it'd be useful. The question, to me, is all about feasibility.

From the explainer:

We are OK letting :snapped cycle like :hover, as long as it's only once per lifecycle update. We believe developers will be responsible with this, as they have with :hover.

What does that mean exactly? I suppose (especially given that @tabatkins is a co-author) that you're aware of https://wiki.csswg.org/faq#selectors-that-depend-on-layout

The :hover loop goes through hit-testing, and thus skipping hit-testing on relayout and waiting for most movement solves it. In particular, you don't particularly have to care that the loop is or isn't caused by using :hover itself. Just skip hit-testing in general when doing relayout, and you're good. Actual implementations may be more subtle, but conceptually that's sufficient, and not a source of meaningful breakage.

But snapping loops don't go through hit testing, so the thing you'd have to skip would be something else.

I presume you mean that you wouldn't resnap based on relayout, but that seems bad, at least in general. It would solve the problem, but cause other breakage.

.container { scroll-snap-type: block mandatory; }
:snapped { scroll-snap-align: none; }

(Note: I think scrolling isn't clearly defined either as part of layout, nor as not being part of layout. Even if it isn't, the same problem with actual relayout can be created by adding something like :snapped { margin-top: 1px; } to the previous example.)

Without mitigation, this would generate a weird oscillating layout, when you're forced snap to something, thus making it not a snap-point, thus jump to another one (because in mandatory snapping, you always have to snap to something), and repeat.

This loop could be interrupted by just not doing snapping on re-layout and only making it kick in if there's some user interaction, but that's very explicitly against the spec (see https://drafts.csswg.org/css-scroll-snap/#re-snap), and for good reasons.

So we'd need a narrower condition for breaking the loop at one iteration. You seem to think it's doable, but it's not obvious to me how. Could you provide more details? (Also, does your answer also work for a hypothetical :stuck pseudo relating to position: sticky, as discussed in the FAQ?)

@argyleink
Copy link
Contributor Author

thx! 🙂

So we'd need a narrower condition for breaking the loop at one iteration.

I think #5979 (comment) is helpful for this. I could add details about the explained tight and loose cycles to the explainer? how it's not a tight cycle to maybe regret, but a loose one following the reputable :hover loop strategy?

Also, does your answer also work for a hypothetical :stuck pseudo relating to position: sticky, as discussed in the FAQ?

We haven't discussed :stuck for a bit, but I'd love for it to follow :snapped footsteps 🤓

@smfr
Copy link
Contributor

smfr commented Feb 23, 2022

Related: #156

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed proposing :snapped, and agreed to the following:

  • RESOLVED: Start Scroll Snap 2 ED, with :snapped and snapping event model, with Adam Argyle as additional editor
The full IRC log of that discussion <TabAtkins> Topic: proposing :snapped
<TabAtkins> GitHub: https://github.com//issues/6985
<TabAtkins> argyle: Explainer in the issue
<TabAtkins> argyle: Hacks and intense work with IntersectionObserver to figur eout what element is snapped to.
<TabAtkins> argyle: Especially with snapping being a 2-d thing, where it can be snapped to two elements
<TabAtkins> argyle: And multiple elements could overlap, any of which could be the snappee
<bkardell_> present +
<TabAtkins> argyle: This proposal attempts to deal with layout-dependency issues
<TabAtkins> argyle: And lets the user know if there is a snapped element
<TabAtkins> argyle: Biggest gotcha is that if you have a scroller that's shrinkwrapped so items don't have padding, not every item might be snappable
<TabAtkins> argyle: So this isn't necessarily useful for selection
<TabAtkins> argyle: Example, header tabs in a scroller, and whichever is snapped to determines the contents of the tab
<TabAtkins> argyle: Right now people go to JS that does synthetic scrolling and snapping to do things like this
<TabAtkins> argyle: So this brings the functionality into CSS.
<TabAtkins> argyle: The explainer addresses the previous issue; using scroll-padding can enable all elements to be snapped
<TabAtkins> argyle: Second biggest issue is avoiding the :hover issue, where something can "jiggle" matching
<TabAtkins> argyle: CSS has "loose" and "tight" loops, whether it goes out to user interaction or cycles within the style engine
<TabAtkins> argyle: The epxlainer defines how to have :snapped be a loose loop like :hover, once per frame
<TabAtkins> argyle: And it would only be able to infinite-loop during user interaction; it wouldn't be able to jiggle on its own while the page is at rest
<florian> q+
<TabAtkins> argyle: So devs *could* apply styles to make something snap/unsnap rapidly while the user is interacting, but that's it, and it shoudl hopefully be very obvious
<flackr> q+
<smfr> q+
<TabAtkins> argyle: So our hope is that we can take the good success of :hover and apply it over to :snapped, letting users do snapping control without JS.
<TabAtkins> argyle: Maybe flackr or TabAtkins can talk about the loop prevention.
<astearns> ack florian
<astearns> q+
<TabAtkins> florian: I'm convinced there's a giant use-case. Not too worried about the first problem, about things not being reachable by snapping.
<TabAtkins> florian: Am bothered by the second situation. In proximity it's fine, but in mandatory i'm a little concerned...
<TabAtkins> florian: I've not yet convinced myself you can apply the :hover workaround while maintaining the invariant of mandatory snapping.
<TabAtkins> florian: That is, that while restyling you must resnap.
<TabAtkins> florian: You can probably limit it to once per frame, but you'd have jiggling at rest, or you'd violate the mandatory requirement and not be snapped at some times.
<TabAtkins> florian: So my quesiton is if we *need* to solve the problem this way? Would it be simpler to have snapping events that fire when snapping to something?
<TabAtkins> florian: You'd still have loops if your JS did silly things, but that's much wider.
<fantasai> +1 to JS events
<argyle> https://github.com/argyleink/ScrollSnapExplainers
<astearns> q- (was going to ask about events)
<TabAtkins> argyle: Some JS events talked about in the explainer; those are part of the proposal. snapped, spanchange, and snapTo() function
<astearns> q-
<TabAtkins> argyle: So yeah, JS definitely important.
<fantasai> That issue's been open for awhile and needs help https://github.com//issues/156
<TabAtkins> s/snapped, spanchange/snapChanged, snapChanging events/
<TabAtkins> argyle: "changing" triggered when you've passed a threshold to start switching to a new snap target
<TabAtkins> florian: My question is if it's *sufficient* to have events, rather than just the pseudo?
<TabAtkins> argyle: There's a lot of cases where the CSS visual feedback is justa s important to JS knowing the snapped items.
<TabAtkins> argyle: I've got an example that adds a huge gap for one frame to detect the snappee... the timing isn't right. We're trying to give CSS visibility into the inner state.
<TabAtkins> argyle: I don't think we could use just a JS solution. But I do think there's a competetive proposal in Style Queries.
<TabAtkins> argyle: So style queries might bea ble to pacify this use-case.
<TabAtkins> (I don't think they could; snapping isn't exposed in CSS state except via side-channels of properties being applied.)
<astearns> ack flackr
<florian> q+ to comment on the limits of similarity with hover
<TabAtkins> flackr: I think we have similar problems with :hover, and :hover state doesn't update until you move your cursor, we could do a similar thing for :snapped, so even if snap changes we wouldn't update the :snapped until user input occurs
<TabAtkins> flackr: We think in most cases devs wouldn't create these cycles, because they usually don't with :hover
<TabAtkins> flackr: We'll have to make sure we have the same snapped element, that does get tricky.
<TabAtkins> flackr: I think there are some cases that - say you have snapping in both directions, you probably want the element snapped in both directions to get the pseudo rather than first in dom order, there's some tricky cases we'll need ot make sure of
<astearns> ack smfr
<TabAtkins> smfr: Your primary reasons to want sounded like something appropriate for script - knowing what element was selected.
<TabAtkins> smfr: Wonder if you considered exposing on the scroller itself a list of snapped elements.
<TabAtkins> smfr: Like .scrollLeft, etc, could have .snappedElements
<TabAtkins> smfr: Woudl change on scroll events
<TabAtkins> argyle: Like that idea, kinda goes with the scrollTo() options for snapping
<TabAtkins> argyle: JS should be able to get the list of snappable children. Sounds like a good idea
<astearns> ack florian
<Zakim> florian, you wanted to comment on the limits of similarity with hover
<TabAtkins> florian: There is indeed a similar to :hover when we talka bout proximity.
<TabAtkins> florian: But there is not in proximity a strong expectation you're always snapped to something. If we break a loop, that's fine.
<TabAtkins> florian: With mandatory you're required to always be snapped to something. Always - authors can depend on this.
<TabAtkins> florian: There's never an unsnapped state.
<TabAtkins> q+ to respond to florian
<flackr> q+ to respond to florian
<TabAtkins> florian: Not just scrolling can trigger this, restyling or animation can change the position of the snapped element.
<TabAtkins> florian: If we have a clean way to make this continue being true, great
<TabAtkins> florian: But I don't see a sufficient explanation in the explainer to be clear on this.
<TabAtkins> florian: If it's impossible, oh well, we'll figure it out. Worry is that it's possible, just very hard to do and maybe non-interoperable so the invariant is broken in different ways
<argyle> looks like as part of the event data on the js events snapChanged and snapChanging, i've added snappedList and snappedTargetsList which provide arrays of snap children on axes https://github.com/argyleink/ScrollSnapExplainers/tree/main/js-snapChanged#:~:text=SnapEvent.snappedList. i do still think there's room for a generic api into this tho!
<astearns> ack flackr
<Zakim> flackr, you wanted to react to florian
<TabAtkins> flackr: I think the first proposal I presented does let you maintain mandatory snapping
<TabAtkins> flackr: Your snapped state changes the style, which might cause you to snap to another target, but your :snapped won't reflect that until the next frame
<fantasai> TabAtkins: Underneath the cover, smandatory snapping invariant is maintained, but not seen on every frame
<astearns> ack fantasai
<Zakim> fantasai, you wanted to point at issue #156
<astearns> ack TabAtkins
<Zakim> TabAtkins, you wanted to respond to florian
<astearns> ack flackr
<Zakim> flackr, you wanted to respond to florian
<fantasai> https://github.com//issues/156
<TabAtkins> fantasai: Wanted to point people to discussion about even tmodel, been open for a long time
<TabAtkins> fantasai: I think this pseudo-class would be great, and event model should be avaible at the same time or before the pseudo.
<TabAtkins> astearns: Agree, not against the pseudo, but think the events are the more important thing
<argyle> event `snapChanging` https://github.com/argyleink/ScrollSnapExplainers/tree/main/js-snapChanging
<TabAtkins> astearns: Could even wait on the pseudo until we notice the events are often being used to add a class
<argyle> event `snapChanged` https://github.com/argyleink/ScrollSnapExplainers/tree/main/js-snapChanged
<TabAtkins> fantasai: I'd also like to see the situation with mandatory snapping and overlarge elements fixed first; it makes content inaccessible if done wrong.
<TabAtkins> astearns: Any other comments? Concerns?
<TabAtkins> astearns: So it sounds like you have a lot of feedback.
<TabAtkins> argyle: The events should be well-defined, but I haven't taken up WG time yet. ARe they appropriate here?
<TabAtkins> fantasai: Yes, they'll be specced in this group.
<TabAtkins> fantasai: Probably in Scroll Snap 2.
<fantasai> TabAtkins: Sounded like ppl interested in adding to Scroll Snap 2, should we resolve to start that spec with this in it?
<TabAtkins> astearns: Any objections?
<TabAtkins> fantasai: We shoudl resolve to start an ED and include both :snapped and the events
<TabAtkins> florian: If there's a different event model drafted in an issue, shoudl probably decide
<TabAtkins> fantasai: Can put both in for now and figure it out
<TabAtkins> astearns: So proposal is to have Scroll Snap 2 ED with pseudo-class and event models, with Adam as editor?
<TabAtkins> florian: think we should have both so it doesn't get frozen in without discussion
<TabAtkins> argyle: I'll be checking soon how the two proposals compare
<TabAtkins> astearns: Objections? Anyone need more time?
<TabAtkins> RESOLVED: Start Scroll Snap 2 ED, with :snapped and snapping event model, with Adam Argyle as additional editor
@flackr
Copy link
Contributor

flackr commented Feb 23, 2022

Another complication to figure out if we can resolve is getting the initial frame correct. Before the browser has completed style and layout it might now know which element is snapped, so in order for the element to get the correct :snapped style on the initial frame (assume it is initially snapped) we may have to repeat style and layout.

@fvpDev
Copy link

fvpDev commented Sep 23, 2022

Hey y'all! As a young web dev I am super excited for the implementation of this. How's it coming along? Also, thank you for being awesome!

@argyleink
Copy link
Contributor Author

It's still within folks' viewport, and is relatively high in priority of lists. Thanks for checkin in 🙂

flackr pushed a commit to flackr/csswg-drafts that referenced this issue May 12, 2023
This was marked as UD, however, all of the features have been discussed.
Previous resolutions:
scroll-start: w3c#6986 (comment)
:snapped, snapChanged and snapChanging and creation of css-scroll-snap-2 ED: w3c#6985 (comment)
svgeesus pushed a commit that referenced this issue May 12, 2023
This was marked as UD, however, all of the features have been discussed.
Previous resolutions:
scroll-start: #6986 (comment)
:snapped, snapChanged and snapChanging and creation of css-scroll-snap-2 ED: #6985 (comment)
@tabatkins
Copy link
Member

Agenda+ to give a heads up about the scrollsnap* events - we've discussed them a few times during the call already (issues #10173, #9697), and consider them pretty stable and ready to go now. Just want to put out feelers to see if anyone has any objections to what's in the draft.

@css-meeting-bot
Copy link
Member

The CSS Working Group just discussed [css-scroll-snap] Proposing `:snapped`: exposing private snapped item browser state for developers and designers, and agreed to the following:

  • RESOLVED: Publish css-scroll-snap-2 as FPWD minus scroll-start property
The full IRC log of that discussion <emeyer> TabAtkins: Want to give a heads up about scroll-snap events
<emeyer> …These are the replacement for the topic originally raised in this issue
<emeyer> …They fire when a thing snaps or is snapping, so you can track them that way
<emeyer> …So, please review the events in the issue so we can get on track for forward movement
<astearns> ack fantasai
<emeyer> fantasai: This doesn't have FPWD, so you're putting a call for FPWD?
<emeyer> TabAtkins: Yeah, let's do it
<emeyer> …This is something one of our editors has been working on and we think it needs more visibility in the group
<emeyer> fantasai: The rest of the stuff in this publication is also ready to go?
<emeyer> …Somewhat
<emeyer> DavidA: Two parts of this spec, part has to do with snap events and part with snap targets
<emeyer> …This is only about the events part; the other part isn't as ready
<emeyer> flackr: I also see styyling in the scroll snap 2 spec
<emeyer> DavidA: Only the events are ready to move forward at the moment
<emeyer> fantasai: What do you mean by that?
<emeyer> DavidA: I think they can be implemented, we have one in Chrome we'd like to ship but we want more eyes on it first
<emeyer> fantasai: What's wrong with the rest of the spec?
<emeyer> flackr: We don't have an implementation of snapped, which is morst likely to change
<emeyer> …There was a request to change it, so we could mark that as at risk
<emeyer> astearns: I don't think we have to At Risk in FPWD
<emeyer> fantasai: Then mark it as an issue that it might change
<emeyer> …For scroll-start, what use cases does it solve that aren't solved by content alignment?
<emeyer> TabAtkins: We're not asking for an overview of the entire draft, so please defer topic discussion about things outside events unless you spot something really bad
<emeyer> fantasai: If you think you're going to drop parts of it, drop them before FPWD?
<emeyer> flackr: I think it would be fine to drop
<emeyer> (missed)
<fantasai> PROPOSED: Publish css-scroll-snap-2 as FPWD minus scroll-start property
<emeyer> astearns: Objections?
<emeyer> (silence)
<emeyer> RESOLVED: Publish css-scroll-snap-2 as FPWD minus scroll-start property
DavMila pushed a commit to DavMila/csswg-drafts that referenced this issue Jun 26, 2024
Towards resolving [6985](w3c#6985 (comment))
this patch removes scroll-start from the spec and notes that :snapped
is being replaced.
tabatkins pushed a commit that referenced this issue Jul 10, 2024
* [css-scroll-snap-2] Drop scroll-start and add issue to :snapped

Towards resolving [6985](#6985 (comment))
this patch removes scroll-start from the spec and notes that :snapped
is being replaced.

* Remove "shorthand" for scroll-start-target

* [css-scroll-snap-2] Put carousel example back into scroll-start spec

---------

Co-authored-by: David Awogbemila <awogbemila@chromium.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment