前言
我们先来看一段目前两大 UI 框架中对话框的动画效果
- Element-Plus,等于没有动画效果
- Ant-Design,一段不是那么复杂的动画往往能提升观看体验
3. 更改el-dialog动画,效果预览
一、动画原理
通过阅读ant-design源码,发现他们使用了transform-origin这个属性轻松实现了对话框的缩放位移效果。
-
我们可以先简单定义一个block元素
<template> <div class="block"></div> </template> <style> .block { width: 100px; height: 100px; border-radius: 8px; background-color: #409eff; } </style> -
我们给这个块块实现一个简单的缩放动画,在样式中添加
.block { width: 100px; height: 100px; border-radius: 8px; background-color: #409eff; animation: scale 1s ease infinite; // 定义一个无限循环的动画 } // 新增一个缩放动画 @keyframes scale { 0% { opacity: 0; transform: scale3d(0, 0, 1); } 100% { opacity: 1; transform: scale3d(1, 1, 1); } }此时的效果:
-
最重要的一步来了,对于
transform-origin这个属性,默认值为transform-origin:50% 50% 0三个值分别对应着水平、垂直和z轴,默认值其实就是中心点!我们修改这个默认值为200px 200px.block { width: 100px; height: 100px; border-radius: 8px; background-color: #409eff; animation: scale 1s ease infinite; // 定义一个无限循环的动画 transform-origin: 200px 200px 0; // 新增代码 } // 新增一个缩放动画 @keyframes scale { 0% { opacity: 0; transform: scale3d(0, 0, 1); } 100% { opacity: 1; transform: scale3d(1, 1, 1); } }这样,你就可以感受到其中的奥妙了
二、过程分析
承上所述,我们的步骤为
鼠标点击按钮 -> 对话框从鼠标点击的位置弹出
对话框关闭 -> 对话框回到鼠标点击的位置
画了一张简单的图
- 首先,当对话框弹出时,我们需要获取到鼠标点击的位置
(e.x,e.y),以及对话框距离可视区域的距离offsetLeft和offsetTop - 我们需要的
水平和垂直偏移量是一个相对距离,相对于当前元素原点的位置,所以存在负值 - 计算得出,图中的蓝色线条部分
const transformLeft = e.x - offsetLeft; // 水平偏移 const transformTop = e.x - offetTop; // 垂直偏移
三、代码实现
- 获取鼠标点击位置,分析一下点击事件的处理,点击按钮后弹出对话框,对于对话框我们肯定已经在按钮上绑定了一个
click事件用于点击事件处理逻辑,我们可以给window再添加一个点击事件处理要点来了,我们要确保这个点击事件比// 保存鼠标点击位置 const mousePosition = { x: 0, y: 0, }; /** * @description: 点击事件回调 * @param {MouseEvent} e */ const clickHandler = (e: MouseEvent) => { mousePosition.x = e.x; mousePosition.y = e.y; }; document.documentElement.addEventListener("click", clickHandler, true); // 事件捕获弹出对话框按钮的点击事件先执行,因此,在addEvenetListener这个方法中,第三个参数的意思为,在捕获阶段执行。
我们通过鼠标点击按钮,那么浏览器对于事件处理的流程为捕获->冒泡,我们为按钮添加的点击事件默认在冒泡阶段执行,那么上面的事件处理我们希望早于按钮事件,也就是捕获阶段就执行。 - 在对话框显示的时候,设置
transform-origin实现位移动画<template> <el-button type="primary" @click="visible = true"> element plus</el-button> <el-dialog v-model="visible" :title="'对话框'" :width="500" ref="modalRef"> <h1>Element-Plus 对话框</h1> <h1>Element-Plus 对话框</h1> <template #footer> <el-button @click="visible = false">cancel</el-button> <el-button type="primary" @click="visible = false">ok</el-button> </template> </el-dialog> </template> <script setup lang="ts"> import { watch, ref } from "vue"; const modalRef =ref<any>(); const visible = ref(false); watch( () => visible.value, (value) => { if (value) { const node: HTMLElement = modalRef.value?.dialogRef; // 这里是获取到el-dialog的dom节点 console.log(modalRef); // 可以自己打印一下这个ref const computedStyle = getComputedStyle(node); // 样式集合 let width; // 对话框宽度 if (/px/g.test(computedStyle.width)) { // 如果宽度是像素类型 // 正则替换像素 width = Number(computedStyle.width.replace(/px/g, "")); } else { // 宽度为百分比类型 // 正则替换百分比并转化为数字格式 width = document.documentElement.clientWidth * (Number(computedStyle.width.replace(/%/g, "")) / 100); } const top = computedStyle.marginTop.replace(/px/g, ""); // 对话框距离顶部的距离 // 计算变换偏移 // element-plus 中,对话框默认是居中的 const transformLeft = triggerPosition.x - (document.documentElement.clientWidth - width) / 2; // 本质上为对话框左上角的 x 距离触发点 x 的距离 const transformTop = triggerPosition.y - Number(top); // 本质上为对话框左上角的 y 距离触发点 y 的距离 node.style.transformOrigin = `${transformLeft}px ${transformTop}px`; } } ); </script>
四、覆盖element-plus原有动画
如果仅仅只是想得到一个缩放效果,这里就足够了
-
通过阅读 element-plus 源码得知,内部利用
vue的transition组件实现动画,类名为dialog-fade -
我们首先定义几个动画,对应对话框的
出现和消失动画/* animation.scss */ @keyframes dialog-open { 0% { opacity: 0; transform: scale(0.2); } 100% { opacity: 1; transform: scale(1); } } @keyframes dialog-close { 0% { opacity: 1; transform: scale(1); } 100% { opacity: 0; transform: scale(0.2); } } // 遮罩层动画 @keyframes fade-out { 0% { opacity: 1; } 100% { opacity: 0; } } -
然后覆盖element-plus原有的动画,大功告成!
/* transition.scss */ .dialog-fade-enter-active { .el-dialog { animation: dialog-open 0.3s cubic-bezier(0.32, 0.14, 0.15, 0.86); } } .dialog-fade-leave-active { animation: fade-out 0.2s linear; .el-dialog { animation: dialog-close 0.2s cubic-bezier(0.78, 0.14, 0.15, 0.86); } }
功能拓展
1. 组件封装(代码地址)
对话框是中后台应用中常用的业务组件,因此我们将其可以封装成组件。
代码仓库 -> 传送门
2. 拖拽
element-plus 已经发布正式版2.0,他们为对话框添加了拖拽效果,不巧的是他们使用了transform来实现对话框的移动,这会与本文中的transform-origin冲突,因此本文中的设置方法不能将draggble设置为true,我会在下一篇文章中介绍如何用vue3的自定义指令实现拖拽
本帖未经允许禁止转载