The other day I stumbled upon a peculiar problem with scroll persistence in a seemingly straightforward layout. Usually, browsers prevent layout shifts if changes are happening off-screen, and yet that wasn’t the case.
The Layout
The layout in question can be distilled into the following:
On the left, there is a component with a static header and a sticky button. Sticky elements can only stick within a container, so this component is stretched all the way down. On the right, there is long content with a dynamically updated element on top. This dynamic element changes its height unpredictably. The layout itself is a pretty straightforward grid.
The Problem
The problem is that if we scroll the page to the bottom, the content will still jump on every height change, even though browsers are usually smart enough to adjust the scroll position so that the perceived scroll position remains intact.
As a short investigation showed, the issue is that the left and right columns compete for these scroll adjustments, and browsers prioritize the elements according to their position in the DOM. The left column comes first, and therefore browsers adjust the scroll position so that we see the same part of the sticky element container. This is not practical, as we will see the sticky element regardless, and we actually want to lock the content on the right.
Moving Elements in the DOM
It can be fixed by moving the right column before the left column in the DOM and then reordering them back in place via CSS. However, this approach will break some accessibility features, most notably the button tab indices. This can be seen in the following example:
If we try to tab-navigate between buttons, the right button will come first, which is undesirable.
Removing the Competition
After some time, I realized that it was possible to remove the competition altogether. We can move the sticky element to the top and use the grid cell as its container.
This approach is a bit more complex than the initial one, but now we only have the right column and the scroll persistence works flawlessly without sacrificing accessibility.
Conclusion
Browsers are capable of preventing layout shifts on dynamic content updates. However, sometimes multiple elements compete against each other for being able to stay in place, which may lead to confusing behavior.
The ideal solution is to simply remove the competition. If that’s impossible, we can move the element that we need to lock in place so that it comes before the other elements in the DOM.
Top comments (0)