Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/common-symbols-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Instructs the client router to skip view transition animations when the browser is already providing its own visual transition, such as a swipe gesture.
32 changes: 22 additions & 10 deletions packages/astro/src/transitions/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,13 @@ async function updateDOM(
moveToLocation(swapEvent.to, swapEvent.from, options, pageTitleForBrowserHistory, historyState);
triggerEvent(TRANSITION_AFTER_SWAP);

if (fallback === 'animate') {
if (!currentTransition.transitionSkipped && !swapEvent.signal.aborted) {
animate('new').finally(() => currentTransition.viewTransitionFinished!());
} else {
currentTransition.viewTransitionFinished!();
}
// Resolve the finished promise of the simulation's ViewTransition.
// For 'animate', wait for the new-page animation to complete first.
// For other fallback modes (e.g. 'swap'), resolve immediately — no animation needed.
if (fallback === 'animate' && !currentTransition.transitionSkipped && !swapEvent.signal.aborted) {
Comment on lines +324 to +327
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, you want to ensure that the simulated finished promise resolves.
The code for the simulated promises might need some update, anyhow.
For now this seems good enough.

animate('new').finally(() => currentTransition.viewTransitionFinished!());
} else {
currentTransition.viewTransitionFinished?.();
}
}

Expand All @@ -343,6 +344,7 @@ async function transition(
to: URL,
options: Options,
historyState?: State,
hasUAVisualTransition = false,
) {
// The most recent navigation always has precedence
// Yes, there can be several navigation instances as the user can click links
Expand Down Expand Up @@ -503,18 +505,21 @@ async function transition(
}

document.documentElement.setAttribute(DIRECTION_ATTR, prepEvent.direction);
if (supportsViewTransitions) {
if (supportsViewTransitions && !hasUAVisualTransition) {
// This automatically cancels any previous transition
// We also already took care that the earlier update callback got through
currentTransition.viewTransition = document.startViewTransition(
async () => await updateDOM(prepEvent, options, currentTransition, historyState),
);
} else {
// Simulation mode requires a bit more manual work
// Simulation mode requires a bit more manual work.
// Also used when PopStateEvent.hasUAVisualTransition indicates the browser already
// provided a visual transition (e.g. Safari swipe gesture) — in that case, fallback
// is "swap" to skip animations.
const updateDone = (async () => {
// Immediately paused to set up the ViewTransition object for Fallback mode
await Promise.resolve(); // hop through the micro task queue
await updateDOM(prepEvent, options, currentTransition, historyState, getFallback());
await updateDOM(prepEvent, options, currentTransition, historyState, hasUAVisualTransition ? 'swap' : getFallback());
return undefined;
})();

Expand Down Expand Up @@ -612,7 +617,14 @@ function onPopState(ev: PopStateEvent) {
const nextIndex = state.index;
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
currentHistoryIndex = nextIndex;
transition(direction, originalLocation, new URL(location.href), {}, state);
transition(
direction,
originalLocation,
new URL(location.href),
{},
state,
ev.hasUAVisualTransition,
);
}

const onScrollEnd = () => {
Expand Down
Loading