UniApp頁面切換動效實戰:打造流暢精致的轉場體驗
引言
在移動應用開發中,頁面切換動效不僅能提升用戶體驗,還能傳達應用的品質感。隨著HarmonyOS的普及,用戶對應用的動效體驗要求越來越高。本文將深入探討如何在UniApp中實現流暢精致的頁面切換動效,并重點關注HarmonyOS平臺的適配優化。
技術方案設計
1. 實現思路
頁面切換動效主要包含以下幾個關鍵點:
- 路由切換監聽
- 動畫狀態管理
- 過渡效果實現
- 性能優化處理
2. 技術選型
- 動畫實現:CSS3 + Animation API
- 狀態管理:Pinia
- 路由管理:uni-router
- 性能優化:requestAnimationFrame + CSS硬件加速
核心實現
1. 頁面切換容器組件
<!-- components/PageTransition.vue -->
<template><view class="page-transition"><view class="page-container":class="[transitionName,{ 'is-switching': isSwitching }]":style="containerStyle"><slot></slot></view><!-- 過渡遮罩層 --><view v-if="showMask"class="transition-mask":style="maskStyle"></view></view>
</template><script lang="ts" setup>
import { ref, computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useTransitionStore } from '@/stores/transition'
import { createAnimation } from '@/utils/animation'
import type { TransitionMode } from '@/types'// 組件屬性定義
const props = withDefaults(defineProps<{mode?: TransitionModeduration?: numbertiming?: stringdirection?: 'forward' | 'backward'
}>(), {mode: 'slide',duration: 300,timing: 'ease-in-out',direction: 'forward'
})// 狀態管理
const router = useRouter()
const route = useRoute()
const transitionStore = useTransitionStore()// 響應式數據
const isSwitching = ref(false)
const showMask = ref(false)
const animation = ref(null)// 計算屬性
const transitionName = computed(() => {const { mode, direction } = propsreturn `transition-${mode}-${direction}`
})const containerStyle = computed(() => ({'--transition-duration': `${props.duration}ms`,'--transition-timing': props.timing
}))const maskStyle = computed(() => ({'--mask-opacity': transitionStore.maskOpacity
}))// 監聽路由變化
watch(() => route.path, async (newPath, oldPath) => {if (newPath === oldPath) returnawait startTransition()
})// 初始化動畫實例
const initAnimation = () => {animation.value = createAnimation({duration: props.duration,timingFunction: props.timing})
}// 開始過渡動畫
const startTransition = async () => {isSwitching.value = trueshowMask.value = true// 根據不同模式執行對應動畫switch (props.mode) {case 'slide':await executeSlideAnimation()breakcase 'fade':await executeFadeAnimation()breakcase 'zoom':await executeZoomAnimation()breakcase 'custom':await executeCustomAnimation()break}// 結束過渡await finishTransition()
}// 滑動動畫實現
const executeSlideAnimation = async () => {const isForward = props.direction === 'forward'const startX = isForward ? '100%' : '-100%'const endX = '0%'animation.value.translateX(startX).step().translateX(endX).step()return new Promise(resolve => {setTimeout(resolve, props.duration)})
}// 淡入淡出動畫實現
const executeFadeAnimation = async () => {animation.value.opacity(0).step().opacity(1).step()return new Promise(resolve => {setTimeout(resolve, props.duration)})
}// 縮放動畫實現
const executeZoomAnimation = async () => {const isForward = props.direction === 'forward'const startScale = isForward ? 0.8 : 1.2animation.value.scale(startScale).opacity(0).step().scale(1).opacity(1).step()return new Promise(resolve => {setTimeout(resolve, props.duration)})
}// 自定義動畫實現
const executeCustomAnimation = async () => {// 執行自定義動畫邏輯emit('before-transition')await transitionStore.executeCustomTransition()emit('after-transition')
}// 完成過渡
const finishTransition = async () => {isSwitching.value = falseshowMask.value = false// 重置動畫狀態animation.value.reset()// 觸發完成事件emit('transition-end')
}// 事件聲明
const emit = defineEmits<{(e: 'before-transition'): void(e: 'after-transition'): void(e: 'transition-end'): void
}>()// 組件初始化
onMounted(() => {initAnimation()
})
</script><style lang="scss">
.page-transition {position: relative;width: 100%;height: 100%;overflow: hidden;.page-container {position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: var(--page-bg, #fff);transition: transform var(--transition-duration) var(--transition-timing),opacity var(--transition-duration) var(--transition-timing);&.is-switching {pointer-events: none;}// 滑動過渡&.transition-slide-forward {transform: translateX(100%);&.is-switching {transform: translateX(0);}}&.transition-slide-backward {transform: translateX(-100%);&.is-switching {transform: translateX(0);}}// 淡入淡出過渡&.transition-fade-forward,&.transition-fade-backward {opacity: 0;&.is-switching {opacity: 1;}}// 縮放過渡&.transition-zoom-forward {transform: scale(0.8);opacity: 0;&.is-switching {transform: scale(1);opacity: 1;}}&.transition-zoom-backward {transform: scale(1.2);opacity: 0;&.is-switching {transform: scale(1);opacity: 1;}}}.transition-mask {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(0, 0, 0, var(--mask-opacity, 0.3));z-index: 9999;transition: opacity var(--transition-duration) var(--transition-timing);}
}// 深色模式適配
@media (prefers-color-scheme: dark) {.page-transition {.page-container {--page-bg: #121212;}}
}
</style>
2. 路由配置與動畫管理
// router/index.ts
import { createRouter } from '@/uni-router'
import type { TransitionMode } from '@/types'interface RouteConfig {path: stringname: stringcomponent: anymeta?: {transition?: {mode?: TransitionModeduration?: numberdirection?: 'forward' | 'backward'}}
}const routes: RouteConfig[] = [{path: '/pages/index/index',name: 'Home',component: () => import('@/pages/index/index.vue'),meta: {transition: {mode: 'slide',duration: 300}}},{path: '/pages/detail/detail',name: 'Detail',component: () => import('@/pages/detail/detail.vue'),meta: {transition: {mode: 'zoom',duration: 400}}}
]const router = createRouter({routes
})// 路由守衛中處理轉場動畫
router.beforeEach((to, from, next) => {const toDepth = to.path.split('/').lengthconst fromDepth = from.path.split('/').length// 根據路由深度判斷前進后退const direction = toDepth >= fromDepth ? 'forward' : 'backward'// 設置轉場動畫配置to.meta.transition = {...to.meta.transition,direction}next()
})export default router
3. 狀態管理
// stores/transition.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'export const useTransitionStore = defineStore('transition', () => {// 狀態定義const isTransitioning = ref(false)const maskOpacity = ref(0.3)const currentTransition = ref<TransitionMode>('slide')// 動畫控制方法const startTransition = (mode: TransitionMode) => {isTransitioning.value = truecurrentTransition.value = mode}const endTransition = () => {isTransitioning.value = false}const setMaskOpacity = (opacity: number) => {maskOpacity.value = opacity}// 自定義轉場動畫const executeCustomTransition = async () => {// 實現自定義轉場邏輯return new Promise(resolve => {setTimeout(resolve, 300)})}return {isTransitioning,maskOpacity,currentTransition,startTransition,endTransition,setMaskOpacity,executeCustomTransition}
})
HarmonyOS平臺優化
1. 性能優化
-
動畫性能
// utils/performance.ts export const optimizeAnimation = (element: HTMLElement) => {// 開啟硬件加速element.style.transform = 'translateZ(0)'element.style.backfaceVisibility = 'hidden'// 使用will-change提示element.style.willChange = 'transform, opacity' }export const cleanupAnimation = (element: HTMLElement) => {element.style.transform = ''element.style.backfaceVisibility = ''element.style.willChange = '' }
-
內存管理
// utils/memory.ts export class MemoryManager {private static instance: MemoryManagerprivate cache: Map<string, any>private constructor() {this.cache = new Map()}static getInstance() {if (!MemoryManager.instance) {MemoryManager.instance = new MemoryManager()}return MemoryManager.instance}cacheAnimation(key: string, animation: any) {this.cache.set(key, animation)}getAnimation(key: string) {return this.cache.get(key)}clearCache() {this.cache.clear()} }
2. 動效適配
-
動畫曲線
// constants/animation.ts export const HarmonyOSCurves = {EASE_OUT: 'cubic-bezier(0.33, 0, 0.67, 1)',EASE_IN: 'cubic-bezier(0.33, 0, 1, 1)',STANDARD: 'cubic-bezier(0.2, 0.4, 0.8, 0.9)' }
-
手勢適配
// mixins/gesture.ts export const useGesture = () => {const handleGesture = (event: TouchEvent) => {// 處理手勢邏輯const touch = event.touches[0]const startX = touch.clientXconst startY = touch.clientY// 判斷手勢方向const direction = getGestureDirection(startX, startY)return {direction,distance: Math.sqrt(startX * startX + startY * startY)}}return {handleGesture} }
最佳實踐建議
-
動效設計
- 遵循自然運動規律
- 保持動畫時長適中
- 避免過度動畫
-
性能優化
- 使用CSS3硬件加速
- 避免重繪和回流
- 合理使用動畫緩存
-
用戶體驗
- 提供動畫開關選項
- 支持手勢返回
- 保持動效一致性
總結
通過本文的實踐,我們實現了一個功能完備、性能優異的頁面切換動效系統。該方案具有以下特點:
- 豐富的動效類型
- 流暢的過渡體驗
- 優秀的性能表現
- 完善的平臺適配
- 良好的可擴展性
希望本文的內容能夠幫助開發者在UniApp項目中實現更加精致的頁面切換效果,同時為HarmonyOS平臺的應用開發提供參考。
參考資源
- UniApp官方文檔
- HarmonyOS動效設計規范
- 前端動畫性能優化指南
- 移動端手勢交互設計