feat(nuxt): allow controlling view transitions in page meta (#25264)

This commit is contained in:
Horváth Bálint 2024-01-29 13:23:51 +01:00 committed by GitHub
parent d2cca0ebc8
commit 995acd9983
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 84 additions and 4 deletions

View File

@ -426,6 +426,35 @@ export default defineNuxtConfig({
})
```
The possible values are: `false`, `true`, or `'always'`.
If set to true, Nuxt will not apply transitions if the user's browser matches `prefers-reduced-motion: reduce` (recommended). If set to `always`, Nuxt will always apply the transition and it is up to you to respect the user's preference.
By default, view transitions are enabled for all [pages](/docs/guide/directory-structure/pages), but you can set a different global default.
```ts [nuxt.config.ts]
export default defineNuxtConfig({
app: {
// Disable view transitions globally, and opt-in on a per page basis
viewTransition: false
},
})
```
It is possible to override the default `viewTransition` value for a page by setting the `viewTransition` key in [`definePageMeta`](/docs/api/utils/define-page-meta) of the page:
```vue [pages/about.vue]
<script setup lang="ts">
definePageMeta({
viewTransition: false
})
</script>
```
::alert{type="warning"}
Overriding view transitions on a per-page basis will only have an effect if you have enabled the `experimental.viewTransition` option.
::
If you are also using Vue transitions like `pageTransition` and `layoutTransition` (see above) to achieve the same result as the new View Transitions API, then you may wish to _disable_ Vue transitions if the user's browser supports the newer, native web API. You can do this by creating `~/middleware/disable-vue-transitions.global.ts` with the following contents:
```js

View File

@ -33,6 +33,7 @@ interface PageMeta {
alias?: string | string[]
pageTransition?: boolean | TransitionProps
layoutTransition?: boolean | TransitionProps
viewTransition?: boolean | 'always'
key?: false | string | ((route: RouteLocationNormalizedLoaded) => string)
keepalive?: boolean | KeepAliveProps
layout?: false | LayoutKey | Ref<LayoutKey> | ComputedRef<LayoutKey>
@ -104,6 +105,14 @@ interface PageMeta {
Set name of the transition to apply for current page. You can also set this value to `false` to disable the page transition.
**`viewTransition`**
- **Type**: `boolean | 'always'`
**Experimental feature, only available when [enabled in your nuxt.config file](/docs/getting-started/transitions#view-transitions-api-experimental)**</br>
Enable/disable View Transitions for the current page.
If set to true, Nuxt will not apply the transition if the users browser matches `prefers-reduced-motion: reduce` (recommended). If set to `always`, Nuxt will always apply the transition.
**`redirect`**
- **Type**: [`RouteRecordRedirectOption`](https://router.vuejs.org/guide/essentials/redirect-and-alias.html#redirect-and-alias)

View File

@ -2,10 +2,12 @@ import { isChangingPage } from '../components/utils'
import { useRouter } from '../composables/router'
import { defineNuxtPlugin } from '../nuxt'
// @ts-expect-error virtual file
import { viewTransition } from '#build/nuxt.config.mjs'
import { appViewTransition as defaultViewTransition } from '#build/nuxt.config.mjs'
export default defineNuxtPlugin((nuxtApp) => {
if (!document.startViewTransition || (viewTransition !== 'always' && window.matchMedia('(prefers-reduced-motion: reduce)').matches)) { return }
if (!document.startViewTransition) {
return
}
let finishTransition: undefined | (() => void)
let abortTransition: undefined | (() => void)
@ -13,9 +15,14 @@ export default defineNuxtPlugin((nuxtApp) => {
const router = useRouter()
router.beforeResolve((to, from) => {
if (!isChangingPage(to, from)) {
const viewTransitionMode = to.meta.viewTransition ?? defaultViewTransition
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
const prefersNoTransition = prefersReducedMotion && viewTransitionMode !== 'always'
if (viewTransitionMode === false || prefersNoTransition || !isChangingPage(to, from)) {
return
}
const promise = new Promise<void>((resolve, reject) => {
finishTransition = resolve
abortTransition = reject

View File

@ -1,6 +1,6 @@
import { existsSync, readdirSync } from 'node:fs'
import { mkdir, readFile } from 'node:fs/promises'
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, logger, updateTemplates, useNitro } from '@nuxt/kit'
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, logger, updateTemplates, useNitro } from '@nuxt/kit'
import { dirname, join, relative, resolve } from 'pathe'
import { genImport, genObjectFromRawEntries, genString } from 'knitwork'
import { joinURL } from 'ufo'
@ -473,6 +473,26 @@ export default defineNuxtModule({
}
})
// add page meta types if enabled
if (nuxt.options.experimental.viewTransition) {
addTypeTemplate({
filename: 'types/view-transitions.d.ts',
getContents: ({ nuxt }) => {
const runtimeDir = resolve(distDir, 'pages/runtime')
const composablesFile = relative(join(nuxt.options.buildDir, 'types'), resolve(runtimeDir, 'composables'))
return [
'import { ComputedRef, MaybeRef } from \'vue\'',
`declare module ${genString(composablesFile)} {`,
' interface PageMeta {',
` viewTransition?: boolean | 'always'`,
' }',
'}',
].join('\n')
}
})
}
// Add <NuxtPage>
addComponent({
name: 'NuxtPage',

View File

@ -145,6 +145,20 @@ export default defineUntypedSchema({
*/
pageTransition: false,
/**
* Default values for view transitions.
*
* This only has an effect when **experimental** support for View Transitions is
* [enabled in your nuxt.config file](/docs/getting-started/transitions#view-transitions-api-experimental).
*
* This can be overridden with `definePageMeta` on an individual page.
* @see https://nuxt.com/docs/getting-started/transitions#view-transitions-api-experimental
* @type {typeof import('../src/types/config').NuxtAppConfig['viewTransition']}
*/
viewTransition: {
$resolve: async (val, get) => val ?? await get('experimental.viewTransition') ?? false
},
/**
* Default values for KeepAlive configuration between pages.
*

View File

@ -144,6 +144,7 @@ export interface NuxtAppConfig {
head: AppHeadMetaObject
layoutTransition: boolean | TransitionProps
pageTransition: boolean | TransitionProps
viewTransition?: boolean | 'always'
keepalive: boolean | KeepAliveProps
}