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

Fix sticky pager jumps #1825

Merged
merged 6 commits into from
Nov 6, 2023
Merged

Fix sticky pager jumps #1825

merged 6 commits into from
Nov 6, 2023

Conversation

gaearon
Copy link
Collaborator

@gaearon gaearon commented Nov 6, 2023

The recently introduced pager component renders three things:

  • The pager "header" (e.g. list info)
  • The "tab bar" below it (e.g. Posts | About)
  • The "content" below both of those things

However, the loading sequence appears to be extremely janky:

before.mov

This happens for three reasons:

  1. The header changes size as we load data. So if we rendered anything below it (tab bar and/or content) while it was still a glimmer, we're gonna have to shift those things down when it fully loads. This feels jarring visually.

  2. Our logic to position the content (so that it may fluidly scroll with the tabs staying sticky) relies on knowing both the header and the tab bar height. However, initially we don't know them at all, the header size changes when it turns from glimmer into content, and also React Native onLayout is always async so we can't rely on that being up-to-date anyway.

  3. As if that weren't enough, it seems like changing a scroll view padding on iOS on the fly can mess up the scroll position.

The Fix

First, let's enforce that content is revealed top down. First, we reveal the header glimmer. We wait for it to "settle" which depends on loading data. Since we can't use <Suspense> here (which is the canonical React solution to this problem), we track this manually. Until isHeaderReady={true} is passed, we don't try showing anything below to the user at all.

In layout measurement code, I separated measuring "just" the header from measuring the tab bar. Previously we were measuring the header including the tab bar plus the tab bar separately. This made the calculation a bit confusing. Now we have two state variables that we can check for being zeroes. If either of them is zero, we're not ready to render the content.

As an optimization, we do render the tab bar a bit early with opacity: 0. So that we get its layout information early. We get away with it because its height is not going to change, unlike with the header.

Finally, when we know the header has settled (isHeaderReady={true}) and we have both headerOnlyHeight and tabBarHeight calculated, we're going to render the content (this will include the content's glimmer if it hasn't loaded yet).

Test Plan

Feeds:

after_feeds_feeds.mov

Lists:

after_lists.mov

Mod Lists:

after_mod_lists.mov

Renaming a list:

after_renaming.mov

There is a little jump when renaming that didn't get captured on the video. However I think we can live with it.

Also went through this on Android and web.

@gaearon gaearon requested a review from pfrazee November 6, 2023 22:25
Copy link
Collaborator

@pfrazee pfrazee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic work as always

@gaearon gaearon merged commit d715246 into main Nov 6, 2023
4 checks passed
@gaearon gaearon deleted the fix-pager-jumps branch November 6, 2023 22:30
@gaearon
Copy link
Collaborator Author

gaearon commented Nov 6, 2023

"hope it works"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants