官方文档介绍:自定义指令
主要就是利用vue指令的功能,获取所绑定元素的dom对象dom_A以及传递过来的回调方法fun_A,然后监听浏览器的mousedown和mouseup事件(mousedown作为辅助信息,真正触发传递的回调方法的是mouseup事件),当前事件中鼠标位置对应的dom对象dom_B不属于dom_A,则代表鼠标点击了dom_A外部,触发clickoutside回调方法。
参考:vue自定义指令clickoutside使用以及扩展用法
element ui clickoutside 指令源码解读
1.引入指令
import Clickoutside from '@/utils/clickoutside'
新建一个clickoutside.js文件,自定义 clickOutside指令,当鼠标点击了指令所绑定的元素外面时触发事件:
import Vue from 'vue'
const isServer = Vue.prototype.$isServer
const on = (function () {
if (!isServer && document.addEventListener) {
return function (element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false)
}
}
} else {
return function (element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler)
}
}
}
})()
const nodeList = []
const ctx = '@@clickoutsideContext'
let startClick
let seed = 0
!Vue.prototype.$isServer && on(document, 'mousedown', e => (startClick = e))
!Vue.prototype.$isServer &&
on(document, 'mouseup', e => {
nodeList.forEach(node => node[ctx].documentHandler(e, startClick))
})
function createDocumentHandler(el, binding, vnode, excludes) {
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 (excludes && (excludes.includes(mousedown.target) || excludes.includes(mouseup.target))) {
return
}
if (binding.expression && el[ctx].methodName && vnode.context[el[ctx].methodName]) {
vnode.context[el[ctx].methodName]()
} else {
el[ctx].bindingFn && el[ctx].bindingFn()
}
}
}
/**
* v-clickoutside
* @desc 点击元素外面才会触发的事件
* @example
* ```vue
* <div v-element-clickoutside="handleClose">
* ```
*/
export default {
bind(el, binding, vnode) {
nodeList.push(el)
const id = seed++
el[ctx] = {
id,
documentHandler: createDocumentHandler(el, binding, vnode, binding.value.excludes),
methodName: binding.expression,
bindingFn: typeof binding.value === 'function' ? binding.value : binding.value.fn,
}
},
update(el, binding, vnode) {
el[ctx].documentHandler = createDocumentHandler(el, binding, vnode, binding.value.excludes)
el[ctx].methodName = binding.expression
el[ctx].bindingFn = typeof binding.value === 'function' ? binding.value : binding.value.fn
},
unbind(el) {
const len = nodeList.length
for (let i = 0; i < len; i++) {
if (nodeList[i][ctx].id === el[ctx].id) {
nodeList.splice(i, 1)
break
}
}
delete el[ctx]
},
}
钩子函数
————————————————————————————————————————
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用。
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。
componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind
:只调用一次,指令与元素解绑时调用。
钩子函数参数
————————————————————————————————————————
el
:指令所绑定的元素,可以用来直接操作 DOM。binding
:一个对象,包含以下 property:
name
:指令名,不包括v-
前缀。value
:指令的绑定值。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。arg
:传给指令的参数,可选。modifiers
:一个包含修饰符的对象。vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
2.声明指令
export default {
directives: { Clickoutside },
}
3.使用指令
<div
v-if="activeRoom"
v-show="showPop"
v-clickoutside="e => popClickoutside('clickOutside')"
@click.stop
>
<detailCard
:room-info="activeRoom"
:can-click-outside-close.sync="canClickOutsideClose"
:show-pop.sync="showPop"
:show-actions="showDetailCardActions"
@refreshData="refreshData"
>
<slot :room="activeRoom" />
</detailCard>
@click.stop 阻止事件冒泡
@click.prevent 阻止事件的默认行为
mounted() {
window.addEventListener('scroll', this.popClickoutside)
},
beforeDestroy() {
window.removeEventListener('scroll', this.popClickoutside)
},
methods: {
popClickoutside(from) {
if (from === 'clickOutside' && !this.canClickOutsideClose) {
return
}
this.showPop = false
},
refreshData() {
this.$emit('refreshData')
this.showPop = false
},
}
windowScroll 监听页面滚动
我需要实现的效果是,页面中有几个大区域,大区域中排列着几个div小方块。点击某个大区域时,该区域内的小方块样式变大并且显示出序号,再点击其中某个大方块会弹出弹框显示大方块内具体情况。滑动页面或点击大区域外,弹框消失;再次点击该区域,大方块变小。
如果不添加scroll事件,如果点击了大区域中的某个div小方块,再点击大区域之外,关联的弹框内容还是存在并且会随着页面滚动。