怎么点击下拉框外面关闭下拉框
之前看到我不能没有的 5 个 Vue.js 库,里面第一个就是Click Off to Close
,最主要功能就是,在用户点击元素之外的时候触发一个事件。
此文的目标,手写自定义指令实现此功能。
功能比如自己封装一个下拉框,或者封装一个日历,希望在点击非下拉框部分,能关闭下拉框。
网络异常,图片无法展示
|
普通的下拉框
先实现一个普通的下拉框。 就是,一个框 + 一个面板。 点击框,显示或隐藏面板。
网络异常,图片无法展示
|
<div id="app"> <div class="select-box"> <div class="trigger-head" @click="clickSelect">点击下拉</div> <div v-if="isShow" class="trigger-body">我是下拉框</div> </div> </div> <script src="https://unpkg.com/vue"></script> <script> const vm = new Vue({ el: "#app", data: { isShow: false }, methods: { clickSelect() { this.isShow = !this.isShow; } } }); </script> <!-- style这里先放后面,毕竟不是说的重点 --> <style> .select-box { width: 200px; margin-left: 200px; } .trigger-head { border: 1px solid #ccc; padding: 10px; } .trigger-body { width: 200px; height: 300px; background-color: #ccc; position: absolute; } </style>
重点:点击外面就会关闭面板
我先贴下代码,看完代码,基本就明白了
mounted() { document.addEventListener("click", (e)=> { // 记得在.select-box那边加上ref="selectBox" const selectBox = this.$refs.selectBox; // 重点来了:selectBox里是否包含点击的元素,不包含点击的元素就隐藏面板 if (!selectBox.contains(e.target)) { this.isShow = false; } }); },
代码一看,其实看客们大约就明白了。
click 事件绑定在全局,然后通过contains
这个关键性的方法,判断点击的元素是不是selectBox
的后代,要不是的话,就肯定点在了selectBox
的外面,顺道就隐藏面板。至此,最关键性的逻辑就搞定了。
封装成自定义指令
最核心的逻辑就是上面了。 接下来就是封装成一个指令,专门处理这种,点击元素之外触发某种操作。
这里封装一个,clickOutside
的指令,绑定指令的元素,表示点击元素之外的地方就会执行相应的函数。
// html那边 <div v-click-outside="hidePanel" ref="selectBox" class="select-box" > Vue.directive("click-outside", (el, bindings, vnode) => { // el就是绑定指令的元素,bindings.expression就是动态参数这里是hide,vnode是绑定指令的元素的虚拟节点,vnode.context就是节点所在的vm实例 document.addEventListener("click", e => { // 点击的是 绑定指令元素么 不是就执行函数 if (!el.contains(e.target)) { let method = bindings.expression; vnode.context[method](); } }); }); // 顺便记得在methods里面添加 hidePanel(){ this.isShow = false }
自定义指令的钩子
上面基本差不多了,但是还有个优化的点,因为绑定事件是在document
上,我们应该择时解绑。
在阅读完官网的文档之后,可以想到unbind
这个钩子,一旦指令解绑,document 也就会自动解绑事件。
这里注意,为了方便解绑,所以需要将点击事件赋值给el.handle
。
优化之后的指令代码如下:
Vue.directive("click-outside", { // el就是绑定指令的元素,bindings.expression就是动态参数这里是hide,vnode是绑定指令的元素的虚拟节点,vnode.context就是节点所在的vm实例 bind(el, bindings, vnode) { el.handle = e => { // 点击的是 绑定指令元素么 不是就触发 参数传进来的函数 if (!el.contains(e.target)) { let method = bindings.expression; vnode.context[method](); } }; document.addEventListener("click", el.handle); }, unbind(el) { // 相关事件移除 document.removeEventListener("click", el.handle); } });
附加:自动聚焦的指令
不知道有没有发现,即便我们在input
那边加上autofocus
属性,但聚焦一瞬即过,然后就没有光标了。
因为#app
里面的代码进入了一次fragment,编译好之后,然后再倒进去。所以初始有一瞬间的聚焦,再倒进去之后,聚焦效果就会失效。
于是呢,要是想要自动聚焦的效果,可以封装个简单的指令,这边直接用官网的。
// <input type="text" v-focus> Vue.directive('focus', { // 当被绑定的元素插入到 DOM 中时 inserted: function (el) { // 聚焦元素 el.focus() } })