场景: 手写级联组件下拉框时,需要点击级联组件区域外触发事件来关闭下拉框
// vue2.x 点击元素外触发事件的指令
Vue.directive("clickoutside", {
// 初始化指令
bind(el, binding, vnode) {
function documentHandler(e) {
// 这里判断点击的元素是否是本身,是本身,则返回
if (el.contains(e.target)) {
return false;
}
// 判断指令中是否绑定了函数
if (binding.expression) {
// 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
binding.value(e);
}
}
// 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
el.__vueClickOutside__ = documentHandler;
document.addEventListener("click", documentHandler);
},
update() {},
unbind(el, binding) {
// 解除事件监听
document.removeEventListener("click", el.__vueClickOutside__);
delete el.__vueClickOutside__;
},
});
级联组件my-cascader.vue
<template>
<div class="my-cascader" v-clickoutside="handleClose()">
<div>级联组件</div>
<!-- 一堆实现逻辑 -->
</div>
</template>
<script>
import { composedPath } from "@/common/utils";
export default {
methods: {
handleClose(e) {
const path = composedPath(e)
// 判断点击区域是否在元素以内,若用e.path.some..火狐和safira会报错,故用composedPath
let open = path.some(
c =>
Array.from(
c.classList && c.classList.length ? c.classList : []
).includes("my-cascader")
);
// open为false,说明在区域外,此时关闭下拉框
if (!open) {
this.open = open;
}
}
}
}
</script>
附composedPath
方法
const composedPath = e => {
if (e.path) {
return e.path;
}
let target = e.target;
e.path = [];
while (target.parentNode !== null) {
e.path.push(target);
target = target.parentNode;
}
e.path.push(document, window);
return e.path;
};
或者重写全局event
事件对象,在main.js
引入
// Event.composedPath
(function(e, d, w) {
if(!e.composedPath) {
e.composedPath = function() {
if (this.path) {
return this.path;
}
var target = this.target;
this.path = [];
while (target.parentNode !== null) {
this.path.push(target);
target = target.parentNode;
}
this.path.push(d, w);
return this.path;
}
}
})(Event.prototype, document, window);