翻了翻element-ui源码,发现一个很实用的指令clickoutside
前言
指令(directive)在 vue 开发中是一项很实用的功能,指令可以绑定到某一元素或组件,使功能的颗粒度更精细。今天在翻 element-ui 的源码时,发现一个还挺实用的工具指令,跟大伙分享一下。
clickoutside 的使用及效果
该指令的源码在 src/utils 下的 clickoutside.js。它功能是指令需要接收一个函数,当用户鼠标点击的区域在绑定指令的元素之外时,会触发该函数。
那么使用这个指令能够实现什么功能呢?我想到一个功能,就像我们常用的抽屉组件,在点击抽屉之外的区域时,抽屉就会消失(但 elementui 中不是用这种方式,而是用一个遮罩层实现)。
接下来我们来看看怎么玩这个指令,很简单,只需要引入这个文件注册指令就好了。
// main.js
import Vue from 'vue'
import clickoutside from 'element-ui/src/utils/clickoutside'
Vue.directive('clickoutside', clickoutside)
使用:
<div v-show="show" v-clickoutside="handler"><div>
export default {
data() {
return {
show: true
}
},
methods: {
handler() {
this.show = false
}
}
}
效果:
源码分析
clickoutside 看起来还挺不错,下面看看它是如何实现的。首先是它的指令钩子定义:
const nodeList = [];
const ctx = '@@clickoutsideContext';
let seed = 0;
export default {
// 指令绑定时触发
bind(el, binding, vnode) {
// 每次绑定时会把dom元素存放到 nodeList 中
nodeList.push(el);
// 创建递增id标识
const id = seed++;
// 在dom元素上设置一些属性和方法
// ctx的作用是一个标识,为了不和原生的属性冲突
el[ctx] = {
id,
// 这个是点击元素区域外时会执行的函数,后面会提到
documentHandler: createDocumentHandler(el, binding, vnode),
// 绑定的值表达式,值相当于上面例子中的 "handler" 字符串
methodName: binding.expression,
// 绑定的值,值相当于上面例子中的 handler 函数
bindingFn: binding.value
};
},
// 组件更新时触发
update(el, binding, vnode) {
el[ctx].documentHandler = createDocumentHandler(el, binding, vnode);
el[ctx].methodName = binding.expression;
el[ctx].bindingFn = binding.value;
},
// 指令解绑时触发
unbind(el) {
let len = nodeList.length;
// 找到对应的dom元素,从 nodeList 移除它
for (let i = 0; i < len; i++) {
if (nodeList[i][ctx].id === el[ctx].id) {
nodeList.splice(i, 1);
break;
}
}
// 移除之前添加的自定义属性
delete el[ctx];
}
};
源码内部会对 docuemnt 鼠标事件进行监听:
let startClick;
// 鼠标按下时 记录按下元素的事件对象
!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e));
// 鼠标松开时 遍历 nodeList 中的元素,执行 documentHandler
!Vue.prototype.$isServer && on(document, 'mouseup', e => {
nodeList.forEach(node => node[ctx].documentHandler(e, startClick));
});
接下来最核心的就是 documentHandler 函数,它是由 createDocumentHandler 创建出来的:
function createDocumentHandler(el, binding, vnode) {
// 接收参数为:鼠标松开和鼠标按下的事件对象
return function(mouseup = {}, mousedown = {}) {
// 这里一系列的判断点击区域是否在元素内,如果在区域内则跳出
if (!vnode ||
!vnode.context ||
!mouseup.target ||
!mousedown.target ||
el.contains(mouseup.target) ||
el.contains(mousedown.target) ||
el === mouseup.target ||
(vnode.context.popperElm &&
(vnode.context.popperElm.contains(mouseup.target) ||
vnode.context.popperElm.contains(mousedown.target)))) return;
// 执行我们绑定指令时的函数
if (binding.expression &&
el[ctx].methodName &&
vnode.context[el[ctx].methodName]) {
// vnode.context 是组件实例上下文
// 就像开头的例子,methodName 是 "handler",通过索引上下文的属性找到 methods 中定义的 handler 函数
vnode.context[el[ctx].methodName]();
} else {
el[ctx].bindingFn && el[ctx].bindingFn();
}
};
}
至此整个指令流程分析就完了。
小插曲
在经过一些demo的使用后,发现该指令在某些场景下会出现不理想的效果。例如:抽屉内有 el-select 选择栏时,选择栏的 dom 是挂载到 body 下,导致在点击完选择项后被判断为区域外点击。
其实这也符合逻辑,因为点击的地方也确实在区域外,只是在这种场景下看起来像是“bug”一样。然后我发现源码里提供了一个选项解决这种问题。可以在使用指令的组件 data 里定义 popperElm 属性,它的值是一个 dom。
export default {
mounted() {
this.popperElm = document.querySelector('.el-select-dropdown.el-popper')
}
}
在源码里会通过 popperElm 进行判断:
if (!vnode ||
!vnode.context ||
!mouseup.target ||
!mousedown.target ||
el.contains(mouseup.target) ||
el.contains(mousedown.target) ||
el === mouseup.target ||
(vnode.context.popperElm &&
(vnode.context.popperElm.contains(mouseup.target) ||
vnode.context.popperElm.contains(mousedown.target)))) return;
如果 popperElm 包含鼠标点击的 dom 则跳出逻辑。
然后我又想到了一个问题,popperElm 只能设置一个,当有多个选择栏组件时,还是会出现上面所说的情况。我的想法是,把 clickoutside 给 copy 一份下来,把 popperElm 改成可以接受数组类型,判断时去循环判断,这样应该可以解决问题。
还有一件有趣的事,我在全局搜索时发现 element-ui 里好像没有用到这个指令。
结语
clickoutside 不止抽屉的场景,只要你想在点击某个元素区域之外做些事情,都可以考虑它。
除了这个,还有很多优秀的第三方指令,例如 element-ui 中的 v-loading 可以实现局部的加载动画,常用的 vue-lazyload 中的 v-lazy 可以实现图片的懒加载。
个人认为指令属于那种用得少但很实用的东西,可能在开发功能时都没有考虑到用指令来实现,如果你还不了解指令,赶快学起来。
感谢阅读
欢迎关注公众号【奔跑的前端er】,专注于分享前端技术文章,和大家一起进步。
翻了翻element-ui源码,发现一个很实用的指令clickoutside的更多相关文章
- element ui源码解析 -- input篇
el-input是element ui中使用最频繁的组件之一了,分析其构成从四个方面入手:DOM结构,属性,样式,事件入手 DOM结构: <div> <input /> < ...
- element ui源码解析 -- button篇
要看源码就得从最简单的开始,button够简单的了,就从他开始吧. 安装依赖后源码目录在:node_modules/element-ui/packages中,可以看到这里的文件夹命名是不是很熟悉,就是 ...
- Element UI 源码—— Carousel 走马灯学习
参考博客:https://segmentfault.com/a/1190000014384638?utm_source=tag-newest
- storm源码之一个class解决nimbus单点问题【转】
本文导读: storm nimbus 单节点问题概述 storm与解决nimbus单点相关的概念 nimbus目前无法做到多节点的原因 解决nimbus单点问题的关键 业界对nimbus单点问题的努力 ...
- 控件真的很好用,突然感觉自己以前研究Discuz!NT366源码的方式很2了
控件真的很好用,突然感觉自己以前研究Discuz!NT366源码的方式很2了,就是按钮上的或其他控件上的图片哪里去了?
- 用Scratch2.0源码定制一个自己的编辑器
用Scratch2.0源码定制一个自己的编辑器,换成自己的软件名称和图标,添加中文字体,修复汉化错误等等1.准备:下载Scratch2.0源码.安装开发工具Adobe Flash Builder4.7 ...
- 如何实现 Https拦截进行 非常规“抓包” 珍惜Any 看雪学院 今天 前段时间在自己做开发的时候发现一个很好用的工具,OKHttp的拦截器(何为拦截器?就是在每次发送网络请求的时候都会走的一个回调)大概效果如下:
如何实现 Https拦截进行 非常规“抓包” 珍惜Any 看雪学院 今天 前段时间在自己做开发的时候发现一个很好用的工具,OKHttp的拦截器(何为拦截器?就是在每次发送网络请求的时候都会走的一个回调 ...
- 翻String.Format源码发现的新东西:StringBuilderCache
起因: 记不清楚今天是为毛点想F12看String.Format的实现源码了,反正就看到了下图的鸟东西: 瞬间石化有没有,StringBuilder还能这么获取? 研究StringBuilderCac ...
- iview 和 Elemet UI 源码比较
(近期给自己立了个小flag,读源码,每周至少读1篇源码) 下面来谈谈iview 和 Elemet UI 这两个基于Vue 的UI 框架源码的基本结构以及区别. 一.文件结构开发主要放在根文件夹下的s ...
随机推荐
- Java使用POI的SXSSFWorkbook与HSSFWorkbook导出复杂表头
一.HSSFWorkbook与SXSSFWorkbook的区别: HSSFWorkbook是对Excel2003以前的版本进行操作的,即后缀名为.xls SXSSFWorkbook时对Excel200 ...
- H5简单内容
1.简单认识H5 HTML5不仅仅是作为HTML标记语言的一个最新版本,更重要的是它指定了Web开发的一系列标准,成为第一个将Web作为应用开发平台的HTML语言. 我们日常讨论的H5其实是有一个泛称 ...
- 用于测试XmlAnalyzer 1.00版的八个测试用例
XmlAnalyzer 工程目的:将XML的属性和子节点按字母序排列,然后整形输出: 08. 原文=<project xmlns="http://maven.apache.org/PO ...
- C#通过Com串口进行Barcode Printer
前言 工作中有遇到Barcode打印的需求,最开始是通过打印机型号找到对应的打印机,再进行操作,但是需要匹配的打印机型号太多,而且不定,所以处理起来太过麻烦. 后面通过找到通过串口找到打印机,直接传输 ...
- IDEA里如何引入JQuery
HTML页面的JavaScript里一般可以直接使用类似:<script src="js/jquery.js"></script>语句可以直接引入,但是会遇 ...
- 基于Goc的Golang代码VSCode实时染色方案
近日,Li Yiyang 老师基于Goc做了个VS Code插件,能够直观的看到被执行到的代码,当真充满想象力: 感兴趣的同学可以去goc仓库查看详情. Goc的核心能力就在于能够帮助我们在被测程序运 ...
- fiddler工具介绍及证书设置
fiddler 目录 1.Fiddler介绍 01.介绍 02.简单使用 03.结果状态码 介绍完了,接下来就到证书了 2.Fiddler证书设置 这就是fiddler证书设置的全部步骤了 1.Fid ...
- JVM-STW-stop the world
Stop The World Stop一the一World,简称STW,指的是Gc事件发生过程中,会产生应用程序的停顿.停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称 ...
- 分布式系统监视zabbix-安装
zabbix概述 Zabbix是什么 Zabbix 是由Alexei Vladishev创建,目前由Zabbix SIA在持续开发和支持. Zabbix 是一个企业级的分布式开源监控方案. Zabbi ...
- 如何在 PyPI安装python的软件包?
安装软件包 本节介绍如何安装Python的基本知识.包裹. 需要注意的是,这个上下文中的“包”一词被用作分布(即要安装的一组软件),而不是指包装在Python源代码中导入(即模块的容器).Python ...