【原始碼解讀】js原生訊息提示外掛

limes發表於2021-01-23

效果如下:

關閉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內元素設定動畫;
 
外掛預覽地址:https://www.jq22.com/jquery-info23645

相關文章