源码
在线预览
功能分析
参考 ElementPlus 官网给出的例子,我们的 Message 组件至少需要:
- 自定义各种类型的 Message(error、info、success 等)
- 提供静态方法调用,如 ()
- 进入、退出动画;Message 消失后上移动画
Message 组件
样式方面 Messge 组件非常简单,就是个 fixed 居中的 div,而进入、退出动画可以通过 transition 实现,消失可以通过 props 调用回调通知调用者。
<template>
<transition @before-leave="onClose" @after-leave="onDestroy" name="message-fade">
<div v-show="visiable" :class="messageStyle" :style="{ top: `${top}px` }">
<span>{{ message }}</span>
</div>
</transition>
</template>
<script setup>
import types from './types'
import { onMounted, ref, computed } from 'vue'
const props = defineProps({
type: {
type: String,
default: 'info',
validator(value) {
return Object.values(types).includes(value)
}
},
top: {
type: Number,
default: 20
},
message: {
type: String,
required: true
},
duration: {
type: Number,
default: 3000
},
onDestroy: Function,
onClose: Function
})
const visiable = ref(false)
const messageStyle = computed(() => ['message', props.type])
const close = () => {
visiable.value = false
}
onMounted(() => {
visiable.value = true
})
setTimeout(close, props.duration)
</script>
<style scoped>
.message {
@apply fixed z-50 left-1/2 rounded px-4 py-2 transform -translate-x-1/2 min-w-3/10 transition-all
}
. {
color: #73767a;
background: #f4f4f5;
}
. {
color: #c45656;
background: #fde2e2;
}
. {
color: #529b2e;
background: #e1f3d8;
}
. {
color: #b88230;
background: #faecd8;
}
.message-fade-enter-from,
.message-fade-leave-to {
@apply transform -translate-x-1/2 -translate-y-20px opacity-0
}
显示 Message 的逻辑实现
把组件挂载到 DOM 上还是用 h + render 方式,这里主要的问题是当前一个 Message 消失后如何通知后面的 Message 上移。具体实现也不难,我们需要
- 创建一个数组保存所有正在显示的 Message
- 当 Message close 回调触发时,找到被 close 的 Message,从数组中删除,并且从该元素的下标开始依次处理后序 Message 的 top
有了 Message 实例数组后,我们还可以轻松的计算出 Message 的初始显示高度。
import { h, render } from 'vue'
import types from './types'
import MessageCpn from './'
const instances = []
function Message(options) {
let top = 20;
instances.forEach(vm => {
top += (vm.el.offsetHeight + 16) || 16
});
const container = document.createDocumentFragment()
const vm = h(MessageCpn, {
...options,
top,
onClose() {
close(vm)
},
onDestroy() {
render(null, container)
}
})
render(vm, container)
document.body.appendChild(container)
instances.push(vm)
}
function close(vm) {
const index = instances.findIndex(ins => ins === vm)
if (index === -1) {
return;
}
instances.splice(index, 1)
for (let start = index; start < instances.length; start++) {
const cpn = instances[start].component
cpn.props.top -= vm.el.offsetHeight + 16
}
}
Object.values(types).forEach(type => {
Message[type] = (options) => {
options.type = type;
return Message(options)
}
})
export default Message