The View Transitions API brings native, high-performance page transitions to web applications. When combined with Vue.js, it enables seamless navigation experiences that were previously complex to implement. Let's explore how to implement these transitions effectively.
Understanding View Transitions
The View Transitions API captures the state of your page before and after a transition, automatically creating smooth animations between these states. This is particularly powerful in Single Page Applications (SPAs) like Vue.js.
Basic Implementation
First, let's set up a basic Vue.js application with view transitions:
// App.vue
<script setup>
import { useRouter } from 'vue-router'
import { ref } from 'vue'
const router = useRouter()
const isTransitioning = ref(false)
async function handleNavigation(to) {
if (!document.startViewTransition) {
return router.push(to)
}
isTransitioning.value = true
const transition = document.startViewTransition(async () => {
await router.push(to)
})
await transition.finished
isTransitioning.value = false
}
</script>
<template>
<div class="app-container">
<nav>
<a @click="handleNavigation('/')">Home</a>
<a @click="handleNavigation('/about')">About</a>
</nav>
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</div>
</template>
Styling Transitions
Add CSS to control the transition animations:
/* styles.css */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.5s;
}
::view-transition-old(root) {
animation: fade-out 0.5s ease-out forwards;
}
::view-transition-new(root) {
animation: fade-in 0.5s ease-in forwards;
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fade-out {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-20px);
}
}
Advanced Transitions
Named Transitions
You can create more sophisticated transitions by naming specific elements:
<template>
<div class="product-card">
<img
:src="product.image"
style="view-transition-name: product-image"
>
<h2 style="view-transition-name: product-title">
{{ product.title }}
</h2>
</div>
</template>
Conditional Transitions
Implement different transitions based on navigation direction:
<script setup>
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const previousPath = ref('/')
const transitionName = computed(() => {
const isForward = route.path.length > previousPath.value.length
previousPath.value = route.path
return isForward ? 'slide-left' : 'slide-right'
})
</script>
<style>
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: transform 0.5s ease;
}
.slide-left-enter-from {
transform: translateX(100%);
}
.slide-left-leave-to {
transform: translateX(-100%);
}
.slide-right-enter-from {
transform: translateX(-100%);
}
.slide-right-leave-to {
transform: translateX(100%);
}
</style>
Handling Complex State
For more complex transitions involving state changes:
<script setup>
import { ref, watch } from 'vue'
const currentState = ref({})
const previousState = ref({})
watch(currentState, (newState, oldState) => {
previousState.value = oldState
document.startViewTransition(async () => {
await updateUI(newState)
})
})
async function updateUI(state) {
// Update your UI components here
await nextTick()
}
</script>
Performance Optimization
1. Debouncing Transitions
Prevent transition spam with debouncing:
<script setup>
import { debounce } from 'lodash-es'
const debouncedTransition = debounce((to) => {
document.startViewTransition(async () => {
await router.push(to)
})
}, 200)
</script>
2. Preloading Content
Improve transition smoothness by preloading:
<script setup>
async function handleNavigation(to) {
// Start preloading
const preloadPromise = preloadContent(to)
// Start transition
const transition = document.startViewTransition(async () => {
await preloadPromise // Wait for content
await router.push(to)
})
}
async function preloadContent(route) {
// Implement your preloading logic here
const component = await router.resolve(route).component
await component.load()
}
</script>
Browser Support and Fallbacks
Implement graceful fallbacks for browsers that don't support View Transitions:
<script setup>
function supportsViewTransitions() {
return !!document.startViewTransition
}
async function handleNavigation(to) {
if (!supportsViewTransitions()) {
// Fallback to Vue's built-in transitions
return router.push(to)
}
// Proceed with view transitions
const transition = document.startViewTransition(...)
}
</script>
<template>
<transition
v-if="!supportsViewTransitions()"
name="fade"
mode="out-in"
>
<router-view />
</transition>
<router-view v-else />
</template>
Best Practices
- Keep Transitions Short
- Aim for 300-500ms duration
- Use ease-in-out timing functions
- Handle Loading States
<template>
<div :class="{ 'is-loading': isTransitioning }">
<loading-spinner v-if="isTransitioning" />
<router-view v-else />
</div>
</template>
- Memory Management
// Clean up resources
onBeforeUnmount(() => {
// Cancel any pending transitions
if (activeTransition) {
activeTransition.skipTransition()
}
})
- Accessibility
// Respect user preferences
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
// Disable or simplify transitions
}
Conclusion
View Transitions in Vue.js provide a powerful way to create smooth, native-feeling navigation experiences. By following these implementation patterns and best practices, you can create engaging transitions that enhance your application's user experience without sacrificing performance or accessibility.
Remember to:
- Test transitions across different devices and browsers
- Implement appropriate fallbacks
- Consider user preferences for reduced motion
- Optimize performance with preloading and debouncing
- Keep transitions subtle and purposeful
The View Transitions API, combined with Vue.js's reactivity system, opens up new possibilities for creating polished, professional web applications with minimal effort.