详解Element 指令clickoutside源码分析攻略
简介
这篇攻略将详细介绍Element UI库中使用的指令clickoutside的源码实现。常常需要在页面中对元素执行点击外部关闭操作,这种需求就可以通过clickoutside指令来实现。
环境
本篇攻略基于Vue.js和Element UI库实现。
功能
clickoutside指令的主要功能是用于监听元素外面的点击事件,从而可以实现定义点击元素外面的区域时关闭某些DOM节点。
使用方式
clickoutside指令的使用方式为:
<div v-clickoutside="handler"></div>
注意:需要在Vue实例的directives
属性中全局注册这个指令。
实现原理
clickoutside指令本质上是一个Vue自定义指令,其主要实现在src/directives/clickoutside.js
中。该指令主要实现了下面两个功能:
- 监听元素外部的鼠标点击事件
- 触发绑定到该指令的方法
监听鼠标点击事件
clickoutside指令的核心功能是监听鼠标点击事件,并决定是否触发相关的事件。监听外部点击事件的原理是通过事件冒泡来实现的,包括以下几个步骤:
- 给需要监听的元素添加一个mousedown事件,当这个函数被触发时,会对
document
添加一个mouseup事件,同时记录鼠标点击时的target。 - 当触发mouseup事件时,会先判断点击目标是否是当前元素或其子元素,如果是,则不做任何处理;如果不是,则触发绑定的函数。
- 在监听mousemove和mouseup事件期间,需要同时监听
document
的mouseleave事件,当鼠标移除document
时,会触发mouseup事件。
基本代码实现如下:
function bind(el, binding, vnode) {
const documentHandler = function (e) {
if (el.contains(e.target)) {
return false;
}
if (binding.expression) {
binding.value(e);
}
};
el.__vueClickOutside__ = documentHandler;
document.addEventListener('mousedown', documentHandler);
}
然而,由于鼠标事件的特性,在某些情况下,以上实现会出现一些问题。
- mousedown事件会被触发两次
- 当外部点击第一个元素时,会触发mousedown事件
- 当外部点击第二个元素时,此时外部元素已经被替换了,所以外部元素的mousedown事件会再次触发
- 在VPopper组件中,弹出VPopper后,由于弹出框是在外部的,所以会导致VPopper组件的mousedown事件不会被触发,无法正确关闭组件
针对这些问题,clickoutside指令使用了一些技巧来解决。
- 在mousedown事件发生时,对于新创建的弹出框元素,需要将其加入到被监听元素列表中,同时将所有监听的元素加入到另一个列表中。并在mouseup时遍历这个列表,判断是否需要触发事件。
- 当弹出框出现时,判断鼠标点击的目标是否在被监听元素列表中,如果是,则直接终止后续触发操作。
修正后的代码如下:
function bind(el, binding, vnode) {
const documentHandler = function (e) {
if (vnode.context && !vnode.context.popperElm.contains(e.target) && el !== e.target && !el.contains(e.target)) {
vnode.context[el.__vueClickOutside__].call(null, e);
}
};
el.__vueClickOutside__ = documentHandler;
binding.def.addEvent(document.documentElement, 'mousedown', documentHandler);
}
const addEvent = function (el, event, handler) {
if (el.addEventListener) {
return el.addEventListener(event, handler, false);
}
if (el.attachEvent) {
return el.attachEvent('on' + event, handler);
}
el['on' + event] = handler;
};
示例
示例1:使用clickoutside关闭菜单
在菜单弹出框外部时,点击后应该关闭弹出框。
<template>
<div>
<el-button type="primary" @click="showMenu = !showMenu">点击弹出菜单</el-button>
<el-dropdown :show-menu.sync="showMenu" v-clickoutside="handleClose">
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item in list" :key="item">{{ item }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
export default {
data() {
return {
showMenu: false,
list: ['选项一', '选项二', '选项三']
};
},
methods: {
handleClose() {
this.showMenu = false;
}
}
};
</script>
示例2:在VPopper组件中使用clickoutside
在VPopper弹框外部时,点击后应该关闭弹框。
<template>
<div>
<el-button ref="trigger" type="primary" @click="visible = !visible">点击弹出弹框</el-button>
<el-popover ref="popover" v-model="visible" v-clickoutside="handleClose">
<p>这里是弹框的内容</p>
<p>这里是弹框的内容</p>
<p>这里是弹框的内容</p>
<p>这里是弹框的内容</p>
<p>这里是弹框的内容</p>
</el-popover>
</div>
</template>
<script>
export default {
data() {
return {
visible: false
};
},
methods: {
handleClose() {
this.visible = false;
}
},
mounted() {
//调用VPopper的方法,将弹框内容插入到body元素中,弹框外的元素由于组件弹框的位置而被替换
this.$refs.popover.doDestroy = function () {
this.$el.parentNode.removeChild(this.$el);
this.$destroy();
};
this.$refs.popover.popperElm = this.$refs.popover.$el;
document.body.appendChild(this.$refs.popover.$el);
}
};
</script>
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解Element 指令clickoutside源码分析 - Python技术站