闲言碎语
自己在写组件库,目前已经写了popper组件,popup组件,然后现在在写颜色选择器。全部都是用的vue3和ts。
缘起于popper组件的细节优化。我的颜色选择器组件,基于popper为底层组件。然后除了一般的参数控制关闭之外。我需要做到点击在非组件绑定的元素区域要关闭弹窗。
引入使用
要结果的直接最后复制源码
import ClickOutside from '../ClickOutside'
export default defineComponent({
directives: {
'dht-click-outside': ClickOutside.directive,
},
}
)
<span v-dht-click-outside={clickOutside}></span>
基础原理实现
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>js实现的点击div区域外隐藏div区域</title>
<style type="text/css">
#myDiv{
border:1px solid #000000;
width:200px;
height:100px;
background:#FF0000;
}
</style>
<script type="text/javascript">
window.onload=function(){
var myDiv = document.getElementById("myDiv");
document.addEventListener("click",function(){
console.log(11111)
myDiv.style.display="none";
});
myDiv.addEventListener("click",function(event){
console.log(2222)
event=event||window.event;
event.stopPropagation();
});
};
</script>
</head>
<body>
<div id="myDiv"></div>
</body>
这部分代码就是核心原理了。或者我们可以根据元素的xy轴判断等等。
翻看elemenPlus源码,addEventListener细节
我的组件也是有些模仿elementUI的,所以就翻看他怎么实现的。然后我发现elementUI自己实现了一个自定义指令。ClickOutside
其中有一个关键的细节。指令可以绑定到不同的元素上面,但是elementUI做了一个操作,他把绑定之后的数据存到了Map下面,我想本身已经绑定到元素下面了,为什么还需要存放到一个数组一样的结构中呢。
实验之后,发现
下面这种代码方式,addEventListener只会触发一次
<span v-dht-click-outside={clickOutside}</span>
<span v-dht-click-outside={clickOutside}></span>
源码
我自己也懒,把elementUI的源码小改改直接放项目里面使用了。
下面是完整源码,使用参考上面的引入使用
// 该代码抄自elementUI
import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue'
type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void
type FlushList = Map<
HTMLElement,
{
documentHandler: DocumentHandler
bindingFn: (...args: unknown[]) => unknown
}
>
const on = function (
element: HTMLElement | Document | Window,
event: string,
handler: EventListenerOrEventListenerObject,
useCapture = false,
): void {
if (element && event && handler) {
element.addEventListener(event, handler, useCapture)
}
}
const nodeList: FlushList = new Map()
let startClick: MouseEvent
on(document, 'mousedown', (e) => (startClick = e as MouseEvent))
on(document, 'mouseup', (e) => {
for (const { documentHandler } of nodeList.values()) {
documentHandler(e as MouseEvent, startClick)
}
})
function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler {
let excludes: HTMLElement[] = []
if (Array.isArray(binding.arg)) {
excludes = binding.arg
} else {
// due to current implementation on binding type is wrong the type casting is necessary here
excludes.push((binding.arg as unknown) as HTMLElement)
}
return function (mouseup, mousedown) {
const popperRef = (binding.instance as ComponentPublicInstance<{
popperRef: HTMLElement
}>).popperRef
const mouseUpTarget = mouseup.target as Node
const mouseDownTarget = mousedown.target as Node
const isBound = !binding || !binding.instance
const isTargetExists = !mouseUpTarget || !mouseDownTarget
const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget)
const isSelf = el === mouseUpTarget
const isTargetExcluded =
(excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) ||
(excludes.length && excludes.includes(mouseDownTarget as HTMLElement))
const isContainedByPopper =
popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget))
if (
isBound ||
isTargetExists ||
isContainedByEl ||
isSelf ||
isTargetExcluded ||
isContainedByPopper
) {
return
}
binding.value()
}
}
const ClickOutside: ObjectDirective = {
beforeMount(el, binding) {
nodeList.set(el, {
documentHandler: createDocumentHandler(el, binding),
bindingFn: binding.value,
})
},
updated(el, binding) {
nodeList.set(el, {
documentHandler: createDocumentHandler(el, binding),
bindingFn: binding.value,
})
},
unmounted(el) {
nodeList.delete(el)
},
}
export default ClickOutside
非ts代码实现
const on = function (element, event, handler, useCapture = false) {
if (element && event && handler) {
element.addEventListener(event, handler, useCapture);
}
};
const nodeList = new Map();
let startClick;
on(document, 'mousedown', (e) => (startClick = e));
on(document, 'mouseup', (e) => {
for (const { documentHandler } of nodeList.values()) {
documentHandler(e, startClick);
}
});
function createDocumentHandler(el, binding) {
let excludes = [];
if (Array.isArray(binding.arg)) {
excludes = binding.arg;
}
else {
// due to current implementation on binding type is wrong the type casting is necessary here
excludes.push(binding.arg);
}
return function (mouseup, mousedown) {
const popperRef = binding.instance.popperRef;
const mouseUpTarget = mouseup.target;
const mouseDownTarget = mousedown.target;
const isBound = !binding || !binding.instance;
const isTargetExists = !mouseUpTarget || !mouseDownTarget;
const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget);
const isSelf = el === mouseUpTarget;
const isTargetExcluded = (excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) ||
(excludes.length && excludes.includes(mouseDownTarget));
const isContainedByPopper = popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget));
if (isBound ||
isTargetExists ||
isContainedByEl ||
isSelf ||
isTargetExcluded ||
isContainedByPopper) {
return;
}
binding.value();
};
}
const ClickOutside = {
beforeMount(el, binding) {
nodeList.set(el, {
documentHandler: createDocumentHandler(el, binding),
bindingFn: binding.value,
});
},
updated(el, binding) {
nodeList.set(el, {
documentHandler: createDocumentHandler(el, binding),
bindingFn: binding.value,
});
},
unmounted(el) {
nodeList.delete(el);
},
};
export default ClickOutside;
//# sourceMappingURL=directive.js.map
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!