[发财UI] Message组件的实现
前言
最近在实现Message组件,就是会从屏幕顶端弹出的一个小提醒,过一会儿就消失了。我个人非常喜欢这个设计,感觉在后续的复用性也很高,于是就打算自己手写一个作为发财UI的组件
支持的功能
目前的Message有四种类型:
- 
普通提醒normal  
 
- 
成功提醒success  
 
- 
警告提醒warning  
 
- 
错误提醒error  
 
同时还支持设置持续的时间:

使用方法
是不是非常简单😉
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | <template><div>
 <button @click="popNormalMsg">打开一个普通提醒</button>
 </div>
 </template>
 
 <script lang="ts">
 import {popMessage} from "../../lib/popMessage";
 
 export default {
 name: "Message1.demo",
 components: {Button},
 setup() {
 const popNormalMsg = () => {
 popMessage({
 message: '这是一个全局显示的普通提醒',
 msgType: 'normal',
 closeDelay: '2000',
 })
 }
 return {popNormalMsg}
 }
 }
 </script>
 
 | 
实现过程
如何实现不同类型的切换?
其实切换类型只是切换图标而已哈哈哈
这里使用了IconPark图标库,这里使用了一个投机取巧的办法,把不同的图标命名为相应的type,可以节省一些处理的步骤
| href=#warning | msgType='warning' | 
| href=#error | msgType='error' | 
| 1
 | const typeIndicator = `<use href="#${props.msgType}"></use>`
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 
 | <template><div ref="msgDiv" class="rich-message">
 <svg class="iconpark-icon" v-html="typeIndicator">
 //2️⃣typeIndicator的内容会原封不动的跑到这里
 </svg>
 //3️⃣最后和svg标签一起变成type对应的图标
 <div class="rich-message-msgText">{{ message }}</div>
 </div>
 </template>
 
 <script lang="ts">
 
 export default {
 name: "Message",
 props: {
 message: {
 type: String,
 required: true,
 },
 msgType: {
 type: String,
 default: 'normal',
 },
 },
 setup(props) {
 const typeIndicator = `<use href="#${props.msgType}"></use>`
 return {typeIndicator}
 
 }
 }
 </script>
 
 
 | 
如何实现Message的弹出和消失?
使用了CSS的transform,实际上就是Message在初始状态下是藏在画面外的,通过添加一个.message-active的类来让它显示出来,在经过closeDelay毫秒后移除.message-active类。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | .rich-message {...
 transform: translateY(-100px);
 transition: all 250ms;
 
 &.message-active {
 transform: translateY(0px);
 opacity: 1;
 }
 }
 
 | 
如何实现往下排列而非堆叠?
为了让他们能够一个一个的排列下来而不是堆叠在一起,我想到了insertAdjacentElement()方法
| 1
 | element.insertAdjacentElement(position, element);
 | 
position有下面四种取值
- 
'beforebegin': 在该元素本身的前面。
 
- 
'afterbegin':只在该元素当中,在该元素第一个子孩子前面。
 
- 
'beforeend':只在该元素当中,在该元素最后一个子孩子后面。
 
- 
'afterend': 在该元素本身的后面。
 
不难发现这里似乎可以使用beforeend和afterend。经过我的思考,为了保持DOM树的整洁,我采用了创建一个msgContainer的div来存放所有的Message的方法,因此我也相应的使用了beforeend
| 12
 3
 4
 5
 6
 7
 8
 
 | let msgContainer = document.getElementById('msgDiv')if (msgContainer === null) {
 msgContainer = document.createElement('div')
 msgContainer.id = 'msgDiv'
 document.body.appendChild(msgContainer)
 }
 const div = document.createElement('div');
 msgContainer.insertAdjacentElement('beforeend', div)
 
 | 
给msgContainer一个CSS样式
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | #msgDiv {position: absolute;
 top: 0;
 left: 50%;
 transform: translateX(-50%);
 
 display: flex;
 flex-direction: column;
 align-items: center;
 justify-content: center;
 }
 
 | 
如何实现添加和移除.message-active类?
如果msgDiv在创建时就带有.message-active类,那么将会闪现在页面中,所以msgDiv应该是在渲染后被添加了.message-active类,为了实现这个效果,使用了一个setTimeout()。
同时在closeDelay之后将这个类移除。
但是这样存在一个问题,这个msgDiv只是看不见了,依然存在于DOM树中。
| 12
 3
 4
 5
 6
 7
 8
 
 | setTimeout(() => {msgDiv.classList.add('message-active')
 }, 0)
 
 setTimeout(() => {
 msgDiv.classList.remove('message-active')
 }, closeDelay * 1);
 
 
 | 
如何将隐藏的Message从DOM树中移除
在Message的淡出动画结束后移除就好了,这里使用了.ontransitionendAPI,但是还存在问题,即如果有多个Message,他们会同时消失,原因是虽然每个Message在创建时都会有一个计时器,但是在移除时却是所有的msgDiv一起移除,因此需要有区分的方法。
| 12
 3
 4
 5
 6
 7
 
 | setTimeout(() => {msgDiv.classList.remove('message-active')
 msgDiv.ontransitionend = () => {
 app.unmount();
 div.remove();
 }
 }, closeDelay * 1);
 
 | 
如何区分不同的Message?
在本项目中,我使用了随机生成ID的方式,如此一番就能精准的控制每个msgDiv
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
 | function randomLetter(len) {let str = '';
 for (let i = 0; i < len; i++) {
 str += String.fromCharCode(~~(Math.random() * 26 + 65));
 }
 return str;
 }
 
 const msgId = randomLetter(~~(Math.random() * 10 + 30))
 const app = createApp({
 render() {
 return h(Message, {
 message,
 msgType,
 id: msgId,
 });
 }
 });
 app.mount(div);
 
 const msgDiv = document.getElementById(String(msgId))
 
 setTimeout(() => {
 msgDiv.classList.add('message-active')
 }, 0)
 
 setTimeout(() => {
 msgDiv.classList.remove('message-active')
 msgDiv.ontransitionend = () => {
 app.unmount();
 div.remove();
 }
 }, closeDelay * 1);
 
 
 | 
最后的一个小细节
我们使用了一个msgContainer将所有的Message包裹的起来,从而实现顺序排列,但是在最后一个Message消失后,msgContainer会作为一个空的div仍然存在于DOM树中,这很不环保,因此在最后一个Message消失后将msgContainer也一并移除
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | setTimeout(() => {msgDiv.classList.remove('message-active')
 msgDiv.ontransitionend = () => {
 app.unmount();
 div.remove();
 if (msgContainer.children.length === 0) {
 msgContainer.remove()
 }
 }
 }, closeDelay * 1);
 
 | 
后记
以上就是我写Message组件时的所有思路,如果你有更好的想法,欢迎留言告诉我呀~