summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Martin Trapp <94928215+martrapp@users.noreply.github.com> 2023-09-12 17:37:28 +0200
committerGravatar GitHub <noreply@github.com> 2023-09-12 11:37:28 -0400
commit2db9762eb06d8a95021556c64e0cbb56c61352d5 (patch)
tree0c0f345f80fd1d8af8c3306e7c01a25e0e28a9d9
parent4105491732440e3fd3b984b780e338e0753e3787 (diff)
downloadastro-2db9762eb06d8a95021556c64e0cbb56c61352d5.tar.gz
astro-2db9762eb06d8a95021556c64e0cbb56c61352d5.tar.zst
astro-2db9762eb06d8a95021556c64e0cbb56c61352d5.zip
Restore horizontal scroll position on history navigation (view transitions) (#8505)
-rw-r--r--.changeset/polite-ravens-serve.md5
-rw-r--r--packages/astro/components/ViewTransitions.astro38
-rw-r--r--packages/astro/e2e/fixtures/view-transitions/src/pages/wide-page.astro12
-rw-r--r--packages/astro/e2e/view-transitions.test.js30
4 files changed, 74 insertions, 11 deletions
diff --git a/.changeset/polite-ravens-serve.md b/.changeset/polite-ravens-serve.md
new file mode 100644
index 000000000..0f5d28c92
--- /dev/null
+++ b/.changeset/polite-ravens-serve.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Restore horizontal scroll position on history navigation (view transitions)
diff --git a/packages/astro/components/ViewTransitions.astro b/packages/astro/components/ViewTransitions.astro
index 4d615188d..6ae8f3592 100644
--- a/packages/astro/components/ViewTransitions.astro
+++ b/packages/astro/components/ViewTransitions.astro
@@ -15,6 +15,7 @@ const { fallback = 'animate' } = Astro.props as Props;
type Direction = 'forward' | 'back';
type State = {
index: number;
+ scrollX: number;
scrollY: number;
};
type Events = 'astro:page-load' | 'astro:after-swap';
@@ -37,9 +38,9 @@ const { fallback = 'animate' } = Astro.props as Props;
// we reloaded a page with history state
// (e.g. history navigation from non-transition page or browser reload)
currentHistoryIndex = history.state.index;
- scrollTo({ left: 0, top: history.state.scrollY });
+ scrollTo({ left: history.state.scrollX, top: history.state.scrollY });
} else if (transitionEnabledOnThisPage()) {
- history.replaceState({ index: currentHistoryIndex, scrollY }, '');
+ history.replaceState({ index: currentHistoryIndex, scrollX, scrollY }, '');
}
const throttle = (cb: (...args: any[]) => any, delay: number) => {
let wait = false;
@@ -208,17 +209,29 @@ const { fallback = 'animate' } = Astro.props as Props;
// Chromium based browsers (Chrome, Edge, Opera, ...)
scrollTo({ left: 0, top: 0, behavior: 'instant' });
+ let initialScrollX = 0;
let initialScrollY = 0;
if (!state && loc.hash) {
const id = decodeURIComponent(loc.hash.slice(1));
const elem = document.getElementById(id);
// prefer scrollIntoView() over scrollTo() because it takes scroll-padding into account
- elem && (initialScrollY = elem.offsetTop) && elem.scrollIntoView();
- } else if (state && state.scrollY !== 0) {
- scrollTo(0, state.scrollY); // usings default scrollBehavior
+ if (elem) {
+ elem.scrollIntoView();
+ initialScrollX = Math.max(
+ 0,
+ elem.offsetLeft + elem.offsetWidth - document.documentElement.clientWidth
+ );
+ initialScrollY = elem.offsetTop;
+ }
+ } else if (state) {
+ scrollTo(state.scrollX, state.scrollY); // usings default scrollBehavior
}
!state &&
- history.pushState({ index: ++currentHistoryIndex, scrollY: initialScrollY }, '', loc.href);
+ history.pushState(
+ { index: ++currentHistoryIndex, scrollX: initialScrollX, scrollY: initialScrollY },
+ '',
+ loc.href
+ );
triggerEvent('astro:after-swap');
};
@@ -280,7 +293,7 @@ const { fallback = 'animate' } = Astro.props as Props;
}
// 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 }, '');
+ !state && history.replaceState({ index: currentHistoryIndex, scrollX, scrollY }, '');
document.documentElement.dataset.astroTransition = dir;
if (supportsViewTransitions) {
@@ -357,8 +370,11 @@ const { fallback = 'animate' } = Astro.props as Props;
ev.preventDefault();
// push state on the first navigation but not if we were here already
if (location.hash) {
- history.replaceState({ index: currentHistoryIndex, scrollY: -(scrollY + 1) }, '');
- const newState: State = { index: ++currentHistoryIndex, scrollY: 0 };
+ history.replaceState(
+ { index: currentHistoryIndex, scrollX, scrollY: -(scrollY + 1) },
+ ''
+ );
+ const newState: State = { index: ++currentHistoryIndex, scrollX: 0, scrollY: 0 };
history.pushState(newState, '', link.href);
}
scrollTo({ left: 0, top: 0, behavior: 'instant' });
@@ -404,7 +420,7 @@ const { fallback = 'animate' } = Astro.props as Props;
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
currentHistoryIndex = nextIndex;
if (state.scrollY < 0) {
- scrollTo(0, -(state.scrollY + 1));
+ scrollTo(state.scrollX, -(state.scrollY + 1));
} else {
navigate(direction, new URL(location.href), state);
}
@@ -432,7 +448,7 @@ const { fallback = 'animate' } = Astro.props as Props;
// There's not a good way to record scroll position before a back button.
// So the way we do it is by listening to scrollend if supported, and if not continuously record the scroll position.
const updateState = () => {
- persistState({ ...history.state, scrollY });
+ persistState({ ...history.state, scrollX, scrollY });
};
if ('onscrollend' in window) addEventListener('scrollend', updateState);
diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/wide-page.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/wide-page.astro
new file mode 100644
index 000000000..4862122c0
--- /dev/null
+++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/wide-page.astro
@@ -0,0 +1,12 @@
+---
+import Layout from '../components/Layout.astro';
+---
+<Layout>
+ <a id="click-right" href="#click-one">go right</a>
+ <article id="widepage">
+ <div style="width:300vw; text-align: center">
+ <a id="click-top" href="#click-right">go to top</a> |
+ <a id="click-one" href="/one">go to 1</a>
+ </div>
+ </article>
+</Layout>
diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js
index 64041ab05..6421b5fc3 100644
--- a/packages/astro/e2e/view-transitions.test.js
+++ b/packages/astro/e2e/view-transitions.test.js
@@ -585,4 +585,34 @@ test.describe('View Transitions', () => {
styles = await page.locator('style').all();
expect(styles.length).toEqual(totalExpectedStyles, 'style count has not changed');
});
+
+ test('Horizontal scroll position restored on back button', async ({ page, astro }) => {
+ await page.goto(astro.resolveUrl('/wide-page'));
+ let article = page.locator('#widepage');
+ await expect(article, 'should have script content').toBeVisible('exists');
+
+ let locator = page.locator('#click-one');
+ await expect(locator).not.toBeInViewport();
+
+ await page.click('#click-right');
+ locator = page.locator('#click-one');
+ await expect(locator).toBeInViewport();
+ locator = page.locator('#click-top');
+ await expect(locator).toBeInViewport();
+
+ await page.click('#click-one');
+ let p = page.locator('#one');
+ await expect(p, 'should have content').toHaveText('Page 1');
+
+ await page.goBack();
+ locator = page.locator('#click-one');
+ await expect(locator).toBeInViewport();
+
+ locator = page.locator('#click-top');
+ await expect(locator).toBeInViewport();
+
+ await page.click('#click-top');
+ locator = page.locator('#click-one');
+ await expect(locator).not.toBeInViewport();
+ });
});