"Sub-page" `Sticky` Components
See original GitHub issueThis ticket is specifically for discussion of supporting <Sticky>
elements attached to in-document scrollable elements.
As an example of such a case:
<div className="scrollable"><StickyContainer><Sticky>...</Sticky></StickyContainer></div>
There are two ways to interpret the expectation in this case:
- When
<Sticky>
is scrolled to the top of thescrollable
element, it sticks to the top of the window. - When
<Sticky>
is scrolled to the top of thescrollable
element, it sticks to the top of the element.
Let’s leave the first interpretation aside for now, since it’s unlikely to be a useful one. This leaves three cases for us to consider.
- Sticking to a scrollable element outside the
StickyContainer
.- This is most similar to the existing behavior.
- Sticking to a scrollable element inside the
StickyContainer
. - Sticking to a scrollable
StickyContainer
.
Catch 1 (demonstrated here) – scroll
events don’t propagate. This implies that the only way to intercept contained scroll events is to subscribe to the scrollable element, which further implies that automagically subscribing to the correct scrollable (non-StickyContainer
) element is either expensive, unreliable, or impossible.
Possible Solution 1.A – we can solve this problem by requiring the user to supply a prop identifying the scrollable element to watch, but sharp edge cases abound.
Possible Solution 1.B – we can restrict scrollable elements we’ll respect to StickyContainer
instances (or possibly a ScrollableStickyContainer
subclass).
Catch 2 – position: fixed
(which is used for all of our low-jank positioning) cannot be contained by scrollable elements. This would require us to implement a separate positioning computation for non-window containers.
Possible Solution 2.A – we can require the scrollable element to have position
, and use a position: absolute
based position calculation.
Possible Solution 2.B – we can recalculate a screen-based positioning based on the watched element when appropriate.
Catch 3 – StickyContainer
s are designed to be nested, so that more deeply nested Sticky
elements will stick to one another (a la the timeline example). This is accomplished by StickyContainer
s subscribing to data from their ancestors about other sticky elements. StickyContainer
s being used within a scrollable element context MUST NOT inherit that data.
Possible Solution 3.A – we use the presence of the prop identifying the scrollable element as a sentinel for when we should avoid subscribing to ancestor data.
Possible Solution 3.B – we use a ScrollableStickyContainer
subclass as the data subscription boundary.
This may not represent a complete list of the known concerns or possible solutions, but it seems like one potential solution for solving the general “sub-page” problem might look like this:
<StickyContainer>
...
<Sticky key="first">
I listen to `window`
I stick to `window`
</Sticky>
...
<StickyContainer>
...
<Sticky key="second">
I listen to `window`
I stick to "first"
</Sticky>
...
<ScrollableStickyContainer key="container">
<Sticky key="inner">
I listen to "container"
I stick to "container"
</Sticky>
...
<StickyContainer>
<Sticky key="fourth">
I listen to "container"
I stick to "inner"
</Sticky>
</StickyContainer>
</ScrollableStickyContainer>
</StickyContainer>
</StickyContainer>
To those who are interested in this feature, does this seem like an acceptable solution?
Issue Analytics
- State:
- Created 7 years ago
- Reactions:12
- Comments:8 (3 by maintainers)
Top GitHub Comments
I don’t view 1.A and 1.B as wildly incompatible, but I feel like 1.B is easier to explain and understand. The biggest difference between the options is flexibility, but if option 1.B were acceptably flexible, it’s probably preferable.
As far as Catch 2 goes, 2.A is simpler to implement and understand, but again, restricts the styles that can be applied to the scrollable container (specifically, not
position: static
). 2.B requires us to calculate position based on container scroll and all parent container scrolls and window scrolls … which isn’t terribly fun.3.A really only works with 1.A, and 3.B really only works with 1.B.
This is far from a polished sample, but I spent a little time this evening hacking to investigate the performance characteristics. This implements a solution around {1,2,3}.B without much error bounds checking, and without completing the “stacking” of Sticky components.
It also differentiates between
Sticky
andInlineSticky
components, which is more about being lazy than an expected implementation.The biggest takeaway for me is that the implementation isn’t as janky as I’d feared it might be, which is a nice perk. I will likely spend a little more time hacking on this example as time allows…
http://codepen.io/anon/pen/rLVOoE
A little note about the “fixed” issue, a little while we rolled out a simpler version of Sticky and by accident I ran into the fact that you can nest fixed elements in relation to each other not the window with just CSS, when you apply any 3rd transformation on the parent , it’s children are now on a coordinate system based on the parent, so the window width now becomes the parent width, I looked this up to make sure it’s not a bug and turns out the browsers implemented it this way on purpose. So for example:
Window with a small chat box container inside of it, this chat box is position fixed to the bottom right, now let’s say you want to have a navbar fixed to the top of that chat box container, if you do top:0, width 100% , it as expected will be at the top of the window nowhere near the chat box, however if you apply for example a translateY(0) on the chatbox, now the chat navbar instead will assume the “window” is the width of the chatbox , and top:0 now becomes the top of the chatbox. Just something cool I ran into by accident and have been using it situationally since .