如何用原生js写一个弹窗消息提醒插件
嗨,这里是芝麻,今天我们一块来做一个“弹窗消息提醒”插件。

喏,就是这么一个效果。
1. 分析
- 当消息被触发的时候,会有一个自上而下的淡入过程。
- 在持续了一段时间后会自动的消失,或者是需要用户来手动的点击关闭按钮。
- 在消息消失的时候,会有一个自下而上的淡出过程。
- 消息是可以叠加弹出的,最新的消息会排在消息列表的最后面。
- 当前面的消息消失后,后面的消息会有一个向上滑动效果。
然后消息本身是有三部分组成
- 消息图标,用来区分不同类型的消息。
- 消息文本。
- 关闭按钮,并不是所有消息都需要关闭按钮。
2. 实现样式
那么,不管我们是用原生js还是vue,首先呢,我们都需要把这个消息的基本样式给写出来,然后再通过js来控制消息的弹出和关闭。
所以,我们先来写html和css。
1 <!-- message.html --> 2 3 <!-- 这个css是我引用阿里的一些字体图标,请戳: https://www.iconfont.cn/ --> 4 <link rel="stylesheet" href="http://at.alicdn.com/t/font_1117508_wxidm5ry7od.css"> 5 <link rel="stylesheet" href="./message.css"> 6 <script src="./message.js"></script> 7 8 <!-- 消息外层容器,因为消息提醒基本上是全局的,所以这里用id,所有的弹出消息都是需要插入到这个容器里边的 --> 9 <div id="message-container"> 10 <div class="message"> 11 <!-- 消息图标 icon icon-success对应我的阿里字体图标的font-class --> 12 <div class="type icon icon-success"></div> 13 <!-- 消息文本 --> 14 <div class="text">这是一条正经的消息~</div> 15 <!-- 关闭按钮 --> 16 <div class="close icon icon-close"></div> 17 </div> 18 <div class="message"> 19 <div class="type icon icon-error"></div> 20 <div class="text">这是一条正经的消息~</div> 21 </div> 22 </div>
1 /* message.css */ 2 3 #message-container { 4 position: fixed; 5 left: 0; 6 top: 0; 7 right: 0; 8 9 /* 采用flex弹性布局,让容器内部的所有消息可以水平居中,还能任意的调整宽度 */ 10 display: flex; 11 flex-direction: column; 12 align-items: center; 13 } 14 #message-container .message { 15 background: #fff; 16 margin: 10px 0; 17 padding: 0 10px; 18 height: 40px; 19 box-shadow: 0 0 10px 0 #eee; 20 font-size: 14px; 21 border-radius: 3px; 22 23 /* 让消息内部的三个元素(图标、文本、关闭按钮)可以垂直水平居中 */ 24 display: flex; 25 align-items: center; 26 } 27 #message-container .message .text { 28 color: #333; 29 padding: 0 20px 0 5px; 30 } 31 #message-container .message .close { 32 cursor: pointer; 33 color: #999; 34 } 35 36 /* 给每个图标都加上不同的颜色,用来区分不同类型的消息 */ 37 #message-container .message .icon-info { 38 color: #0482f8; 39 } 40 #message-container .message .icon-error { 41 color: #f83504; 42 } 43 #message-container .message .icon-success { 44 color: #06a35a; 45 } 46 #message-container .message .icon-warning { 47 color: #ceca07; 48 } 49 #message-container .message .icon-loading { 50 color: #0482f8; 51 }
大概是这么一个效果:

3. 实现动画
接下来要做的就是这个消息的弹出和消失动画,我们还是用css来实现。
想要在css里边实现自定义的动画,首先需要用@keyframes来定义一个动画规则,然后再通过animation属性把动画应用到某个元素上就可以了。
所谓的动画规则其实就是一个动画序列,或者可以理解为一个个的关键帧,而关键帧的内部就是你想改变的css属性,你可以在关键帧里边写上几乎任何的css属性,当动画被应用的时候,这些css属性就会根据各个关键帧做出相应的变换。
那我们先用@keyframes来写一个动画规则吧
1 /* message.css */ 2 3 /* 这个动画规则我们就叫做message-move-in吧,随后我们会用animation属性在某个元素上应用这个动画规则。 */ 4 @keyframes message-move-in { 5 0% { 6 /* 前边分析过了,弹出动画是一个自上而下的淡入过程 */ 7 /* 所以在动画初始状态要把元素的不透明度设置为0,在动画结束的时候再把不透明度设置1,这样就会实现一个淡入动画 */ 8 opacity: 0; 9 /* 那么“自上而下”这个动画可以用“transform”变换属性结合他的“translateY”上下平移函数来完成 */ 10 /* translateY(-100%)表示动画初始状态,元素在实际位置上面“自身一个高度”的位置。 */ 11 transform: translateY(-100%); 12 } 13 100% { 14 opacity: 1; 15 /* 平移到自身位置 */ 16 transform: translateY(0); 17 } 18 }
然后我们再定义一个和message元素同级的类move-in,把message-move-in这个动画规则给应用到move-in类上,这样我们需要让哪个消息弹出,就只需要在消息的类上加一个move-in就行。
1 /* message.css */ 2 3 #message-container .message.move-in { 4 /* animation属性是用来加载某个动画规则 请参考 https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation */ 5 animation: message-move-in 0.3s ease-in-out; 6 }
我们来看下怎么用这个move-in:

可以看到,只需要在某个message上追加一个move-in就能实现弹出动画。
那么,消失动画也是一个套路,只不过跟弹出动画反过来而已。
1 /* message.css */ 2 3 @keyframes message-move-out { 4 0% { 5 opacity: 1; 6 transform: translateY(0); 7 } 8 100% { 9 opacity: 0; 10 transform: translateY(-100%); 11 } 12 } 13 14 #message-container .message.move-out { 15 animation: message-move-out 0.3s ease-in-out; 16 /* 让动画结束后保持结束状态 */ 17 animation-fill-mode: forwards; 18 }
animation-fill-mode: forwards;这个是干嘛的呢?因为动画结束后默认会回到元素的最初状态,在这里表现的是消失后又出现了,如图:

所以animation-fill-mode: forwards;是为了让动画结束后保持这个结束状态,也就是不在显示了。

4. 编写js插件
那么,在写js之前呢,我们先来思考一下,如果你是插件的使用者,你想怎么来调用这个插件?
我们的插件很简单,就是在需要的时候弹出一个消息,假设插件他提供给我们的是一个类,就叫做Message吧,并且他内部有一个show方法,那么只要使用者实例化这个类后,调用他的show方法,然后传入不同的参数就可以弹出一个消息了。而且我们所实例化的对象可以是全局唯一的。
1 <!-- message.html --> 2 <!-- 省略... --> 3 4 <script> 5 // message可以定义为全局对象,项目中可以直接调用。 6 const message = new Message(); 7 message.show({ 8 type: 'success', 9 text: '点个关注不迷路~' 10 }); 11 </script>
所以呢,我们要先写一个Message类,并且必须要实现一个show方法。
1 /* message.js */ 2 3 class Message { 4 constructor() { 5 6 } 7 8 show({ type = 'info', text = '' }) { 9 10 } 11 }
这里我直接用了es6的class关键词,其实他的内部还是原型链的形式。用class呢,可以让我们更直观的了解这个类。
根据我们在第一部分的分析,所有的消息元素都是需要在js中创建的,所以我们不需要使用者来写任何html代码,那么我们只需要在对象被实例化new Message()的时候,就去创建消息容器message-container,后续在调用show方法时候,直接把消息插入到message-container内部即可。
1 /* message.js */ 2 3 class Message { 4 5 /** 6 * 构造函数会在实例化的时候自动执行 7 */ 8 constructor() { 9 const containerId = 'message-container'; 10 // 检测下html中是否已经有这个message-container元素 11 this.containerEl = document.getElementById(containerId); 12 13 if (!this.containerEl) { 14 // 创建一个Element对象,也就是创建一个id为message-container的dom节点 15 this.containerEl = document.createElement('div'); 16 this.containerEl.id = containerId; 17 // 把message-container元素放在html的body末尾 18 document.body.appendChild(this.containerEl); 19 } 20 } 21 22 show({ type = 'info', text = '' }) { 23 24 } 25 }
这样,我们调用const message = new Message()的时候会在dom中自动的插入一个message-container节点。
那么,最重要的还是我们的show方法:
- 创建一个消息节点,并把它追加到
message-container容器的末尾。- 设定一个时间,在这个时间结束后自动的将消息移除。
- 监听“关闭按钮”的
click事件,来让用户可以手动的移除消息。
我们一步一步来。
4.1 创建一个消息节点,并把它追加到message-container容器的末尾。
1 class Message { 2 3 // 省略... 4 5 show({ type = 'info', text = '' }) { 6 // 创建一个Element对象 7 let messageEl = document.createElement('div'); 8 // 设置消息class,这里加上move-in可以直接看到弹出效果 9 messageEl.className = 'message move-in'; 10 // 消息内部html字符串 11 messageEl.innerHTML = ` 12 <span class="icon icon-${type}"></span> 13 <div class="text">${text}</div> 14 <div class="close icon icon-close"></div> 15 `; 16 // 追加到message-container末尾 17 // this.containerEl属性是我们在构造函数中创建的message-container容器 18 this.containerEl.appendChild(messageEl); 19 } 20 }
我们来调用下试试~
1 <!-- message.html --> 2 <!-- 省略... --> 3 4 5 <button class="btn">弹窗消息提醒</button> 6 7 <script> 8 // message可以定义为全局对象,项目中可以直接调用。 9 const message = new Message(); 10 document.querySelector('.btn').addEventListener('click', () => { 11 message.show({ 12 type: 'success', 13 text: '点个关注不迷路~' 14 }); 15 }); 16 17 </script>

4.2 设定一个时间,在这个时间结束后自动的将消息移除。
1 // message.js 2 3 class Message { 4 5 // 省略... 6 7 show({ type = 'info', text = '', duration = 2000 }) { 8 // 省略... 9 10 // 用setTimeout来做一个定时器 11 setTimeout(() => { 12 // Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除! 13 messageEl.remove(); 14 }, duration); 15 } 16 }

可以看到,消息在过了2秒后,自动的从dom树中移除了,不过呢并没有动画,还记得前边我们写了move-out类吗?这个类和message是同级的。现在我们只需要在定时结束后把这个类应用到message元素上就行。
1 // message.js 2 3 class Message { 4 5 // 省略... 6 7 show({ type = 'info', text = '', duration = 2000 }) { 8 // 省略... 9 10 // 用setTimeout来做一个定时器 11 setTimeout(() => { 12 // 首先把move-in这个弹出动画类给移除掉,要不然会有问题,可以自己测试下 13 messageEl.className = messageEl.className.replace('move-in', ''); 14 // 增加一个move-out类 15 messageEl.className += 'move-out'; 16 17 // 这个地方是监听动画结束事件,在动画结束后把消息从dom树中移除。 18 // 如果你是在增加move-out后直接调用messageEl.remove,那么你不会看到任何动画效果 19 messageEl.addEventListener('animationend', () => { 20 // Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除! 21 messageEl.remove(); 22 }); 23 }, duration); 24 } 25 }
注意观察dom树的变化:

4.3 监听“关闭按钮”的click事件,来让用户可以手动的移除消息。
有时候呢,我们希望消息能够一直展示,直到用户来手动的关闭掉,那么首先我们要加一个参数,用来控制是否展示这个关闭按钮。
1 // message.js 2 3 class Message { 4 5 // 省略... 6 7 show({ type = 'info', text = '', duration = 2000, closeable = false }) { 8 // 创建一个Element对象 9 let messageEl = document.createElement('div'); 10 // 设置消息class,这里加上move-in可以直接看到弹出效果 11 messageEl.className = 'message move-in'; 12 // 消息内部html字符串 13 messageEl.innerHTML = ` 14 <span class="icon icon-${type}"></span> 15 <div class="text">${text}</div> 16 `; 17 18 // 是否展示关闭按钮 19 if (closeable) { 20 // 创建一个关闭按钮 21 let closeEl = document.createElement('div'); 22 closeEl.className = 'close icon icon-close'; 23 // 把关闭按钮追加到message元素末尾 24 messageEl.appendChild(closeEl); 25 26 // 监听关闭按钮的click事件,触发后将调用我们的close方法 27 // 我们把刚才写的移除消息封装为一个close方法 28 closeEl.addEventListener('click', () => { 29 this.close(messageEl) 30 }); 31 } 32 33 // 追加到message-container末尾 34 // this.containerEl属性是我们在构造函数中创建的message-container容器 35 this.containerEl.appendChild(messageEl); 36 37 // 只有当duration大于0的时候才设置定时器,这样我们的消息就会一直显示 38 if (duration > 0) { 39 // 用setTimeout来做一个定时器 40 setTimeout(() => { 41 this.close(messageEl); 42 }, duration); 43 } 44 } 45 46 /** 47 * 关闭某个消息 48 * 由于定时器里边要移除消息,然后用户手动关闭事件也要移除消息,所以我们直接把移除消息提取出来封装成一个方法 49 * @param {Element} messageEl 50 */ 51 close(messageEl) { 52 // 首先把move-in这个弹出动画类给移除掉,要不然会有问题,可以自己测试下 53 messageEl.className = messageEl.className.replace('move-in', ''); 54 // 增加一个move-out类 55 messageEl.className += 'move-out'; 56 57 // 这个地方是监听动画结束事件,在动画结束后把消息从dom树中移除。 58 // 如果你是在增加move-out后直接调用messageEl.remove,那么你不会看到任何动画效果 59 messageEl.addEventListener('animationend', () => { 60 // Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除! 61 messageEl.remove(); 62 }); 63 } 64 }
我们来调用下试试~
1 <!-- message.html --> 2 <!-- 省略... --> 3 4 5 <button class="btn">弹窗消息提醒</button> 6 7 <script> 8 // message可以定义为全局对象,项目中可以直接调用。 9 const message = new Message(); 10 document.querySelector('.btn').addEventListener('click', () => { 11 message.show({ 12 type: 'warning', 13 text: '点我旁边的叉叉试试', 14 duration: 0, // 不会自动消失 15 closeable: true, // 可手动关闭 16 }); 17 }); 18 19 </script>

其实已经写的差不多了,不过还是有一些小问题,比如当我们弹出两个甚至更多消息的时候,如果前边的消息消失后,下面的消息会直接跳到上面的位置,很僵硬,没有任何的滑动,如图:

我们可以通过css的transition属性来让meesage的高度逐渐变小,这样下面的元素就会根据变化来逐渐上移。
1 /* message.css */ 2 /* 省略... */ 3 4 #message-container .message { 5 background: #fff; 6 margin: 10px 0; 7 padding: 0 10px; 8 height: 40px; 9 box-shadow: 0 0 10px 0 #ccc; 10 font-size: 14px; 11 border-radius: 3px; 12 13 /* 让消息内部的三个元素(图标、文本、关闭按钮)可以垂直水平居中 */ 14 display: flex; 15 align-items: center; 16 17 /* 增加一个过渡属性,当message元素的高度和margin变化时候将会有一个过渡动画 */ 18 transition: height 0.2s ease-in-out, margin 0.2s ease-in-out; 19 } 20 21 /* 省略... */
然后我们只需要在Message类的close方法中做一下改变:
1 close(messageEl) { 2 // 首先把move-in这个弹出动画类给移除掉,要不然会有问题,可以自己测试下 3 messageEl.className = messageEl.className.replace('move-in', ''); 4 // 增加一个move-out类 5 messageEl.className += 'move-out'; 6 7 // move-out动画结束后把元素的高度和边距都设置为0 8 // 由于我们在css中设置了transition属性,所以会有一个过渡动画 9 messageEl.addEventListener('animationend', () => { 10 messageEl.setAttribute('style', 'height: 0; margin: 0'); 11 }); 12 13 // 这个地方是监听transition的过渡动画结束事件,在动画结束后把消息从dom树中移除。 14 messageEl.addEventListener('transitionend', () => { 15 // Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除! 16 messageEl.remove(); 17 }); 18 }
看效果:

结尾
好了,基本上已经写好了,不过为了各个浏览器的兼容性,建议大家用babel转码,如果想发布,可以用webpack把js和css一块打包。
不过我们还是少考虑了一个场景,现在的关闭消息都是对象内部调用close方法来实现,如果我们希望外部能够控制消息的关闭呢,比如我请求服务器时候弹出一个loading的消息,现在服务器返回数据后,我怎么来关闭这个消息呢。
很简单,各位自行实现吧!

浙公网安备 33010602011771号