8

【源码解读】js原生消息提示插件

 3 years ago
source link: http://www.cnblogs.com/kkform/p/14312004.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

效果如下:

NJ3Qby.gif!mobile

关闭message后前后message的衔接非常丝滑,这部分是我比较感兴趣的。带着这个问题先了解下DOM结构,顺便整理下作者的思路。

eamaimQ.png!mobile

从DOM里我们可以看到所有的message都在一个容器里,而这个容器做了绝对定位实现了可视窗口的水平居中,新增的message只要在容器里append对应的元素就会在页面上显示出来。

接下来我们看下每个message元素的秘密~

ze6zIfI.png!mobile

我们可以看到这里设置了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五种,基本场景都覆盖到了。传参比较灵活,可以一个两个三个,而且类型也不固定。带着这些疑问开始了真正的代码走读,解开它神秘的面纱:

首先看下代码结构:

eAJVzei.png!mobile

定义了一个兼容的_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 });

EfUBZrA.png!mobile

刚进入方法会创建msgWrapper变量保存消息父元素,定义默认配置initArgs,暴露cocoMessage变量并在页面元素加载完毕后添加style标签。上图中白色备注是比较通用的方法,下文会将重点放在红色备注的方法上。

首先关注下创建msgWrapper元素的 c方法

VNVVfeU.png!mobile

第一个参数传输对象,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这两个变量

eaIv6rU.png!mobile

共有info、success、warning、error、loading、destoryAll、config这七个方法,这也正是这个插件想要暴露给用户的。默认参数中消息是空字符串,关闭时间是2s,不会显示关闭按钮,当然这些都可以通过config方法修改全局的默认配置,从中也可以看到随时可以修改配置,即时生效。除去destoryAll方法我们先关注下消息的5中类型,创建方法大同小异:都是通过initConfig方法创建的。其中arguments是伪数组,也就是我们调用info等方法传递的所有参数,可以通过通数组一样角标方式取值。loading方法是特殊的,把initConfig方法的结果返回了,通过demo我们知道返回值是个方法,执行后会关闭loading,下文我们再关注下loading类型的消息有什么特殊处理,开始进入 initConfig方法

Mrm6v2Y.png!mobile

这里仅是对参数进行统一,上文中有个疑问,为什么参数可以随便传,而且顺序不一致也不影响?答案就在这个方法里,之前传的参数有提示信息:字符串(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方法 创建消息元素。

z67ZriJ.png!mobileq6jMFvA.png!mobile

方法较长分为两个部分,完成了根据传入的参数创建元素并添加到body中显示出来,并绑定关闭按钮的点击事件和触发自动关闭的条件。图中画问好的正是上文中我们存疑的问题,正因为这里返回了关闭消息的方法就可以实现执行后关闭loading。

到这里还剩 closeMsg方法destoryAll方法 ,我们先看closeMsg:

fAjymaA.png!mobile

首先会设置padding和height为0,进而实现开头说的动画效果,然后执行自定义的关闭回调方法。最后再删除消息元素,如果没有消息也会把父元素一并删除。需要注意的是这里还会判断消息元素是否存在,这并不是冗余的代码,而是考虑到点击按钮关闭和执行全部关闭一起执行时后触发的会报错的问题,因为这里有300ms的延迟。

最后是 destoryAll方法 ,关闭所有消息。

36NfQbr.png!mobile

获取父元素所有的消息元素再循环调用 closeMsg方法 进行删除。

到这里原生的消息提示插件已经解读完毕,这是款很轻量和优秀的插件。

总结:

首先通过立即执行函数避免全局变量污染,只对外暴露七个方法。通过变量检测实现对CommonJs规范、AMD/CMD规范和直接引用的兼容。通过巧妙的参数设计,实现了对参数先后顺序没有要求,通过设置padding和height值来实现动画效果。

亮点:

设置will-change的css属性提高性能和用户体验;

通过兼容监听元素animationEnd事件来执行回调,解决了setTimeout不准确和性能问题;

通过c方法可以很方便得创建元素,有点react的味道;

给svg内元素设置动画;

插件预览地址:https://www.jq22.com/jquery-info23645


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK