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

Rerender useSwipeTransition when direction changes #32379

Merged
merged 7 commits into from
Feb 20, 2025

Conversation

sebmarkbage
Copy link
Collaborator

@sebmarkbage sebmarkbage commented Feb 14, 2025

We can only render one direction at a time with View Transitions. When the direction changes we need to do another render in the new direction (returning previous or next).

To determine direction we store the position we started at and anything moving to a lower value (left/up) is "previous" direction (false) and anything else is "next" (true) direction.

For the very first render we won't know which direction you're going since you're still on the initial position. It's useful to start the render to allow the view transition to take control before anything shifts around so we start from the original position. This is not guaranteed though if the render suspends.

For now we start the first render by guessing the direction such as if we know that prev/next are the same as current. With the upcoming auto start mode we can guess more accurately there before we start. We can also add explicit APIs to startGesture but ideally it wouldn't matter. Ideally we could just start after the first change in direction from the starting point.

@react-sizebot
Copy link

react-sizebot commented Feb 14, 2025

Comparing: 4632e36...f17e3ba

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 515.71 kB 515.71 kB = 92.09 kB 92.09 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.40% 562.25 kB 564.52 kB +0.47% 100.08 kB 100.55 kB
facebook-www/ReactDOM-prod.classic.js = 636.70 kB 636.70 kB = 112.08 kB 112.08 kB
facebook-www/ReactDOM-prod.modern.js = 627.02 kB 627.02 kB = 110.49 kB 110.49 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-dom/cjs/react-dom-client.production.js +0.40% 562.25 kB 564.52 kB +0.47% 100.08 kB 100.55 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.production.js +0.39% 576.98 kB 579.25 kB +0.47% 103.65 kB 104.13 kB
oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js +0.37% 617.80 kB 620.06 kB +0.43% 108.78 kB 109.25 kB
oss-experimental/react-reconciler/cjs/react-reconciler.production.js +0.28% 431.79 kB 432.99 kB +0.35% 69.74 kB 69.98 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer.production.js +0.28% 36.48 kB 36.58 kB +0.34% 6.82 kB 6.85 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer.production.js +0.28% 36.51 kB 36.61 kB +0.32% 6.85 kB 6.88 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer.production.js +0.28% 36.51 kB 36.62 kB +0.34% 6.86 kB 6.88 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-persistent.production.js +0.28% 36.61 kB 36.71 kB +0.34% 6.84 kB 6.87 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-persistent.production.js +0.28% 36.64 kB 36.74 kB +0.32% 6.87 kB 6.90 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-persistent.production.js +0.28% 36.64 kB 36.74 kB +0.32% 6.88 kB 6.90 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer.development.js +0.26% 40.68 kB 40.78 kB +0.30% 7.42 kB 7.44 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer.development.js +0.26% 40.70 kB 40.81 kB +0.28% 7.45 kB 7.47 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer.development.js +0.26% 40.71 kB 40.81 kB +0.30% 7.45 kB 7.47 kB
oss-stable-semver/react-noop-renderer/cjs/react-noop-renderer-persistent.development.js +0.26% 40.82 kB 40.92 kB +0.30% 7.43 kB 7.46 kB
oss-stable/react-noop-renderer/cjs/react-noop-renderer-persistent.development.js +0.26% 40.84 kB 40.95 kB +0.28% 7.46 kB 7.48 kB
oss-experimental/react-noop-renderer/cjs/react-noop-renderer-persistent.development.js +0.26% 40.85 kB 40.95 kB +0.28% 7.47 kB 7.49 kB
oss-experimental/react-dom/cjs/react-dom-client.development.js +0.26% 1,035.99 kB 1,038.67 kB +0.32% 173.44 kB 174.00 kB
oss-experimental/react-dom/cjs/react-dom-profiling.development.js +0.25% 1,052.39 kB 1,055.06 kB +0.32% 176.29 kB 176.85 kB
oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js +0.25% 1,052.91 kB 1,055.58 kB +0.31% 177.18 kB 177.74 kB
oss-experimental/react-reconciler/cjs/react-reconciler.profiling.js +0.25% 484.75 kB 485.96 kB +0.33% 77.59 kB 77.85 kB

Generated by 🚫 dangerJS against 0a3c321

This prevents us from clearing the GestureLane while there are still
active gestures.
This clarifies that this is the refined internal type.
Currently they're the same thing but won't be forever.
@terryjim

This comment was marked as spam.

// has happened. However, if one direction has the same value as current we
// know that it's probably not that direction since it won't do anything anyway.
// TODO: Add an explicit option to provide this.
queue.initialDirection = previous === current;
Copy link
Contributor

Choose a reason for hiding this comment

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

Double checking my understanding: Seems that if this is wrong, the result is an immediate cancel, rerender with the correct direction, but that rerender would have no effect if its a single direction gesture.

For the stack navigation use case, if there's no page to go back to, previous and current would be the same, making the initial direction next. If you are in the middle of a stack and can go in both directions, then this is just a guess and we rely on the immediate cancel when moving to next.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yea. It's only relevant when you start the gesture on like touchdown and haven't moved yet. If it starts once you've moved like in the example in the stacked PRs, which starts on scroll, then you'd pass in the conceptual center offset and you'd already have moved in some direction before you start.

Starting early is mainly interesting to attach a ScrollTimeline before something moves so that if it does move the first frame doesn't lag. For example if you are mostly relying on native scrolling moving the content around but there's like one thing that can snap to either the top or inline in the content that you want to transition. By starting the transition on touchdown you can avoid lagging a frame.

However, this is a little fragile anyway because if something needs to suspend you might lag anyway and if you switch direction you have to be one frame behind anyway. So it's really only in these cases where this guess is useful and even then you could just pass it in explicitly if you wanted to since you'd know the only direction it can go. It's just a convenience for when it does work and playing with out of the box.

@@ -68,10 +68,12 @@ export default function Page({url, navigate}) {
activeGesture.current = null;
cancelGesture();
}
// Reset scroll
swipeRecognizer.current.scrollLeft = !show ? 0 : 10000;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is just an example cleanup to make the fixture reset between scrolls, right? Or am I missing something where setting scrollLeft would be relevant to direction rerenders?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yea, this is just to make sure it resets. In the follow up #32422 I'm expanding this a bit to add proper snapping and updating the canonical state if you go over 50%. However, I'm realizing now that I can just delete this now from #32422 because it'll either just have already snapped back by the time scrollend happens or it'll reset by the effect that happens after the new update commits.

@sebmarkbage sebmarkbage merged commit 88479c6 into facebook:main Feb 20, 2025
194 checks passed
github-actions bot pushed a commit that referenced this pull request Feb 20, 2025
We can only render one direction at a time with View Transitions. When
the direction changes we need to do another render in the new direction
(returning previous or next).

To determine direction we store the position we started at and anything
moving to a lower value (left/up) is "previous" direction (`false`) and
anything else is "next" (`true`) direction.

For the very first render we won't know which direction you're going
since you're still on the initial position. It's useful to start the
render to allow the view transition to take control before anything
shifts around so we start from the original position. This is not
guaranteed though if the render suspends.

For now we start the first render by guessing the direction such as if
we know that prev/next are the same as current. With the upcoming auto
start mode we can guess more accurately there before we start. We can
also add explicit APIs to `startGesture` but ideally it wouldn't matter.
Ideally we could just start after the first change in direction from the
starting point.

DiffTrain build for [88479c6](88479c6)
sebmarkbage added a commit that referenced this pull request Feb 21, 2025
Stacked on #32379

Track the range offsets along the timeline where previous/current/next
is. This can also be specified as an option. This lets you model more
than three states along a timeline by clamping them and then updating
the "current" as you go.

It also allows specifying the "current" offset as something different
than what it was when the gesture started such as if it has to start
after scroll has already happened (such as what happens if you listen to
the "scroll" event).
github-actions bot pushed a commit that referenced this pull request Feb 21, 2025
Stacked on #32379

Track the range offsets along the timeline where previous/current/next
is. This can also be specified as an option. This lets you model more
than three states along a timeline by clamping them and then updating
the "current" as you go.

It also allows specifying the "current" offset as something different
than what it was when the gesture started such as if it has to start
after scroll has already happened (such as what happens if you listen to
the "scroll" event).

DiffTrain build for [662957c](662957c)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants