【源碼解讀】js原生消息提示插件

效果如下:

關閉message後前後message的銜接非常絲滑,這部分是我比較感興趣的。帶著這個問題先了解下DOM結構,順便整理下作者的思路。

 

 

從DOM里我們可以看到所有的message都在一個容器里,而這個容器做了絕對定位實現了可視窗口的水平居中,新增的message只要在容器里append對應的元素就會在頁面上顯示出來。

接下來我們看下每個message元素的秘密~

 

 

 

我們可以看到這裡設置了height和padding屬性的動畫,那上文中的動畫大概率是在關閉時設置height和padding為0,因為bfc的規則在動畫期間前後的message也會擠占其空間,所以看起來比較絲滑。動畫執行完成後再將元素remove掉。

這裡注意到一個不太常用的css屬性:will-change,援引MDN上的表述,這個屬性會根據開發者指定的要改變的值提前做優化準備。will-change

其他內部的元素是常見的根據彈框類型和消息進行渲染,不再進行細究。接下來關注點放到js上。

引入方式很簡單,只有一個js文件

程式碼示例如下:

 1 // 配置全局默認參數
 2 cocoMessage.config({
 3    duration: 10000,
 4 });
 5 // 普通消息,可傳入自動關閉時間、提示資訊、關閉回調
 6 cocoMessage.info(3000, "請先登錄!", function () {
 7     console.log("close");
 8 });
 9 // 成功消息,可傳入element元素
10 var div1 = document.createElement("div");
11 div1.innerText = "修改成功!";
12 cocoMessage.success(div1);
13 // 警告消息,時間設置0不會自動關閉
14 cocoMessage.warning("需要手動關閉", 0);
15 // 失敗消息
16 cocoMessage.error("修改失敗!", 3000);
17 // loading消息
18 var closeMsg = cocoMessage.loading(true);
19 setTimeout(function () {
20     closeMsg();
21 }, 4000);
22 // 關閉所有消息
23 cocoMessage.destroyAll();

 這裡我們可看到支援的類型有info、success、warning、error、loading五種,基本場景都覆蓋到了。傳參比較靈活,可以一個兩個三個,而且類型也不固定。帶著這些疑問開始了真正的程式碼走讀,解開它神秘的面紗:

首先看下程式碼結構:

 

 定義了一個兼容的_typeof方法(原諒我沒看懂這個兼容邏輯,有明白的兄弟可以在評論區留言);然後是一個常見的立即執行函數,函數前的!和用括弧包裹起來的效果一樣,常見的還有+-。

傳進去兩個參數,第一個是void 0(也就是undefined,個人感覺用this也沒問題,感興趣的可以了解下),另一個參數是主體函數,後面會做詳細介紹,我們先看下這個立即執行函數都做了什麼:

先檢查環境中是否有module.exports再檢查define.amd,最後才用全局變數。顯然這個是兼容CommonJs規範、AMD/CMD規範和直接引用的寫法。其中用global = global || self 的寫法而不是window因為可以兼容伺服器端(global全局變數)和瀏覽器端(window全局變數)。這樣會在瀏覽器window變數下暴露cocoMessage變數。另外提一下使用立即執行函數是可以避免污染全局變數的。在進入方法內部前,建議把這部分程式碼收藏一波

1 !function (global, factory) {
2   (typeof exports === "undefined" ? "undefined" : _typeof(exports)) === "object" && typeof module !== "undefined" ? 
3   module.exports = factory() : 
4   typeof define === "function" && define.amd ? 
5   define(factory) : 
6   (global = global || self, global.cocoMessage = factory());
7 }(void 0, function () {
8   // code here
9 });

 

剛進入方法會創建msgWrapper變數保存消息父元素,定義默認配置initArgs,暴露cocoMessage變數並在頁面元素載入完畢後添加style標籤。上圖中白色備註是比較通用的方法,下文會將重點放在紅色備註的方法上。

首先關注下創建msgWrapper元素的c方法

第一個參數傳輸對象,key可以是className來給元素添加class屬性,另一種可以是以_開頭可以給元素綁定相應的事件。

第二個參數可以傳輸文本、元素、包含多個元素的數組(或者偽數組)。

// 創建class為coco-msg-stage的div元素
var msgWrapper = c({
   className: "coco-msg-stage"
}, "默認消息");
// 創建class為coco-msg-wait並在元素上綁定click事件
c({
    className: "coco-msg-wait " ,
    _click: function _click () {
      if (closable) {
        closeMsg(el, onClose);
      }
    }
}
// 多次調用創建複雜元素
c({
   className: "coco-msg-stage"
}, c({
   className: "coco-msg-loading",
   _click: function(){
       console.log("loading")
   }
})
);
然後看下initArgs和cocoMessage這兩個變數

 

共有info、success、warning、error、loading、destoryAll、config這七個方法,這也正是這個插件想要暴露給用戶的。默認參數中消息是空字元串,關閉時間是2s,不會顯示關閉按鈕,當然這些都可以通過config方法修改全局的默認配置,從中也可以看到隨時可以修改配置,即時生效。除去destoryAll方法我們先關注下消息的5中類型,創建方法大同小異:都是通過initConfig方法創建的。其中arguments是偽數組,也就是我們調用info等方法傳遞的所有參數,可以通過通數組一樣角標方式取值。loading方法是特殊的,把initConfig方法的結果返回了,通過demo我們知道返回值是個方法,執行後會關閉loading,下文我們再關注下loading類型的消息有什麼特殊處理,開始進入initConfig方法

 

 

 這裡僅是對參數進行統一,上文中有個疑問,為什麼參數可以隨便傳,而且順序不一致也不影響?答案就在這個方法里,之前傳的參數有提示資訊:字元串(string類型)或元素(Element或object類型)、延遲時間:數字(number類型)、關閉後的回調:方法(function類型)、是否顯示關閉按鈕(boolean類型)。到這裡應該發現這裡的玄機了,每個參數都有唯一的類型而且還不會衝突,這樣就可以根據傳參類型的不同識別傳的值了。封裝後的對象大致如下:

{
   msg: "提示消息",
   type: "info",
   showClose: false,
   duration: 2000,
   onClose: function(){
       console.log("closed")  
   }
}

雖然以上方法設計得很巧妙,但是健壯性要差一些,如果要擴展設置文字是否居中、自定義類名、自定義圖標等功能時不免會要進行重構。所以調用方法改成這樣會便於擴展:

cocoMessage.info({
    msg: '請先登錄',
    duracion: '2000',
    showClose: true,
    onClose: function(){
        console.log('closed')
    }
});

到這一步需要的參數封裝完成了,接下來會調用createMsgEl方法創建消息元素。

 方法較長分為兩個部分,完成了根據傳入的參數創建元素並添加到body中顯示出來,並綁定關閉按鈕的點擊事件和觸發自動關閉的條件。圖中畫問好的正是上文中我們存疑的問題,正因為這裡返回了關閉消息的方法就可以實現執行後關閉loading。

到這裡還剩closeMsg方法destoryAll方法,我們先看closeMsg:

 

 首先會設置padding和height為0,進而實現開頭說的動畫效果,然後執行自定義的關閉回調方法。最後再刪除消息元素,如果沒有消息也會把父元素一併刪除。需要注意的是這裡還會判斷消息元素是否存在,這並不是冗餘的程式碼,而是考慮到點擊按鈕關閉和執行全部關閉一起執行時後觸發的會報錯的問題,因為這裡有300ms的延遲。

最後是destoryAll方法,關閉所有消息。

 

 獲取父元素所有的消息元素再循環調用closeMsg方法進行刪除。

 到這裡原生的消息提示插件已經解讀完畢,這是款很輕量和優秀的插件。
 
總結:
首先通過立即執行函數避免全局變數污染,只對外暴露七個方法。通過變數檢測實現對CommonJs規範、AMD/CMD規範和直接引用的兼容。通過巧妙的參數設計,實現了對參數先後順序沒有要求,通過設置padding和height值來實現動畫效果。
亮點:
設置will-change的css屬性提高性能和用戶體驗;
通過兼容監聽元素animationEnd事件來執行回調,解決了setTimeout不準確和性能問題;
通過c方法可以很方便得創建元素,有點react的味道;
給svg內元素設置動畫;
 
插件預覽地址://www.jq22.com/jquery-info23645