w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.44k stars 657 forks source link

[css-position] Events for stickiness changes #1660

Open cvrebert opened 7 years ago

cvrebert commented 7 years ago

https://drafts.csswg.org/css-position/#sticky-pos

Since the suggestion in #1656 for a :stuck pseudo-class is not feasible, the next best alternative would be for browsers to fire events (e.g. stuck, unstuck), similar to those of CSS Transitions, whenever a position: sticky; element changes its stuckedness. Then the same styling abilities become possible at the cost of a teeny bit of JS and a teeny associated processing delay:

.some-class.is-stuck {
  /* cool stuck-specific styles here */
}
myStickyElement.addEventListener('stuck', function () {
  this.classList.add('is-stuck');
});
myStickyElement.addEventListener('unstuck', function () {
  this.classList.remove('is-stuck');
});

For robustness, the relevant event also needs to be fired when the element changes between the posititon: <anything but sticky> and (position:sticky-and-currently-stuck) states.

jhpratt commented 7 years ago

Agreed 100%. If a pseudoclass isn't feasible (and I agree it isn't for the reason Tab stated), there should still be some way of knowing.

FremyCompany commented 7 years ago

Couldn't you use an IntersectionObserver, narrowing your viewport to the area where the element will end up stuck (either top or bottom of the screen)?

atjn commented 7 years ago

I tried, and was not able to use IntersectionObserver to fix this. It doesn't seem to work with sticky elements when they are sticking to the top of the viewport. Not sure if this is a bug or not.

FremyCompany commented 7 years ago

Seems like a bug. Do you have a test case? That would be super valuable!

atjn commented 7 years ago

Unfortunately, i deleted the buggy code. So i tried to replicate the issue in a simpler setup, but then it actually worked! So maybe the bug is more specific than I thought, or maybe I simply made an error in my original code. I am sorry that I can't be of more help than that.

The good part about this is that we can use it. And I am actually using it on a new small project. It's a slightly more advanced implementation, but if anyone wants to use it in their projects, they are very welcome to take a look at it here: [broken link]. Note that the function only works when the page is viewed on a real/emulated mobile phone. Also note that i am still working on this project for a few days to come, so the site is subject to changes or breaking, but i will not be removing the IntersectionObserver feature.

FremyCompany commented 7 years ago

Great to see that it's actually working for you! FWIW I just tried this in Edge Insider Preview with position:sticky support and it worked like a charm.

FremyCompany commented 7 years ago

Agenda+ to close as NoChange if no one else has a strong opinion on this

SebastianZ commented 7 years ago

While the IntersectionObserver solution is working, it looks a bit like a hack in regard of the rootMargin value and is much more verbose than using events.

Sebastian

css-meeting-bot commented 6 years ago

The Working Group just discussed Events for stickiness changes, and agreed to the following resolutions:

The full IRC log of that discussion <dael> Topic: Events for stickiness changes
<dael> github: https://github.com/w3c/csswg-drafts/issues/1660
<dael> Rossen_: Summary, issue is someone asked if we can have an event to be able to know when the element goes into a stuck state and if it transitions to unstuck.
<dael> Rossen_: There were a few suggestions given, fremy pointed out if you use intersection observer everything works. After a little while people confirmed this is the case. They proposed to close no change.
<dael> Rossen_: I want to hear if you agree with this.
<dael> smfr: I agree
<dael> Rossen_: Objections to close, no change?
<dael> RESOLVED: Close issue 1660 no change.
SebastianZ commented 6 years ago

Unfortunately my comment was not addressed in the discussion. Could this be reconsidered?

Sebastian

atjn commented 6 years ago

I completely agree with @SebastianZ that using the IntersectionObserver is a hack, that would never work as well as a native solution.

And since this issue is very directly linked to any native sticky headline, i think something should be done to make a more native solution.

kizu commented 6 years ago

I would say that I'm also for the native stickiness events and against using hacks or any non-trivial code for that.

More than that, I also totally disagree with any arguments against the :stuck in CSS, but well, it seems that there is no way devs would persuade spec-writers to add it.

jhpratt commented 6 years ago

@kizu Regarding :stuck in CSS, how would you deal with infinite loops (as Tab pointed out)?

kizu commented 6 years ago

I'd start with whitelisting a certain set of properties that are guaranteed to be safe, that would allow developers to achieve most of the use cases for sticky elements (the changes in background, shadow etc.). CSS already has a similar mechanism for stuff like :first-letter, so I don't see a problem with that.

After that it would be possible to start thinking on more complex cases. But I'd say that instead of thinking about this particular pseudoclass and properties, it would be better to start thinking on how to fix the circularity/loops issue for the whole CSS. Its frustrating that so many must-needed for devs things like the :stuck, container queries etc. are blocked by this circularity issue. My proposal would be to make it so any declaration that would create a loop (and I'd say it should be possible to implement this) should be just discarded. So for the

.foo { toggle-states: 2; toggle-initial: 1; } /* makes it checked */
:checked { position: sticky; }
:stuck { toggle-states: none; }

here we apply everything in the specificity order and at the moment something would create a contradiction, it just wouldn't be applied. So, in that case, the toggle-states: none; from the :stuck just wouldn't apply.

But even if that would be hard to implement, we still could at first allow the safe cases that would allow people to do stuff much easier already. That would so much more helpful than just discarding ideas completely.


Upd: And another possible solution (if there are fears of using the selector like :stuck + :cheked, though we already have stuff like :visited that don't trigger in those cases): make it not :stuck, but ::stuck — essentially making it semantically a pseudoelement which would be “created” only when the element is stuck. With this, even more properties could be applied to the stuck element, as any its layout changes could be treated as the transformations are treated now.

FremyCompany commented 6 years ago

Yeah, I agree, @tabatkins, myself and so many others in the csswg really just haven't thought about this nearly enough, and that's why we have not found the obvious solution yet. All this time, the solution was to detect the problematic declarations and remove them. If we remove declarations one by one, we would eventually reach a stable layout in only O(nd²) relayouts, where n is the amount of css declarations in the document, and d the amount of elements in the DOM!

🙄

All the real-word usages of :stuck-like things I have seen in the wild (and Edge just implemented position:sticky so I guess I spent some more time looking at real-world usage than most) do change the layout of descendants of the stuck elements. There is no way we can prove these things wouldn't cause circularity.

RonnyO commented 6 years ago

Previously posted from the wrong account, sorry

I get that :stuck is problematic and it doesn't sound like things around selectors and infinite loops are about to change, so I'd like to add my support to what Chris has offered here - JS events would allow a very quick and simple workaround for devs, and hopefully won't be hard to implement.

Pulling the IntersectionObserver axe is a very complicated overkill, even if it's possible.

Roman stressed that lots of times we just want to alter shadows, color etc. - Mix the browser support of sticky and IntersectionObserver and the code required and you get too much trouble to even touch the standard, I know I'd revert to scroll listeners, and it's a shame. Thanks for considering this and reading through.

kizu commented 6 years ago

All the real-word usages of :stuck-like things I have seen in the wild (and Edge just implemented position:sticky so I guess I spent some more time looking at real-world usage than most) do change the layout of descendants of the stuck elements.

Wow, then we live in two entirely different worlds: I both saw and implemented myself in my practice a lot of sticky cases where the layout didn't change (or wasn't the most important part). The most common thing in a lot of projects I worked on was just adding a shadow and a solid background to make the sticky part visually distinctive from the content beneath.

And anyway, if we'd add just a limited amount of supported properties, then, of course, the design possibilities of that subset would be smaller than what would be possible with all the properties supported. But that does not means in any way that those that can be implemented would be useless. Both developers and designers would find ways to use it in the most efficient way, and if there'd be also proper events for hacking around the layout changes, then more complex cases would be possible too.

What I'm saying is that we don't need to have everything at once. I'm sure that most of the developers would be happy with just repaint-only styles available at the start instead of nothing.

jhpratt commented 6 years ago

A blog post with a live demo using IntersectionObserver was posted a couple days ago by @ebidel.

geoken commented 6 years ago

Going back to the other thread and Tab's comment 'wide' and 'tight' loops. it seems like the distinguishing factor is that because it's based on user interaction, the properties from :hover don't get applied until after the user interaction phase (where the page has been fully rendered). Wouldn't it be possible to just mimic this behaviour even if the specific pseudo selector wasn't dependent on user interaction?

So basically just wait until everything is rendered, then compute whatever is in the :stuck declaration? Obviously the endless loop would still be there, but would this make it function just like :hover loop?

tabatkins commented 6 years ago

While that is definitely better, we don't actually want to repeat the problems that :hover has. They're still bad, they just weren't killer, so we were able to live with them.

hunboy commented 6 years ago

I've cloned the most common usage of sticky header in my own way. I had to use 2 indicators with 2 Observer. Summary: I still vote to the :stuck pseudo, because this code is not my favorite, however works:

https://jsfiddle.net/utasir/92L8L85q/12/

If someone knows how to make it simpler, I'll appreciate that.

Actually my problem with my code if it is against the separation theory of style and html structure. So I'm still looking for a solution without extra html elements.

I've reduced the code to 1 indicator only, but now no reverse animation effect. I think this the max we can do for it with intersectionObserver. Other stuff is: position sticky element has reserved space int he flowed content, so resize made a layout jump. Workaround adding a margin-bottom to :stuck styled header.

https://jsfiddle.net/utasir/92L8L85q/17/

geoken commented 6 years ago

So to summarize;

1) There is an element and the browser will use some logic to set it to 1 of 2 states. 2) Letting the browser tell us when the state has changed or what it is, is unfeasible 3) Letting us create pseudo elements for no other purpose than essentially running hit detection code to try and infer the state of #1 is feasible?

Why is #2 undesirable vs. having us infer that same thing in an indirect way?

rubenlg commented 6 years ago

Sure you can use JS+DOM hacks (based on IntersectionObserver) to figure out when something started to stuck. The exact same argument could be given for position:sticky itself. Plenty of websites implement(ed) it with JS+DOM hacks. And yet, browsers decided to support position:sticky natively to make it more convenient, less hacky, and more robust.

Does anyone have any better argument against the proposal in this issue than "it can be done with JS+DOM hacks"? Or at least one that is consistent with the decision to implement position:sticky in the first place?

upsuper commented 6 years ago

There is a comprehensive explanation about why :stuck is not feasible in CSSWG's FAQ: https://wiki.csswg.org/faq#selectors-that-depend-on-layout

upsuper commented 6 years ago

It seems people have been aware of the FAQ item. Sorry for the noise. For the proposal in the issue itself, the event approach, I don't think that's infeasible.

upsuper commented 6 years ago

Thought about it a bit more, I guess even if we want something more convenient to get notified from stuck / unstuck, that's probably better also be an observer-like thing, rather than an event. I don't think we want to add more event into the layout pipeline, since event needs to be synchronous, which makes it easier to slow down pages, and it doesn't seem to me that the usecase here requires being synchronous. So using an asynchronous observer mechanism should be preferred over events.

upsuper commented 6 years ago

On the other hand, it seems adding one observer type for each thing we want to listen to feels heavier than adding an event, where we can just share the whole mechanism.

Maybe we should invent a new LayoutObserver-ish thing and design an API similar to the event listener, but make it async. That way we can add new observing type easily. Some of the existing events can probably also be added there for performance etc. (Maybe there has been some idea like this proposed in WICG?)

kizu commented 6 years ago

Re :stuck: btw, I don't find the wiki FAQ answer in any way sufficient. The toggle states part should be plain removed, as something that we would never have for similar reasons, and having something that would never matter as an arguments against something else is a circularity issue by itself, haha.

And I never heard any arguments why white-listing properties can't be used. There is a finite amount of properties that are used in most of the use cases for the :stuck, and almost none of those touch layout in any way. Shadows, background, color etc. — in most cases the design needs for :stuck touches on those.

Whitelisting some of the properties would be enough for start, and then we could start to think about the proper global solution for any circularity issues.

rubenlg commented 6 years ago

Thanks for the details @upsuper

What exactly happens when a layout event fires that slows down pages? Is it that the layout algorithm gets blocked until the event handler completes? I thought that event handlers had to run in tasks within the event loop, independently of the layout code of the browser. Or is this an implementation detail of some particular browser?

klimashkin commented 5 years ago

I don't really get how does intersectionObserver with rootMargin hack work here http://htx.antonjuulnaber.dk/afsaltningsanlaegget/ ?

How to read rootMargin: "0px 0px -99% 0px"? What does it mean? It doesn't work in my case.

klimashkin commented 5 years ago

Oh, I think I got it - minus means inwards, shrink bounding rectangle inwards..

But that doesn't work when sticky element is the very first inside it's root, and it has top: 0. Because it doesn't move anywhere on scroll, it becomes sticky immediately, but intention actually to add shadow only after user starts scrolling to show that sticky element has started hovering the following content.

klimashkin commented 5 years ago

Hack with adding sentinel elements to the top and the bottom of the scrolling root like this one has limitations. In certain cases I can't add extra elements inside container and when I can it become not possible to use some pseudo-classes like :first-child for a meaningful content.

In general this way adds cumbersome code and difficulties for other team member to reason about it. So I'm not sure why The Working Group decided that intersectionObserver hack is well enough. It is not in real life.

zipper commented 5 years ago

There is a problem with sentinel elements when we use position: sticky for sticking table parts (column, header, ...). In that case, there are actually several problems. Starting with we are unable to add <div> inside table <tr> element, or dealing with unknown column width. I'm unable to solve this without CSS :stuck or similar JS event or intersetion observer. Only solution for me in this case would be scroll events.

tayloraucoin commented 5 years ago

IntersectionObserver is also non-compatible with IE. So any implementation with it will not be backward compatible with the relic browser.

atjn commented 5 years ago

@tayloraucoin true, but any new spec also won't be compatible, and IE doesn't have support for the sticky position anyways.