原生JavaScript实现消息弹窗插件:从样式到交互的完整教程

作者:木棉

时间:2026-10-23

浏览:144

本文详细讲解如何使用原生JavaScript结合CSS动画编写一个可复用的消息弹窗插件。内容涵盖消息结构的HTML/CSS样式定义、利用@keyframes实现淡入淡出与滑动效果,以及通过ES6 Class封装Message类,实现自动消失、手动关闭及消息队列管理等功能。

文章目录

需求分析与组件结构

1. 需求分析

当消息触发时,需执行自上而下的淡入效果。消息在持续显示指定时间后自动消失,或支持用户手动点击关闭按钮。消息消失时,执行自下而上的淡出效果。支持消息叠加弹出,最新消息位于列表末尾。当前方消息消失后,后续消息需具备向上滑动的过渡效果。消息组件由三部分构成:

消息图标,用于区分不同类型的提示;消息文本内容;关闭按钮,该按钮并非所有消息类型都必需。

CSS样式与动画定义

2. 样式实现

无论使用原生JavaScript还是Vue等框架,首先需定义消息的基本样式,随后通过JavaScript控制消息的弹出与关闭逻辑。因此,我们先构建HTML结构与CSS样式。


<!-- message.html -->
 <!-- 这个css是我引用阿里的一些字体图标,请戳: https://www.iconfont.cn/ -->
<link rel="stylesheet" href="http://at.alicdn.com/t/font_1117508_wxidm5ry7od.css">
<link rel="stylesheet" href="./message.css" rel="external nofollow" >
<script src="./message.js"></script>
 <!-- 消息外层容器,因为消息提醒基本上是全局的,所以这里用id,所有的弹出消息都是需要插入到这个容器里边的 -->
<div id="message-container">
 <div class="message">
 <!-- 消息图标 icon icon-success对应我的阿里字体图标的font-class -->
 <div class="type icon icon-success"></div>
 <!-- 消息文本 -->
 <div class="text">这是一条正经的消息~</div>
 <!-- 关闭按钮 -->
 <div class="close icon icon-close"></div>
 </div>
 <div class="message">
 <div class="type icon icon-error"></div>
 <div class="text">这是一条正经的消息~</div>
 </div>
</div>
/* message.css */
 #message-container {
 position: fixed;
 left: 0;
 top: 0;
 right: 0;
 /* 采用flex弹性布局,让容器内部的所有消息可以水平居中,还能任意的调整宽度 */
 display: flex;
 flex-direction: column;
 align-items: center;
}
#message-container .message {
 background: #fff;
 margin: 10px 0;
 padding: 0 10px;
 height: 40px;
 box-shadow: 0 0 10px 0 #eee;
 font-size: 14px;
 border-radius: 3px;
 /* 让消息内部的三个元素(图标、文本、关闭按钮)可以垂直水平居中 */
 display: flex;
 align-items: center;
}
#message-container .message .text {
 color: #333;
 padding: 0 20px 0 5px;
}
#message-container .message .close {
 cursor: pointer;
 color: #999;
}
 /* 给每个图标都加上不同的颜色,用来区分不同类型的消息 */
#message-container .message .icon-info {
 color: #0482f8;
}
#message-container .message .icon-error {
 color: #f83504;
}
#message-container .message .icon-success {
 color: #06a35a;
}
#message-container .message .icon-warning {
 color: #ceca07;
}
#message-container .message .icon-loading {
 color: #0482f8;
}

3. 动画效果实现

接下来实现消息的弹出与消失动画,这部分逻辑同样由CSS承担。

在CSS中实现自定义动画,需先使用@keyframes定义动画规则,再通过animation属性将规则应用于目标元素。动画规则本质上是动画序列或关键帧集合,关键帧内定义了需改变的CSS属性。当动画应用时,元素属性会根据关键帧进行相应变换。

首先使用@keyframes定义动画规则。


/* message.css */
 /* 这个动画规则我们就叫做message-move-in吧,随后我们会用animation属性在某个元素上应用这个动画规则。 */
@keyframes message-move-in {
 0% {
 /* 前边分析过了,弹出动画是一个自上而下的淡入过程 */
 /* 所以在动画初始状态要把元素的不透明度设置为0,在动画结束的时候再把不透明度设置1,这样就会实现一个淡入动画 */
 opacity: 0;
 /* 那么“自上而下”这个动画可以用“transform”变换属性结合他的“translateY”上下平移函数来完成 */
 /* translateY(-100%)表示动画初始状态,元素在实际位置上面“自身一个高度”的位置。 */
 transform: translateY(-100%);
 }
 100% {
 opacity: 1;
 /* 平移到自身位置 */
 transform: translateY(0);
 }
}

随后定义一个与message元素同级的move-in类,将message-move-in动画规则应用于该类。当需要弹出某条消息时,只需在消息元素上添加move-in类即可。


/* message.css */
 #message-container .message.move-in {
 /* animation属性是用来加载某个动画规则 请参考 https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation */
 animation: message-move-in 0.3s ease-in-out;
}

可见,只需在message元素上追加move-in类即可实现弹出动画。消失动画逻辑类似,但方向相反。


/* message.css */
 @keyframes message-move-out {
 0% {
 opacity: 1;
 transform: translateY(0);
 }
 100% {
 opacity: 0;
 transform: translateY(-100%);
 }
}
 #message-container .message.move-out {
 animation: message-move-out 0.3s ease-in-out;
 /* 让动画结束后保持结束状态 */
 animation-fill-mode: forwards;
}

animation-fill-mode: forwards;的作用是什么?因为动画结束后默认会恢复至元素初始状态,这会导致消息消失后重新出现。

因此,设置animation-fill-mode: forwards;是为了让动画结束后保持最终状态,即确保消息不再显示。

JavaScript插件核心逻辑

4. JavaScript插件开发

在编写JavaScript代码前,需思考插件的使用方式。假设插件提供一个名为Message的类,内部包含show方法。使用者实例化该类后调用show方法并传入参数即可弹出消息。此外,实例化的对象可以是全局唯一的。


<!-- message.html -->
<!-- 省略... -->
 <script>
// message可以定义为全局对象,项目中可以直接调用。
const message = new Message();
message.show({
 type: 'success',
 text: '点个关注不迷路~'
});
</script>

因此,需先编写Message类,并确保实现show方法。


/* message.js */
 class Message {
 constructor() {
 }
 show({ type = 'info', text = '' }) {
 }
}

此处直接使用ES6的class关键字,其底层仍基于原型链。使用class关键字可使类的结构更加直观。

根据第一部分分析,所有消息元素需在JavaScript中动态创建,使用者无需编写任何HTML代码。因此,在对象实例化new Message()时,需自动创建消息容器message-container。后续调用show方法时,直接将消息插入至message-container内部即可。


/* message.js */
 class Message {
 /**
 * 构造函数会在实例化的时候自动执行
 */
 constructor() {
 const containerId = 'message-container';
 // 检测下html中是否已经有这个message-container元素
 this.containerEl = document.getElementById(containerId);
 if (!this.containerEl) {
 // 创建一个Element对象,也就是创建一个id为message-container的dom节点
 this.containerEl = document.createElement('div');
 this.containerEl.id = containerId;
 // 把message-container元素放在html的body末尾
 document.body.appendChild(this.containerEl);
 }
 }
 show({ type = 'info', text = '' }) {
 }
}

这样,当调用const message = new Message()时,DOM中会自动插入一个message-container节点。接下来是最核心的show方法:

创建消息节点,并将其追加至message-container容器末尾。设定时间,在该时间结束后自动移除消息。监听“关闭按钮”的click事件,允许用户手动移除消息。我们将分步实现。

4.1 创建消息节点并追加至message-container容器末尾。


class Message {
 // 省略...
 show({ type = 'info', text = '' }) {
 // 创建一个Element对象
 let messageEl = document.createElement('div');
 // 设置消息class,这里加上move-in可以直接看到弹出效果
 messageEl.className = 'message move-in';
 // 消息内部html字符串
 messageEl.innerHTML = `
 <span class="icon icon-${type}"></span>
 <div class="text">${text}</div>
 <div class="close icon icon-close"></div>
 `;
 // 追加到message-container末尾
 // this.containerEl属性是我们在构造函数中创建的message-container容器
 this.containerEl.appendChild(messageEl);
 }

我们来测试调用效果。


<!-- message.html -->
<!-- 省略... -->
 <button class="btn">弹窗消息提醒</button>
 <script>
 // message可以定义为全局对象,项目中可以直接调用。
 const message = new Message();
 document.querySelector('.btn').addEventListener('click', () => {
 message.show({
 type: 'success',
 text: '点个关注不迷路~'
 });
 });
 </script>


4.2 设定时间,结束后自动移除消息。


// message.js
 class Message {
 // 省略...
 show({ type = 'info', text = '', duration = 2000 }) {
 // 省略...
 // 用setTimeout来做一个定时器
 setTimeout(() => {
 // Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除!
 messageEl.remove();
 }, duration);
 }
}

可见,消息在2秒后自动从DOM树中移除,但未显示动画。前文已定义move-out类,该类与message同级。现在只需在定时结束后将该类应用于message元素即可。


// message.js
 class Message {
 // 省略...
 show({ type = 'info', text = '', duration = 2000 }) {
 // 省略...
 // 用setTimeout来做一个定时器
 setTimeout(() => { // 首先把move-in这个弹出动画类给移除掉,要不然会有问题,可以自己测试下
 messageEl.className = messageEl.className.replace('move-in', ''); // 增加一个move-out类
 messageEl.className += 'move-out';
 // 这个地方是监听动画结束事件,在动画结束后把消息从dom树中移除。
 // 如果你是在增加move-out后直接调用messageEl.remove,那么你不会看到任何动画效果
 messageEl.addEventListener('animationend', () => {
 // Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除!
 messageEl.remove();
 });
 }, duration);
 }
}

4.3 监听“关闭按钮”的click事件,允许用户手动移除消息。

有时希望消息持续展示直至用户手动关闭,因此需添加参数以控制是否显示关闭按钮。


// message.js
 class Message {
 // 省略...
 show({ type = 'info', text = '', duration = 2000, closeable = false }) {
 // 创建一个Element对象
 let messageEl = document.createElement('div');
 // 设置消息class,这里加上move-in可以直接看到弹出效果
 messageEl.className = 'message move-in';
 // 消息内部html字符串
 messageEl.innerHTML = `
 <span class="icon icon-${type}"></span>
 <div class="text">${text}</div>
 `;
 // 是否展示关闭按钮
 if (closeable) {
 // 创建一个关闭按钮
 let closeEl = document.createElement('div');
 closeEl.className = 'close icon icon-close';
 // 把关闭按钮追加到message元素末尾
 messageEl.appendChild(closeEl);
 // 监听关闭按钮的click事件,触发后将调用我们的close方法
 // 我们把刚才写的移除消息封装为一个close方法
 closeEl.addEventListener('click', () => {
 this.close(messageEl)
 });
 }
 // 追加到message-container末尾
 // this.containerEl属性是我们在构造函数中创建的message-container容器
 this.containerEl.appendChild(messageEl);
 // 只有当duration大于0的时候才设置定时器,这样我们的消息就会一直显示
 if (duration > 0) {
 // 用setTimeout来做一个定时器
 setTimeout(() => {
 this.close(messageEl);
 }, duration);
 } 
 }
 /**
 * 关闭某个消息
 * 由于定时器里边要移除消息,然后用户手动关闭事件也要移除消息,所以我们直接把移除消息提取出来封装成一个方法
 * @param {Element} messageEl 
 */
 close(messageEl) {
 // 首先把move-in这个弹出动画类给移除掉,要不然会有问题,可以自己测试下
 messageEl.className = messageEl.className.replace('move-in', '');
 // 增加一个move-out类
 messageEl.className += 'move-out';
 // 这个地方是监听动画结束事件,在动画结束后把消息从dom树中移除。
 // 如果你是在增加move-out后直接调用messageEl.remove,那么你不会看到任何动画效果
 messageEl.addEventListener('animationend', () => {
 // Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除!
 messageEl.remove();
 });
 }
}

我们来测试调用效果。


<!-- message.html -->
<!-- 省略... -->
 <button class="btn">弹窗消息提醒</button>
 <script>
 // message可以定义为全局对象,项目中可以直接调用。
 const message = new Message();
 document.querySelector('.btn').addEventListener('click', () => {
 message.show({
 type: 'warning',
 text: '点我旁边的叉叉试试',
 duration: 0, // 不会自动消失
 closeable: true, // 可手动关闭
 });
 });
 </script>

代码已基本完成,但仍存在小问题:当弹出两条或多条消息时,若前方消息消失,下方消息会直接跳至上方位置,缺乏滑动过渡,显得生硬。

可通过CSS的transition属性使message的高度逐渐变小,从而使下方元素根据高度变化逐渐上移。


/* message.css */
/* 省略... */
 #message-container .message {
 background: #fff;
 margin: 10px 0;
 padding: 0 10px;
 height: 40px;
 box-shadow: 0 0 10px 0 #ccc;
 font-size: 14px;
 border-radius: 3px;
 /* 让消息内部的三个元素(图标、文本、关闭按钮)可以垂直水平居中 */
 display: flex;
 align-items: center;
/* 增加一个过渡属性,当message元素的高度和margin变化时候将会有一个过渡动画 */
 transition: height 0.2s ease-in-out, margin 0.2s ease-in-out;
} 
/* 省略... */

随后只需在Message类的close方法中进行相应调整:


 close(messageEl) {
 // 首先把move-in这个弹出动画类给移除掉,要不然会有问题,可以自己测试下
 messageEl.className = messageEl.className.replace('move-in', '');
 // 增加一个move-out类
 messageEl.className += 'move-out';
 // move-out动画结束后把元素的高度和边距都设置为0
 // 由于我们在css中设置了transition属性,所以会有一个过渡动画
 messageEl.addEventListener('animationend', () => {
 messageEl.setAttribute('style', 'height: 0; margin: 0');
 });
 // 这个地方是监听transition的过渡动画结束事件,在动画结束后把消息从dom树中移除。
 messageEl.addEventListener('transitionend', () => {
 // Element对象内部有一个remove方法,调用之后可以将该元素从dom树种移除!
 messageEl.remove();
 });
 }

兼容性处理与扩展思考

结语 至此,基本功能已实现。为确保各浏览器兼容性,建议使用Babel进行代码转码;若需发布,可使用Webpack将JavaScript与CSS打包。此外,还需考虑一个场景:当前消息关闭均由对象内部调用close方法实现,若需外部控制消息关闭(例如请求服务器时弹出loading消息,服务器返回数据后如何关闭该消息),则需额外处理。具体实现留给读者自行完成。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章