0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-03 22:29:08 -05:00
astro/examples/view-transitions/src/scripts/spa-navigation.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

263 lines
7.5 KiB
JavaScript
Raw Normal View History

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')
}
},
})
}