0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-20 22:12:38 -05:00
astro/examples/view-transitions/src/scripts/spa-navigation.js
2023-09-27 19:25:02 +00:00

254 lines
6.9 KiB
JavaScript

import {
getNavigationType,
getPathId,
isBackNavigation,
shouldNotIntercept,
updateTheDOMSomehow,
useTvFragment,
} from './utils';
// View Transitions support cross-document navigations.
// Should compare performace.
// https://github.com/WICG/view-transitions/blob/main/explainer.md#cross-document-same-origin-transitions
// https://github.com/WICG/view-transitions/blob/main/explainer.md#script-events
function shouldDisableSpa() {
return false;
}
navigation.addEventListener('navigate', (navigateEvent) => {
if (shouldDisableSpa()) return;
if (shouldNotIntercept(navigateEvent)) return;
const toUrl = new URL(navigateEvent.destination.url);
const toPath = toUrl.pathname;
const fromPath = location.pathname;
const navigationType = getNavigationType(fromPath, toPath);
if (location.origin !== toUrl.origin) return;
switch (navigationType) {
case 'home-to-movie':
case 'tv-to-show':
handleHomeToMovieTransition(navigateEvent, getPathId(toPath));
break;
case 'movie-to-home':
case 'show-to-tv':
handleMovieToHomeTransition(navigateEvent, getPathId(fromPath));
break;
case 'movie-to-person':
handleMovieToPersonTransition(navigateEvent, getPathId(fromPath), getPathId(toPath));
break;
case 'person-to-movie':
case 'person-to-show':
handlePersonToMovieTransition(navigateEvent, getPathId(fromPath), getPathId(toPath));
break;
default:
return;
}
});
// TODO: https://developer.chrome.com/docs/web-platform/view-transitions/#transitions-as-an-enhancement
function handleHomeToMovieTransition(navigateEvent, movieId) {
navigateEvent.intercept({
async handler() {
const fragmentUrl = useTvFragment(navigateEvent)
? '/fragments/TvDetails'
: '/fragments/MovieDetails';
const response = await fetch(`${fragmentUrl}/${movieId}`);
const data = await response.text();
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
const thumbnail = document.getElementById(`movie-poster-${movieId}`);
if (thumbnail) {
thumbnail.style.viewTransitionName = 'movie-poster';
}
const transition = document.startViewTransition(() => {
if (thumbnail) {
thumbnail.style.viewTransitionName = '';
}
document.getElementById('container').scrollTop = 0;
updateTheDOMSomehow(data);
});
await transition.finished;
},
});
}
function handleMovieToHomeTransition(navigateEvent, movieId) {
navigateEvent.intercept({
scroll: 'manual',
async handler() {
const fragmentUrl = useTvFragment(navigateEvent)
? '/fragments/TvList'
: '/fragments/MovieList';
const response = await fetch(fragmentUrl);
const data = await response.text();
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
const tempHomePage = document.createElement('div');
const moviePoster = document.getElementById(`movie-poster`);
let thumbnail;
// If the movie poster is not in the home page, removes the transition style so that
// the poster doesn't stay on the page while transitioning
tempHomePage.innerHTML = data;
if (!tempHomePage.querySelector(`#movie-poster-${movieId}`)) {
moviePoster?.classList.remove('movie-poster');
}
const transition = document.startViewTransition(() => {
updateTheDOMSomehow(data);
thumbnail = document.getElementById(`movie-poster-${movieId}`);
if (thumbnail) {
thumbnail.scrollIntoViewIfNeeded();
thumbnail.style.viewTransitionName = 'movie-poster';
}
});
await transition.finished;
if (thumbnail) {
thumbnail.style.viewTransitionName = '';
}
},
});
}
function handleMovieToPersonTransition(navigateEvent, movieId, personId) {
// TODO: https://developer.chrome.com/docs/web-platform/view-transitions/#not-a-polyfill
// ...has example of `back-transition` class applied to document
const isBack = isBackNavigation(navigateEvent);
navigateEvent.intercept({
async handler() {
const response = await fetch('/fragments/PersonDetails/' + personId);
const data = await response.text();
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
let personThumbnail;
let moviePoster;
let movieThumbnail;
if (!isBack) {
// We're transitioning the person photo; we need to remove the transition of the poster
// so that it doesn't stay on the page while transitioning
moviePoster = document.getElementById(`movie-poster`);
if (moviePoster) {
moviePoster.classList.remove('movie-poster');
}
personThumbnail = document.getElementById(`person-photo-${personId}`);
if (personThumbnail) {
personThumbnail.style.viewTransitionName = 'person-photo';
}
}
const transition = document.startViewTransition(() => {
updateTheDOMSomehow(data);
if (personThumbnail) {
personThumbnail.style.viewTransitionName = '';
}
if (isBack) {
// If we're coming back to the person page, we're transitioning
// into the movie poster thumbnail, so we need to add the tag to it
movieThumbnail = document.getElementById(`movie-poster-${movieId}`);
if (movieThumbnail) {
movieThumbnail.scrollIntoViewIfNeeded();
movieThumbnail.style.viewTransitionName = 'movie-poster';
}
}
document.getElementById('container').scrollTop = 0;
});
await transition.finished;
if (movieThumbnail) {
movieThumbnail.style.viewTransitionName = '';
}
},
});
}
function handlePersonToMovieTransition(navigateEvent, personId, movieId) {
const isBack = isBackNavigation(navigateEvent);
navigateEvent.intercept({
scroll: 'manual',
async handler() {
const fragmentUrl = useTvFragment(navigateEvent)
? '/fragments/TvDetails'
: '/fragments/MovieDetails';
const response = await fetch(`${fragmentUrl}/${movieId}`);
const data = await response.text();
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
let thumbnail;
let moviePoster;
let movieThumbnail;
if (!isBack) {
movieThumbnail = document.getElementById(`movie-poster-${movieId}`);
if (movieThumbnail) {
movieThumbnail.style.viewTransitionName = 'movie-poster';
}
}
const transition = document.startViewTransition(() => {
updateTheDOMSomehow(data);
if (isBack) {
moviePoster = document.getElementById(`movie-poster`);
if (moviePoster) {
moviePoster.classList.remove('movie-poster');
}
if (personId) {
thumbnail = document.getElementById(`person-photo-${personId}`);
if (thumbnail) {
thumbnail.scrollIntoViewIfNeeded();
thumbnail.style.viewTransitionName = 'person-photo';
}
}
} else {
document.getElementById('container').scrollTop = 0;
if (movieThumbnail) {
movieThumbnail.style.viewTransitionName = '';
}
}
});
await transition.finished;
if (thumbnail) {
thumbnail.style.viewTransitionName = '';
}
if (moviePoster) {
moviePoster.classList.add('movie-poster');
}
},
});
}