【Vue】Element-Plus 源码学习笔记——实现一个基本的 ElMessage 组件
先贴下 Element-Plus ElMessage 源码的网址。我们需要实现的效果类似 ElMessage,即能够显示多个消息、上一个消息消失下面的消息会自动往上移动、进入移出动画、自定义消息和持续时间。其他选项这里不考虑。大体思路我们的 ElMessage 组件通过函数调用动态显示,无需事先在页面中放入组件,这样能更灵活也更方便。实现的基本思路是使用函数动态渲染组件到页面上。createVNo
先贴下 Element-Plus ElMessage 源码的网址。我们需要实现的效果类似 ElMessage,即能够显示多个消息、上一个消息消失下面的消息会自动往上移动、进入移出动画、自定义消息和持续时间。其他选项这里不考虑。
大体思路
我们的 ElMessage 组件通过函数调用动态显示,无需事先在页面中放入组件,这样能更灵活也更方便。实现的基本思路是使用函数动态渲染组件到页面上。
createVNode 和 render
实际上 createVNode
是 h
函数的别名,这两个函数的功能是创建一个 VNode,这个 VNode 可以理解为是 DOM 的描述,当我们要渲染 VNode 到真正的页面时,就要用到 render 函数。我们可以封装一个 MessageBox.vue
的组件,然后通过 createVNode
创建对应组件的 VNode,用 render 把组件渲染到页面上。
message.vue
<template>
<transition name="fade" @before-leave="onClose" @after-leave="onDestroy">
<div :style="{ top: topOffset + 'px' }" class="message" v-if="visiable">
{{ message }}
</div>
</transition>
</template>
<script>
export default {
props: {
topOffset: {
type: Number,
required: true,
},
message: {
type: String,
required: true,
},
duration: {
type: Number,
default: 3000,
},
id: {
type: Number,
required: true,
},
onDestroy: {
type: Function,
required: true,
},
onClose: {
type: Function,
required: true,
},
},
data() {
return {
visiable: false,
};
},
methods: {
close() {
this.visiable = false;
},
},
mounted() {
this.visiable = true;
setTimeout(() => {
this.close();
}, this.duration);
},
};
</script>
<style scoped>
.message {
position: fixed;
left: 50%;
transform: translate(-50%, 10px);
background-color: burlywood;
padding: 0.5rem;
width: 300px;
z-index: 10001;
transition: top 0.4s;
}
.fade-enter-active,
.fade-leave-active {
transition: transform 0.4s, opacity 0.4s;
}
.fade-enter-from {
transform: translate(-50%, 0);
opacity: 0;
}
.fade-leave-to {
transform: translate(-50%, -10px) !important;
opacity: 0;
}
</style>
组件的基本样式这里不进行讲解,我们把重点放到 transition 动画上面。
v-enter-active、v-leave-active
这两个属性在整个动画期间都会生效,一般用来写动画的过渡属性 transition
。
v-enter-to、v-leave-to
在进入过渡触发前、离开过渡被触发后生效。
v-enter-from、v-leave-from
定义进入、离开过渡的初始状态。
在 message 进入时我们想显示一个 message 向下移动的动画,于是给 message 设置了默认的 translateY(10px)。而 v-enter-from 触发后,translateY(0) 生效,下一帧被移除,于是会执行 translateY(0px) -> translateY(10px) 的动画,达到效果。
显示元素 message.js
import {
remove
} from '@vue/shared';
import {
createVNode,
render
} from 'vue'
import MessageBox from '../components/MessageBox.vue'
let seed = 0;
const instance = [];
const appendTo = document.body;
export default function (message, duration) {
let topOffset = 20;
const container = document.createElement('div');
// 计算当前元素距离顶部的偏移量
instance.forEach(vm => {
topOffset += (vm.el.offsetHeight || 0) + 16;
});
// 保存 id;这行代码是必要的,因为当关闭定时器触发的时候,seed 的值为最后一次增加的值,不保存直接用 seed 会出错
const id = seed;
const vm = createVNode(MessageBox, {
id,
message,
duration,
topOffset,
// 组件销毁时触发的回调
onDestroy() {
render(null, container);
container.remove();
},
// 组件关闭时触发的回调
// 这个回调用于显示组件的移出动画,和 onDestroy 不冲突
onClose() {
close(id)
}
})
// 渲染组件到 container 上
render(vm, container);
// 添加 container 到 body
appendTo.appendChild(container);
// 保存组件实例,销毁时会用到
instance.push(vm);
seed++;
}
const close = (id) => {
const idx = instance.findIndex(vm => vm.props.id === id);
if (idx === -1) {
return;
}
const vm = instance[idx];
const removedHeight = vm.el.offsetHeight;
instance.splice(idx, 1);
for (let i = idx; i < instance.length; i++) {
// 直接赋值组件的 top 为减去移除组件后的高度
const pos = parseInt(instance[i].el.style['top']) - removedHeight - 16;
// topOffset = topOffset - removedHeight - 16 同理
instance[i].component.props.topOffset = pos;
}
}
App.vue 测试显示组件
<script setup>
import message from "./hooks/useMessageBox";
import { ref } from "vue";
const count = ref(1);
</script>
<template>
<button @click="message('lala' + count, 2000)">
show message box
</button>
</template>
<style></style>```
更多推荐
所有评论(0)