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

Increase consistency between navigation with and without ViewTransitions #9279

Merged
merged 8 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
10 changes: 10 additions & 0 deletions .changeset/nervous-beans-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'astro': minor
---

Greater consistency between navigations with and without `<ViewTransitions>`:

- New for direct calls to `navigate()`: Navigation to a URL whose origin does not match the origin of the current page is excluded from view transition processing and triggers the standard navigation of the browser.
- New: Navigation to the current page (= same path name and same search parameters) without a hash fragment triggers view transition processing.
- New for form submission: Navigation to a non-empty hash target on the current page does not trigger view transitions, but updates the browser history directly and scrolls to the target position.
matthewp marked this conversation as resolved.
Show resolved Hide resolved

Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,9 @@ import Layout from '../components/Layout.astro';
Libero id faucibus nisl tincidunt eget nullam non. Faucibus a pellentesque sit amet porttitor eget dolor. Posuere urna nec tincidunt praesent semper feugiat nibh sed. Suspendisse ultrices gravida dictum fusce. Porttitor eget dolor morbi non arcu. Neque egestas congue quisque egestas diam in. Suscipit tellus mauris a diam maecenas sed enim ut sem. Luctus accumsan tortor posuere ac. Tortor posuere ac ut consequat semper viverra. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam nulla. Senectus et netus et malesuada fames ac turpis egestas. Sed libero enim sed faucibus turpis in eu mi bibendum. Sollicitudin tempor id eu nisl nunc mi.
</article>
</Layout>
<script is:inline>
// let playwright know when navigate() is done
document.addEventListener('astro:before-swap', (e) => {
e.viewTransition.ready.then(()=>console.log("ready"))
});
</script>
3 changes: 3 additions & 0 deletions packages/astro/e2e/view-transitions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,10 @@ test.describe('View Transitions', () => {
await expect(locator).toBeInViewport();

// Scroll back to top
// back returns immediately, but we need to wait for navigate() to complete
const waitForReady = page.waitForEvent('console');
await page.goBack();
await waitForReady;
locator = page.locator('#longpage');
await expect(locator).toBeInViewport();

Expand Down
109 changes: 47 additions & 62 deletions packages/astro/src/transitions/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ export const transitionEnabledOnThisPage = () =>
inBrowser && !!document.querySelector('[name="astro-view-transitions-enabled"]');

const samePage = (thisLocation: URL, otherLocation: URL) =>
thisLocation.origin === otherLocation.origin &&
thisLocation.pathname === otherLocation.pathname &&
thisLocation.search === otherLocation.search;
thisLocation.pathname === otherLocation.pathname && thisLocation.search === otherLocation.search;

// When we traverse the history, the window.location is already set to the new location.
// This variable tells us where we came from
Expand Down Expand Up @@ -423,16 +421,22 @@ async function transition(
options: Options,
historyState?: State
) {
// not ours
if (!transitionEnabledOnThisPage() || location.origin !== to.origin) {
location.href = to.href;
return;
}

const navigationType = historyState
? 'traverse'
: options.history === 'replace'
? 'replace'
: 'push';

if (samePage(from, to) && !options.formData /* not yet: && to.hash*/) {
if (navigationType !== 'traverse') {
updateScrollPosition({ scrollX, scrollY });
}
if (navigationType !== 'traverse') {
updateScrollPosition({ scrollX, scrollY });
}
if (samePage(from, to) && !!to.hash) {
moveToLocation(to, from, options, historyState);
return;
}
Expand All @@ -452,61 +456,48 @@ async function transition(
return;
}

function pageMustReload(preparationEvent: TransitionBeforePreparationEvent) {
return (
preparationEvent.to.hash === '' ||
!samePage(preparationEvent.from, preparationEvent.to) ||
preparationEvent.sourceElement instanceof HTMLFormElement
);
}

async function defaultLoader(preparationEvent: TransitionBeforePreparationEvent) {
if (pageMustReload(preparationEvent)) {
const href = preparationEvent.to.href;
const init: RequestInit = {};
if (preparationEvent.formData) {
init.method = 'POST';
init.body = preparationEvent.formData;
}
const response = await fetchHTML(href, init);
// If there is a problem fetching the new page, just do an MPA navigation to it.
if (response === null) {
preparationEvent.preventDefault();
return;
}
// if there was a redirection, show the final URL in the browser's address bar
if (response.redirected) {
preparationEvent.to = new URL(response.redirected);
}

parser ??= new DOMParser();

preparationEvent.newDocument = parser.parseFromString(response.html, response.mediaType);
// The next line might look like a hack,
// but it is actually necessary as noscript elements
// and their contents are returned as markup by the parser,
// see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString
preparationEvent.newDocument.querySelectorAll('noscript').forEach((el) => el.remove());
const href = preparationEvent.to.href;
const init: RequestInit = {};
if (preparationEvent.formData) {
init.method = 'POST';
init.body = preparationEvent.formData;
}
const response = await fetchHTML(href, init);
// If there is a problem fetching the new page, just do an MPA navigation to it.
if (response === null) {
preparationEvent.preventDefault();
return;
}
// if there was a redirection, show the final URL in the browser's address bar
if (response.redirected) {
preparationEvent.to = new URL(response.redirected);
}

// If ViewTransitions is not enabled on the incoming page, do a full page load to it.
// Unless this was a form submission, in which case we do not want to trigger another mutation.
if (
!preparationEvent.newDocument.querySelector('[name="astro-view-transitions-enabled"]') &&
!preparationEvent.formData
) {
preparationEvent.preventDefault();
return;
}
parser ??= new DOMParser();

const links = preloadStyleLinks(preparationEvent.newDocument);
links.length && (await Promise.all(links));
preparationEvent.newDocument = parser.parseFromString(response.html, response.mediaType);
// The next line might look like a hack,
// but it is actually necessary as noscript elements
// and their contents are returned as markup by the parser,
// see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString
preparationEvent.newDocument.querySelectorAll('noscript').forEach((el) => el.remove());

if (import.meta.env.DEV)
await prepareForClientOnlyComponents(preparationEvent.newDocument, preparationEvent.to);
} else {
preparationEvent.newDocument = document;
// If ViewTransitions is not enabled on the incoming page, do a full page load to it.
// Unless this was a form submission, in which case we do not want to trigger another mutation.
if (
!preparationEvent.newDocument.querySelector('[name="astro-view-transitions-enabled"]') &&
!preparationEvent.formData
) {
preparationEvent.preventDefault();
return;
}

const links = preloadStyleLinks(preparationEvent.newDocument);
links.length && (await Promise.all(links));

if (import.meta.env.DEV)
await prepareForClientOnlyComponents(preparationEvent.newDocument, preparationEvent.to);
}

skipTransition = false;
Expand Down Expand Up @@ -571,12 +562,6 @@ export async function navigate(href: string, options?: Options) {
}
return;
}

// not ours
if (!transitionEnabledOnThisPage()) {
location.href = href;
return;
}
await transition('forward', originalLocation, new URL(href, location.href), options ?? {});
}

Expand Down