Skip to content

Commit 5f1fb78

Browse files
authored
fix: only apply view transition between search <-> index pages (#1222)
1 parent eeeb6b6 commit 5f1fb78

File tree

2 files changed

+91
-1
lines changed

2 files changed

+91
-1
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/**
2+
* Scoped View Transitions plugin.
3+
*
4+
* Only triggers the View Transition API when navigating between `/` and `/search`
5+
* (the search-box morph animation). All other navigations are left untouched so
6+
* they feel instant.
7+
*/
8+
export default defineNuxtPlugin(nuxtApp => {
9+
if (!document.startViewTransition) return
10+
11+
let transition: ViewTransition | undefined
12+
let finishTransition: (() => void) | undefined
13+
let hasUAVisualTransition = false
14+
15+
const resetTransitionState = () => {
16+
transition = undefined
17+
finishTransition = undefined
18+
hasUAVisualTransition = false
19+
}
20+
21+
// Respect browser-initiated visual transitions (e.g. swipe-back)
22+
window.addEventListener('popstate', event => {
23+
hasUAVisualTransition =
24+
(event as PopStateEvent & { hasUAVisualTransition?: boolean }).hasUAVisualTransition ?? false
25+
if (hasUAVisualTransition) {
26+
transition?.skipTransition()
27+
}
28+
})
29+
30+
const router = useRouter()
31+
32+
router.beforeResolve(async (to, from) => {
33+
if (to.matched.length === 0) return
34+
35+
const toPath = to.path
36+
const fromPath = from.path
37+
38+
// Only transition between / and /search
39+
if (!isSearchTransition(toPath, fromPath)) return
40+
41+
// Respect prefers-reduced-motion
42+
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
43+
44+
// Skip if browser already handled the visual transition
45+
if (hasUAVisualTransition) return
46+
47+
const promise = new Promise<void>(resolve => {
48+
finishTransition = resolve
49+
})
50+
51+
let changeRoute: () => void
52+
const ready = new Promise<void>(resolve => (changeRoute = resolve))
53+
54+
transition = document.startViewTransition(() => {
55+
changeRoute!()
56+
return promise
57+
})
58+
59+
transition.finished.then(resetTransitionState)
60+
61+
await nuxtApp.callHook('page:view-transition:start', transition)
62+
63+
return ready
64+
})
65+
66+
// Abort on errors
67+
router.onError(() => {
68+
finishTransition?.()
69+
resetTransitionState()
70+
})
71+
nuxtApp.hook('app:error', () => {
72+
finishTransition?.()
73+
resetTransitionState()
74+
})
75+
nuxtApp.hook('vue:error', () => {
76+
finishTransition?.()
77+
resetTransitionState()
78+
})
79+
80+
// Finish when page render completes
81+
nuxtApp.hook('page:finish', () => {
82+
finishTransition?.()
83+
resetTransitionState()
84+
})
85+
})
86+
87+
/** Return true when navigating between `/` and `/search` (either direction). */
88+
function isSearchTransition(toPath: string, fromPath: string): boolean {
89+
const paths = new Set([toPath, fromPath])
90+
return paths.has('/') && paths.has('/search')
91+
}

nuxt.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ export default defineNuxtConfig({
128128
entryImportMap: false,
129129
typescriptPlugin: true,
130130
viteEnvironmentApi: true,
131-
viewTransition: true,
132131
typedPages: true,
133132
},
134133

0 commit comments

Comments
 (0)