• 作者:老汪软件技巧
  • 发表时间:2024-09-06 21:03
  • 浏览量:

前言

日常开发中,弹窗是一个很常用的组件。通过 v-model 传入 boolean 值控制 v-if v-show 指令切换组件dom元素显示状态,但因为是直接控制元素显示/隐藏,所以在UI体现上会特别生硬,这时加入渐入渐出的动画就非常重要了。

如果你的组件显示状态是直接由 v-model 直接控制的,那么你就会因为 v-show 指令在 false 时会将元素 display none 的特性,导致渐出动画无法正常显示,本篇文章便是提供一个解决这种问题的思路。

CSS动画

一个元素只播放一次渐入CSS动画是很简单的,你只需要 animation-name 和 animation-duration 就能让他动起来,但当你需要他再播放一次渐出动画时,最简单的实现方法就是更改他的 animation-name,并重新写一套渐出的CSS动画关键帧,下面我将简略列举两个库不同的实现方法。

这里我研究过 element-plus 和 animista 的两种实现方式

element-plus 就是按照最常见的写法,分别写了两套动画Class,并赋上渐入/渐出的关键帧样式,在显示/隐藏切换 Class 实现渐入渐出。这种方式虽然能实现功能,但弊端就是每写一个组件时你都要为他重写一套 Class 和动画,非常的不智能。

animista 实现方法就非常巧妙,通过 animation-direction: reverse 这个属性,只需要写一次动画帧,用一个 animation-name 就能让渐入动画倒放,实现渐入渐出,以下内容将基于这个逻辑在 Vue 中实现。

基本功能

这是一个最基础的弹窗组件代码,包含了最简单的渐入的效果:




@keyframes rtl 编写好了渐入的动画,通过 .rtl Class 引入样式,在 .s-popup 切换为显示状态时,让其子元素 .s-popup--wrapper.rtl 播放渐入动画。

**注意:**这里有个技巧,弹窗组件的定位样式是写在最外层的 .s-popup 上,而播放动画则是由其子元素 .s-popup--wrapper 负责,这种写法好处是,编写关键帧时不需要考虑元素本身的 transform 定位问题,让写好的关键帧少一些多余样式,能够适用到不同的组件里。

vue过渡效果在哪设置_过渡动画插件_

渐出实现

在基础功能有了以后,就需要实现渐出动画,上面解析 animista 的逻辑时说了,我们只需要让渐入动画倒放就是渐出效果了,在实现代码前,我们需要问题 难点。

正常情况下,渐出在弹窗关闭时就需要触发,但组件在 v-model / props.modelValue 设为 false 后, dom元素 就已经被 display: none 隐藏而导致无法正常播放动画,所以我们需要些额外处理逻辑。

异步关闭弹窗

首先我们需要分出几点核心逻辑:

弹窗显示状态是与 v-model 异步的,需要变量独立控制;弹窗隐藏前,需要播放渐出动画,不能被 display: none 影响;

首先我们可以通过 animationstart 和 animationend 事件读取 animationPlaying 播放中状态,同时在 v-model 触发弹窗关闭后,我们不能真的去隐藏弹窗,而是要在这个时机里设置一个 closed 关闭中状态,去触发渐出动画,让动画去负责弹出的显示控制。具体则通过一个 setInterval 异步方法不断查询 animationPlaying 来更新弹窗的显示状态,代码实现如下:



现在,触发动画的代码已完成,下一步就是控制样式,让渐入动画倒放,实现渐出

触发渐出动画

这里我们结合 Vue3 的特性,使用组合式函数来编写一个可复用的倒放样式。

由于动画是使用同一个 animation-name,所以在触发的那一刻我们需要先把 animation-name 清空,然后 setTimeout 一个 animation-irection: reverse 倒放属性,就能够让元素动画倒放,实现渐出效果了

// useAnimationReverse.ts
import type { Ref } from 'vue';
import { ref, watch } from 'vue';
export function useAnimationReverse(on: Ref<boolean>) {
	const style = ref<{ [key: string]: any }>({});
	watch(on, (val) => {
		if (val) {
            // 先重设元素动画样式
			style.value = {
				animationDuration: 'auto',
				animationTimingFunction: 'ease',
				animationName: 'none',
			};
            // 随后设置倒放
			setTimeout(() => {
				style.value = {
					animationDirection: 'reverse',
				};
			}, 0);
		} else {
			style.value = {};
		}
	});
	return {
		style,
	};
}

最后引入到组件代码中即可完成。

最终代码