Vue中同步CSS动画重置与ref值变化解决状态切换闪屏问题
问题描述
我想要实现3个状态之间的淡入淡出切换动画,为了存储包含图片和文本的状态,我创建了对象数组uploadSteps,通过固定间隔递增数组索引的方式生成动画。
当前遇到CSS相关问题:我想在每个步骤切换之间添加淡入淡出效果,让过渡更平滑,大多数时候运行正常,但偶尔刷新页面后第一次切换时,会出现CSS动画在状态变更前就重启的问题,该问题暂无法稳定复现。
实际运行效果 => 图片淡入 -> 显示图片1 -> 图片1淡出 -> 短暂显示图片1 -> 图片2淡入
期望运行效果 => 图片淡入 -> 显示图片1 -> 图片1淡出 -> 图片2淡入
可以观察到图片1和图片2切换时存在闪屏效果,请问该如何解决该问题?
相关代码如下:
<template> <div class="screenshot-upload-processing-container"> <div class="image-container"> <img ref="imageRef" :src=" currentStep ? $requireImage(currentStep.imagePath).toString() : '' " :alt="currentStep.text" class="image" /> </div> <div class="centered-row"> <span ref="textRef" class="text">{{ currentStep ? currentStep.text : '' }}</span> </div> </div> </template> <script lang="ts"> import { computed, defineComponent, onBeforeUnmount, ref, } from '@nuxtjs/composition-api'; export default defineComponent({ name: 'ScreenshotUploadProcessing', setup() { const stepNumber = ref(0); const timer = ref<NodeJS.Timeout | null>(null); const imageRef = ref<HTMLImageElement | null>(null); const textRef = ref<HTMLSpanElement | null>(null); const uploadSteps = computed(() => { return [ { imagePath: 'image1.svg', text: 'Text 1', }, { imagePath: 'image2.svg', text: 'Text 2', }, { imagePath: 'image3.svg', text: 'Text 3', }, ]; }); const resetAnimations = () => { const image = imageRef.value; const text = textRef.value; if (image) { image.style.animation = 'none'; void image.offsetHeight; image.style.animation = ''; } if (text) { text.style.animation = 'none'; void text.offsetHeight; text.style.animation = ''; } }; const updateStepNumber = () => { timer.value = setInterval(() => { if (stepNumber.value === uploadSteps.value.length - 1) { stepNumber.value = 0; } else { stepNumber.value++; } resetAnimations(); }, 3000); }; onBeforeUnmount(() => { clearInterval(timer.value as NodeJS.Timeout); }); const currentStep = computed(() => uploadSteps.value[stepNumber.value]); updateStepNumber(); return { stepNumber, currentStep, imageRef, textRef, uploadSteps, }; }, }); </script> <style lang="scss" scoped> @keyframes fade { 0%, 100% { opacity: 0; } 40%, 60% { opacity: 1; } } .screenshot-upload-processing-container { display: flex; flex-direction: column; height: 100%; } .image { animation: fade 3s linear; } .image-container { display: flex; align-items: center; justify-content: center; height: 27rem; margin-top: 10.3rem; } .centered-row { display: flex; align-items: center; justify-content: center; margin-top: $spacing-xl; } .text { @extend %text-gradient-1; animation: fade 3s linear; font-weight: $font-weight-medium; } </style>
解决方案
问题根因
闪屏的核心原因是Vue的响应式DOM更新为异步机制,你当前的执行逻辑存在时序错位:
- 定时器触发后先修改
stepNumber的值 - 同步调用
resetAnimations重置动画,此时Vue还未完成DOM更新,图片、文本仍为上一步的旧内容 - 动画重启后先渲染旧内容,等Vue异步更新完DOM替换为新内容时,已经过了数帧,就会出现旧内容短暂闪现的问题
另外setInterval的3000ms间隔和CSS动画的3s时长完全对齐,受主线程任务调度阻塞的影响,很容易出现时序偏差,进一步提升了偶现问题的概率。
修复步骤
1. 引入nextTick等待DOM更新完成
修改逻辑为更新stepNumber后,等待Vue完成DOM渲染,内容替换为新的步骤后再重置动画,从根源避免旧内容参与新动画的首帧渲染。
2. 给CSS动画添加forwards属性
保持动画最后一帧的状态(opacity: 0),避免动画结束后自动跳回显示旧内容。
3. 增加元素初始透明属性
避免初始加载或动画间隙无动画时内容意外显示。
4. 微调定时器间隔(可选)
可将定时器间隔调整为3050ms,给主线程调度留缓冲空间,进一步降低时序错位概率。
修改后的代码片段
Script部分修改
// 新增引入nextTick import { computed, defineComponent, onBeforeUnmount, ref, nextTick } from '@nuxtjs/composition-api'; // 修改定时器内的执行逻辑 const updateStepNumber = () => { timer.value = setInterval(async () => { if (stepNumber.value === uploadSteps.value.length - 1) { stepNumber.value = 0; } else { stepNumber.value++; } // 等待DOM更新完成,内容替换为新步骤后再重置动画 await nextTick() resetAnimations(); }, 3050); // 可微调间隔留缓冲 };
Style部分修改
.image { animation: fade 3s linear forwards; opacity: 0; /* 初始默认透明 */ } .text { @extend %text-gradient-1; animation: fade 3s linear forwards; opacity: 0; /* 初始默认透明 */ font-weight: $font-weight-medium; }
内容的提问来源于stack exchange,提问作者Théo Lavaux
相关产品推荐
相关产品推荐

