[发财UI] Message组件的实现
前言
最近在实现Message组件,就是会从屏幕顶端弹出的一个小提醒,过一会儿就消失了。我个人非常喜欢这个设计,感觉在后续的复用性也很高,于是就打算自己手写一个作为发财UI的组件
支持的功能
目前的Message有四种类型:
-
普通提醒normal
![](https://img.bald3r.wang/img/normal.gif)
-
成功提醒success
![](https://img.bald3r.wang/img/success.gif)
-
警告提醒warning
![](https://img.bald3r.wang/img/warning.gif)
-
错误提醒error
![](https://img.bald3r.wang/img/error.gif)
同时还支持设置持续的时间:
![](https://img.bald3r.wang/img/10s.gif)
使用方法
是不是非常简单😉
1 2 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>`
|
1 2 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
类。
1 2 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
1 2 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样式
1 2 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树中。
1 2 3 4 5 6 7 8
| setTimeout(() => { msgDiv.classList.add('message-active') }, 0)
setTimeout(() => { msgDiv.classList.remove('message-active') }, closeDelay * 1);
|
如何将隐藏的Message从DOM树中移除
在Message的淡出动画结束后移除就好了,这里使用了.ontransitionend
API,但是还存在问题,即如果有多个Message,他们会同时消失,原因是虽然每个Message在创建时都会有一个计时器,但是在移除时却是所有的msgDiv一起移除,因此需要有区分的方法。
1 2 3 4 5 6 7
| setTimeout(() => { msgDiv.classList.remove('message-active') msgDiv.ontransitionend = () => { app.unmount(); div.remove(); } }, closeDelay * 1);
|
如何区分不同的Message?
在本项目中,我使用了随机生成ID的方式,如此一番就能精准的控制每个msgDiv
1 2 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
也一并移除
1 2 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组件时的所有思路,如果你有更好的想法,欢迎留言告诉我呀~