summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/witty-readers-behave.md5
-rw-r--r--packages/astro/components/ViewTransitions.astro23
-rw-r--r--packages/astro/e2e/view-transitions.test.js22
3 files changed, 42 insertions, 8 deletions
diff --git a/.changeset/witty-readers-behave.md b/.changeset/witty-readers-behave.md
new file mode 100644
index 000000000..ec999f763
--- /dev/null
+++ b/.changeset/witty-readers-behave.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Bugfixes for back navigation in the view transition client-side router
diff --git a/packages/astro/components/ViewTransitions.astro b/packages/astro/components/ViewTransitions.astro
index 7b556c252..8fb506037 100644
--- a/packages/astro/components/ViewTransitions.astro
+++ b/packages/astro/components/ViewTransitions.astro
@@ -262,6 +262,9 @@ const { fallback = 'animate' } = Astro.props as Props;
return;
}
+ // Now we are sure that we will push state, and it is time to create a state if it is still missing.
+ !state && history.replaceState({ index: currentHistoryIndex, scrollY }, '');
+
document.documentElement.dataset.astroTransition = dir;
if (supportsViewTransitions) {
finished = document.startViewTransition(() => updateDOM(doc, loc, state)).finished;
@@ -335,28 +338,28 @@ const { fallback = 'animate' } = Astro.props as Props;
// But we want to handle it like any other same page navigation
// So we scroll to the top of the page but do not start page transitions
ev.preventDefault();
- persistState({ ...history.state, scrollY });
- scrollTo({ left: 0, top: 0, behavior: 'instant' });
+ // push state on the first navigation but not if we were here already
if (location.hash) {
- // last target was different
+ history.replaceState({ index: currentHistoryIndex, scrollY: -(scrollY + 1) }, '');
const newState: State = { index: ++currentHistoryIndex, scrollY: 0 };
history.pushState(newState, '', link.href);
}
+ scrollTo({ left: 0, top: 0, behavior: 'instant' });
return;
}
}
// these are the cases we will handle: same origin, different page
ev.preventDefault();
- persistState({ index: currentHistoryIndex, scrollY });
navigate('forward', new URL(link.href));
});
addEventListener('popstate', (ev) => {
if (!transitionEnabledOnThisPage() && ev.state) {
- // The current page doesn't haven't View Transitions,
- // respect that with a full page reload
- // -- but only for transition managed by us (ev.state is set)
+ // The current page doesn't have View Transitions enabled
+ // but the page we navigate to does (because it set the state).
+ // Do a full page refresh to reload the client-side router from the new page.
+ // Scroll restauration will then happen during the reload when the router's code is re-executed
history.scrollRestoration && (history.scrollRestoration = 'manual');
location.reload();
return;
@@ -383,7 +386,11 @@ const { fallback = 'animate' } = Astro.props as Props;
const nextIndex = state.index;
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
currentHistoryIndex = nextIndex;
- navigate(direction, new URL(location.href), state);
+ if (state.scrollY < 0) {
+ scrollTo(0, -(state.scrollY + 1));
+ } else {
+ navigate(direction, new URL(location.href), state);
+ }
});
['mouseenter', 'touchstart', 'focus'].forEach((evName) => {
diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js
index 34f1a4e02..80a180608 100644
--- a/packages/astro/e2e/view-transitions.test.js
+++ b/packages/astro/e2e/view-transitions.test.js
@@ -282,6 +282,28 @@ test.describe('View Transitions', () => {
await expect(locator).toBeInViewport();
});
+ test('Scroll position restored when transitioning back to fragment', async ({ page, astro }) => {
+ // Go to the long page
+ await page.goto(astro.resolveUrl('/long-page'));
+ let locator = page.locator('#longpage');
+ await expect(locator).toBeInViewport();
+
+ // Scroll down to middle fragment
+ await page.click('#click-scroll-down');
+ locator = page.locator('#click-one-again');
+ await expect(locator).toBeInViewport();
+
+ // Scroll up to top fragment
+ await page.click('#click-one-again');
+ locator = page.locator('#one');
+ await expect(locator).toHaveText('Page 1');
+
+ // Back to middle of the page
+ await page.goBack();
+ locator = page.locator('#click-one-again');
+ await expect(locator).toBeInViewport();
+ });
+
test('Scroll position restored on forward button', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/one'));