前端小技巧:用原生js寫一個彈窗消息提醒插件

嗨,這裏是芝麻,今天咱們一塊來作一個「彈窗消息提醒」插件。 css

彈窗消息演示
喏,就是這麼一個效果。

1. 分析

  1. 當消息被觸發的時候,會有一個自上而下淡入過程。
  2. 在持續了一段時間後會自動的消失,或者是須要用戶來手動的點擊關閉按鈕
  3. 在消息消失的時候,會有一個自下而上淡出過程。
  4. 消息是能夠疊加彈出的,最新的消息會排在消息列表的最後面
  5. 當前面的消息消失後,後面的消息會有一個向上滑動效果。

而後消息自己是有三部分組成html

  1. 消息圖標,用來區分不一樣類型的消息。
  2. 消息文本。
  3. 關閉按鈕,並非全部消息都須要關閉按鈕。

2. 實現樣式

那麼,無論咱們是用原生js仍是vue,首先呢,咱們都須要把這個消息的基本樣式給寫出來,而後再經過js來控制消息的彈出和關閉。 因此,咱們先來寫html和css。vue

<!-- 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;
}
複製代碼

大概是這麼一個效果webpack

初始效果

3. 實現動畫

接下來要作的就是這個消息的彈出消失動畫,咱們仍是用css來實現。es6

想要在css裏邊實現自定義的動畫,首先須要用@keyframes來定義一個動畫規則,而後再經過animation屬性把動畫應用到某個元素上就能夠了。web

所謂的動畫規則其實就是一個動畫序列,或者能夠理解爲一個個的關鍵幀,而關鍵幀的內部就是你想改變的css屬性,你能夠在關鍵幀裏邊寫上幾乎任何的css屬性,當動畫被應用的時候,這些css屬性就會根據各個關鍵幀作出相應的變換。瀏覽器

那咱們先用@keyframes來寫一個動畫規則吧bash

/* 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-inbabel

應用動畫

能夠看到,只須要在某個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的消息,如今服務器返回數據後,我怎麼來關閉這個消息呢。 很簡單,各位自行實現吧!

看到這裏你忍心不點個關注嗎??

相關文章
相關標籤/搜索