点击空白处关闭弹窗是常见的网页效果,其实它的实现原理很简单,就是全局监听一个点击事件,当监听到用户点击时,判断target
元素是否在弹窗内,如果是,则不关闭弹窗(点击了关闭按钮例外),否则关闭弹窗。但需要注意一点,当弹窗显示时,才需要监听用户的点击事件,弹窗关闭时,需要及时撤销监听函数。本文介绍两种实现方式,供大家参考学习。
常规方法
template
<button ref="toggleButton" @click="handleClick">show</button>
<div ref="popup" class="popup" v-show="visible">
Popup
<button @click="handleClick">close</button>
</div>
script
<script>
export default {
data() {
return {
visible: false
}
},
methods: {
// 弹窗显示时监听点击事件
show() {
document.addEventListener('click', this.hidePanel, false)
},
// 弹窗隐藏时撤销监听事件
hide() {
document.removeEventListener('click', this.hidePanel, false)
},
// 只有当点击的元素属于弹窗或者 toggle 按钮才会执行 if 里面的代码
hidePanel(e) {
if (!this.$refs.toggleButton.contains(e.target) && !this.$refs.popup.contains(e.target)) {
this.visible = false
this.hide()
}
},
handleClick() {
this.visible = !this.visible
if (this.visible) {
this.show()
} else {
this.hide()
}
}
}
}
</script>
自定义指令
template
<button ref="toggleButton" @click="visible = !visible">show</button>
<!-- 这里用 v-if 代替了 v-show -->
<div ref="popup" class="popup" v-if="visible" v-clickoutside="close">
Popup
<button @click="close">close</button>
</div>
script
<script>
const clickoutside = {
// 初始化指令
bind(el, binding, vnode) {
function documentHandler(e) {
// 这里判断点击的元素是否是本身,是本身,则返回
if (el.contains(e.target)) {
return false
}
// 判断指令中是否绑定了函数
if (binding.expression) {
// 如果绑定了函数 则调用那个函数,此处binding.value就是 close 方法
binding.value(e)
}
}
// 给当前元素绑定个私有变量,方便在 unbind 中可以解除事件监听
el.__vueClickOutside__ = documentHandler
document.addEventListener('click', documentHandler)
},
update() {},
unbind(el, binding) {
// 解除事件监听
document.removeEventListener('click', el.__vueClickOutside__)
delete el.__vueClickOutside__
}
}
export default {
name: 'App',
directives: { clickoutside },
data() {
return {
visible: false
}
},
methods: {
close(e) {
if (!this.$refs.toggleButton.contains(e.target)) {
this.visible = false
}
}
}
}
</script>
在bind
方法里添加点击事件监听器,而在unbind
里撤销监听器。值得注意的是,unbind
只有在元素与指令解绑时(比如元素销毁)才会调用一次,所以弹窗必须用v-if
而不是v-show
,因为v-show
只会隐藏元素,不会真正销毁元素。