You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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更新为异步机制,你当前的执行逻辑存在时序错位:

  1. 定时器触发后先修改stepNumber的值
  2. 同步调用resetAnimations重置动画,此时Vue还未完成DOM更新,图片、文本仍为上一步的旧内容
  3. 动画重启后先渲染旧内容,等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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 06:36:04