[英]Detect click outside element
如何檢測元素外的點擊? 我正在使用 Vue.js,所以它會在我的模板元素之外。 我知道如何在 Vanilla JS 中執行此操作,但我不確定在使用 Vue.js 時是否有更合適的方法來執行此操作?
這是 Vanilla JS 的解決方案: Javascript Detect Click event outside of div
我想我可以使用更好的方法來訪問元素?
有我使用的解決方案,它基於 Linus Borg 的回答,適用於 vue.js 2.0。
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
// here I check that click was outside the el and his children
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent)
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent)
},
});
您使用v-click-outside
綁定到它:
<div v-click-outside="doStuff">
您可以在https://v2.vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments中找到有關自定義指令以及el、綁定、vnode含義的更多信息
將tabindex
屬性添加到您的組件,以便它可以集中並執行以下操作:
<template>
<div
@focus="handleFocus"
@focusout="handleFocusOut"
tabindex="0"
>
SOME CONTENT HERE
</div>
</template>
<script>
export default {
methods: {
handleFocus() {
// do something here
},
handleFocusOut() {
// do something here
}
}
}
</script>
請注意,此解決方案僅適用於 Vue 1。
通過設置一次自定義指令可以很好地解決:
Vue.directive('click-outside', {
bind () {
this.event = event => this.vm.$emit(this.expression, event)
this.el.addEventListener('click', this.stopProp)
document.body.addEventListener('click', this.event)
},
unbind() {
this.el.removeEventListener('click', this.stopProp)
document.body.removeEventListener('click', this.event)
},
stopProp(event) { event.stopPropagation() }
})
用法:
<div v-click-outside="nameOfCustomEventToCall">
Some content
</div>
在組件中:
events: {
nameOfCustomEventToCall: function (event) {
// do something - probably hide the dropdown menu / modal etc.
}
}
JSFiddle 上的工作演示以及有關警告的其他信息:
社區中有兩個可用於此任務的包(均已維護):
此答案基於 MadisonTrash 的上述出色答案,但已更新為使用新的 Vue 3 語法。
Vue 3 現在使用beforeMount
代替bind
和unmounted
代替unbind
( src )。
const clickOutside = {
beforeMount: (el, binding) => {
el.clickOutsideEvent = event => {
// here I check that click was outside the el and his children
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
binding.value();
}
};
document.addEventListener("click", el.clickOutsideEvent);
},
unmounted: el => {
document.removeEventListener("click", el.clickOutsideEvent);
},
};
createApp(App)
.directive("click-outside", clickOutside)
.mount("#app");
我使用 created() 中的函數以稍微不同的方式完成了它。
created() {
window.addEventListener('click', (e) => {
if (!this.$el.contains(e.target)){
this.showMobileNav = false
}
})
},
這樣,如果有人在元素之外點擊,那么在我的情況下,移動導航是隱藏的。
我結合了所有答案(包括來自 vue-clickaway 的一行)並提出了適合我的解決方案:
Vue.directive('click-outside', {
bind(el, binding, vnode) {
var vm = vnode.context;
var callback = binding.value;
el.clickOutsideEvent = function (event) {
if (!(el == event.target || el.contains(event.target))) {
return callback.call(vm, event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent);
},
unbind(el) {
document.body.removeEventListener('click', el.clickOutsideEvent);
}
});
在組件中使用:
<li v-click-outside="closeSearch">
<!-- your component here -->
</li>
這對我有用 Vue.js 2.5.2 :
/**
* Call a function when a click is detected outside of the
* current DOM node ( AND its children )
*
* Example :
*
* <template>
* <div v-click-outside="onClickOutside">Hello</div>
* </template>
*
* <script>
* import clickOutside from '../../../../directives/clickOutside'
* export default {
* directives: {
* clickOutside
* },
* data () {
* return {
showDatePicker: false
* }
* },
* methods: {
* onClickOutside (event) {
* this.showDatePicker = false
* }
* }
* }
* </script>
*/
export default {
bind: function (el, binding, vNode) {
el.__vueClickOutside__ = event => {
if (!el.contains(event.target)) {
// call method provided in v-click-outside value
vNode.context[binding.expression](event)
event.stopPropagation()
}
}
document.body.addEventListener('click', el.__vueClickOutside__)
},
unbind: function (el, binding, vNode) {
// Remove Event Listeners
document.body.removeEventListener('click', el.__vueClickOutside__)
el.__vueClickOutside__ = null
}
}
如果您專門尋找元素外的點擊但仍在父元素內,您可以使用
<div class="parent" @click.self="onParentClick">
<div class="child"></div>
</div>
我將它用於模態。
Vue 3 對指令進行了重大更改,所有 <Vue3 方法都已更改/更新。 如果您想知道如何在Vue 3
中做到這一點,這里是代碼片段。 有關信息,請通過此鏈接
<div v-click-outside="methodToInvoke"></div>
click-outside.js
export default {
beforeMount: function (el, binding, vnode) {
binding.event = function (event) {
if (!(el === event.target || el.contains(event.target))) {
if (binding.value instanceof Function) {
binding.value(event)
}
}
}
document.body.addEventListener('click', binding.event)
},
unmounted: function (el, binding, vnode) {
document.body.removeEventListener('click', binding.event)
}
}
並在main.js
添加以下內容
// Directives
import ClickOutside from './click-outside'
createApp(App)
.directive('click-outside', ClickOutside)
.use(IfAnyModules)
.mount('#app')
我已經更新了 MadisonTrash 的答案以支持 Mobile Safari(它沒有click
事件,必須使用touchend
)。 這還包含一個檢查,以便不會通過在移動設備上拖動來觸發事件。
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.eventSetDrag = function () {
el.setAttribute('data-dragging', 'yes');
}
el.eventClearDrag = function () {
el.removeAttribute('data-dragging');
}
el.eventOnClick = function (event) {
var dragging = el.getAttribute('data-dragging');
// Check that the click was outside the el and its children, and wasn't a drag
if (!(el == event.target || el.contains(event.target)) && !dragging) {
// call method provided in attribute value
vnode.context[binding.expression](event);
}
};
document.addEventListener('touchstart', el.eventClearDrag);
document.addEventListener('touchmove', el.eventSetDrag);
document.addEventListener('click', el.eventOnClick);
document.addEventListener('touchend', el.eventOnClick);
}, unbind: function (el) {
document.removeEventListener('touchstart', el.eventClearDrag);
document.removeEventListener('touchmove', el.eventSetDrag);
document.removeEventListener('click', el.eventOnClick);
document.removeEventListener('touchend', el.eventOnClick);
el.removeAttribute('data-dragging');
},
});
export default {
bind: function (el, binding, vNode) {
// Provided expression must evaluate to a function.
if (typeof binding.value !== 'function') {
const compName = vNode.context.name
let warn = `[Vue-click-outside:] provided expression '${binding.expression}' is not a function, but has to be`
if (compName) { warn += `Found in component '${compName}'` }
console.warn(warn)
}
// Define Handler and cache it on the element
const bubble = binding.modifiers.bubble
const handler = (e) => {
if (bubble || (!el.contains(e.target) && el !== e.target)) {
binding.value(e)
}
}
el.__vueClickOutside__ = handler
// add Event Listeners
document.addEventListener('click', handler)
},
unbind: function (el, binding) {
// Remove Event Listeners
document.removeEventListener('click', el.__vueClickOutside__)
el.__vueClickOutside__ = null
}
}
這是一個基於 MadisonTrash 答案的完整解決方案,以及針對 safari 兼容性和 vue 3 api 更改的 benrwb 和 fredrivett 調整。
下面提出的解決方案仍然有用,並且如何使用仍然有效,但是我將其更改為使用document.elementsFromPoint
而不是event.contains
因為它無法將某些元素(例如 svgs 中的<path>
標簽)識別為子元素。 所以正確的指令是這個:
export default {
beforeMount: (el, binding) => {
el.eventSetDrag = () => {
el.setAttribute("data-dragging", "yes");
};
el.eventClearDrag = () => {
el.removeAttribute("data-dragging");
};
el.eventOnClick = event => {
const dragging = el.getAttribute("data-dragging");
// Check that the click was outside the el and its children, and wasn't a drag
console.log(document.elementsFromPoint(event.clientX, event.clientY))
if (!document.elementsFromPoint(event.clientX, event.clientY).includes(el) && !dragging) {
// call method provided in attribute value
binding.value(event);
}
};
document.addEventListener("touchstart", el.eventClearDrag);
document.addEventListener("touchmove", el.eventSetDrag);
document.addEventListener("click", el.eventOnClick);
document.addEventListener("touchend", el.eventOnClick);
},
unmounted: el => {
document.removeEventListener("touchstart", el.eventClearDrag);
document.removeEventListener("touchmove", el.eventSetDrag);
document.removeEventListener("click", el.eventOnClick);
document.removeEventListener("touchend", el.eventOnClick);
el.removeAttribute("data-dragging");
},
};
const clickOutside = {
beforeMount: (el, binding) => {
el.eventSetDrag = () => {
el.setAttribute("data-dragging", "yes");
};
el.eventClearDrag = () => {
el.removeAttribute("data-dragging");
};
el.eventOnClick = event => {
const dragging = el.getAttribute("data-dragging");
// Check that the click was outside the el and its children, and wasn't a drag
if (!(el == event.target || el.contains(event.target)) && !dragging) {
// call method provided in attribute value
binding.value(event);
}
};
document.addEventListener("touchstart", el.eventClearDrag);
document.addEventListener("touchmove", el.eventSetDrag);
document.addEventListener("click", el.eventOnClick);
document.addEventListener("touchend", el.eventOnClick);
},
unmounted: el => {
document.removeEventListener("touchstart", el.eventClearDrag);
document.removeEventListener("touchmove", el.eventSetDrag);
document.removeEventListener("click", el.eventOnClick);
document.removeEventListener("touchend", el.eventOnClick);
el.removeAttribute("data-dragging");
},
}
createApp(App)
.directive("click-outside", clickOutside)
.mount("#app");
此解決方案監視應用指令的組件的元素和元素的子元素,以檢查event.target
元素是否也是子元素。 如果是這種情況,它將不會觸發,因為它在組件內部。
您只需將其用作任何指令,並使用方法引用來處理觸發器:
<template>
<div v-click-outside="myMethod">
<div class="handle" @click="doAnotherThing($event)">
<div>Any content</div>
</div>
</div>
</template>
我在正文的末尾創建了一個 div,如下所示:
<div v-if="isPopup" class="outside" v-on:click="away()"></div>
.outside 在哪里:
.outside {
width: 100vw;
height: 100vh;
position: fixed;
top: 0px;
left: 0px;
}
away() 是 Vue 實例中的一個方法:
away() {
this.isPopup = false;
}
我使用這段代碼:
顯示隱藏按鈕
<a @click.stop="visualSwitch()"> show hide </a>
顯示隱藏元素
<div class="dialog-popup" v-if="visualState" @click.stop=""></div>
腳本
data () { return {
visualState: false,
}},
methods: {
visualSwitch() {
this.visualState = !this.visualState;
if (this.visualState)
document.addEventListener('click', this.visualState);
else
document.removeEventListener('click', this.visualState);
},
},
更新:移除手表; 添加停止傳播
這個問題已經有很多答案了,而且大部分都是基於類似的自定義指令思想。 這種方法的問題是必須將方法函數傳遞給指令,並且不能像在其他事件中那樣直接編寫代碼。
我創建了一個不同的新包vue-on-clickout
。 在以下位置查看:
它允許人們像任何其他事件一樣編寫v-on:clickout
。 例如,你可以寫
<div v-on:clickout="myField=value" v-on:click="myField=otherValue">...</div>
它有效。
vue-on-clickout
現在支持 Vue 3!
vue-on-clickout
現在被一個新包Clickout-Event
取代,它適用於任何前端框架(或 vanilla)!
我討厭額外的功能,所以......這是一個很棒的 vue 解決方案,沒有額外的 vue 方法,只有 var
<p @click="popup = !popup" v-out="popup">
<div v-if="popup">
My awesome popup
</div>
data:{
popup: false,
}
Vue.directive('out', {
bind: function (el, binding, vNode) {
const handler = (e) => {
if (!el.contains(e.target) && el !== e.target) {
//and here is you toggle var. thats it
vNode.context[binding.expression] = false
}
}
el.out = handler
document.addEventListener('click', handler)
},
unbind: function (el, binding) {
document.removeEventListener('click', el.out)
el.out = null
}
})
您可以像這樣為單擊事件注冊兩個事件偵聽器
document.getElementById("some-area")
.addEventListener("click", function(e){
alert("You clicked on the area!");
e.stopPropagation();// this will stop propagation of this event to upper level
}
);
document.body.addEventListener("click",
function(e) {
alert("You clicked outside the area!");
}
);
簡短的回答:這應該通過Custom Directives來完成。
這里有很多很好的答案也說明了這一點,但是當您開始廣泛使用外部點擊(特別是分層或具有多個排除項)時,我看到的大多數答案都會崩潰。 我在媒體上寫了一篇文章,討論了自定義指令的細微差別以及具體的實施。 它可能無法涵蓋所有邊緣情況,但它涵蓋了我想到的所有內容。
這將考慮多個綁定、多個級別的其他元素排除,並允許您的處理程序僅管理“業務邏輯”。
這是至少定義部分的代碼,請查看文章以獲取完整說明。
var handleOutsideClick={}
const OutsideClick = {
// this directive is run on the bind and unbind hooks
bind (el, binding, vnode) {
// Define the function to be called on click, filter the excludes and call the handler
handleOutsideClick[el.id] = e => {
e.stopPropagation()
// extract the handler and exclude from the binding value
const { handler, exclude } = binding.value
// set variable to keep track of if the clicked element is in the exclude list
let clickedOnExcludedEl = false
// if the target element has no classes, it won't be in the exclude list skip the check
if (e.target._prevClass !== undefined) {
// for each exclude name check if it matches any of the target element's classes
for (const className of exclude) {
clickedOnExcludedEl = e.target._prevClass.includes(className)
if (clickedOnExcludedEl) {
break // once we have found one match, stop looking
}
}
}
// don't call the handler if our directive element contains the target element
// or if the element was in the exclude list
if (!(el.contains(e.target) || clickedOnExcludedEl)) {
handler()
}
}
// Register our outsideClick handler on the click/touchstart listeners
document.addEventListener('click', handleOutsideClick[el.id])
document.addEventListener('touchstart', handleOutsideClick[el.id])
document.onkeydown = e => {
//this is an option but may not work right with multiple handlers
if (e.keyCode === 27) {
// TODO: there are minor issues when escape is clicked right after open keeping the old target
handleOutsideClick[el.id](e)
}
}
},
unbind () {
// If the element that has v-outside-click is removed, unbind it from listeners
document.removeEventListener('click', handleOutsideClick[el.id])
document.removeEventListener('touchstart', handleOutsideClick[el.id])
document.onkeydown = null //Note that this may not work with multiple listeners
}
}
export default OutsideClick
對於那些使用 Vue 3 的人。
Vue3 改變了 Directive Hooks 的語法:
在Vue 3中檢測元素外的點擊:
點擊外部.js
export default function directive(app) {
// you can name the directive whatever you want. -> click-outside
app.directive('click-outside', {
beforeMount(el, binding) {
el.clickOutsideEvent = (evt) => {
evt.stopPropagation();
if (!(el === evt.target || el.contains(evt.target))) {
binding.value(evt, el);
}
};
window.requestAnimationFrame(() => {
document.addEventListener("click", el.clickOutsideEvent);
});
},
unmounted(el) {
document.removeEventListener("click", el.clickOutsideEvent);
},
})
}
注冊指令:
main.js
import { createApp } from "vue";
import App from "./App.vue";
// Import your directive, in order to register it.
import clickOutside from "./directives/click-outside.js"
createApp(App).use(clickOutside).mount("#app");
用法:
<template>
<div class="dropdown" v-click-outside="() => hideDropdown()"></div>
</template>
<script setup>
function hideDropdown() {
console.log("close dropdown")
}
</script>
### OR
<script>
methods: {
hideDropdown() {
console.log("close dropdown")
}
}
</script>
您可以從指令發出自定義本機 javascript 事件。 使用 node.dispatchEvent 創建一個從節點調度事件的指令
let handleOutsideClick;
Vue.directive('out-click', {
bind (el, binding, vnode) {
handleOutsideClick = (e) => {
e.stopPropagation()
const handler = binding.value
if (el.contains(e.target)) {
el.dispatchEvent(new Event('out-click')) <-- HERE
}
}
document.addEventListener('click', handleOutsideClick)
document.addEventListener('touchstart', handleOutsideClick)
},
unbind () {
document.removeEventListener('click', handleOutsideClick)
document.removeEventListener('touchstart', handleOutsideClick)
}
})
哪個可以這樣使用
h3( v-out-click @click="$emit('show')" @out-click="$emit('hide')" )
如果您的組件在根元素內包含多個元素,則可以使用帶有布爾值的It just works™解決方案。
<template>
<div @click="clickInside"></div>
<template>
<script>
export default {
name: "MyComponent",
methods: {
clickInside() {
this.inside = true;
setTimeout(() => (this.inside = false), 0);
},
clickOutside() {
if (this.inside) return;
// handle outside state from here
}
},
created() {
this.__handlerRef__ = this.clickOutside.bind(this);
document.body.addEventListener("click", this.__handlerRef__);
},
destroyed() {
document.body.removeEventListener("click", this.__handlerRef__);
},
};
</script>
<button
class="dropdown"
@click.prevent="toggle"
ref="toggle"
:class="{'is-active': isActiveEl}"
>
Click me
</button>
data() {
return {
isActiveEl: false
}
},
created() {
window.addEventListener('click', this.close);
},
beforeDestroy() {
window.removeEventListener('click', this.close);
},
methods: {
toggle: function() {
this.isActiveEl = !this.isActiveEl;
},
close(e) {
if (!this.$refs.toggle.contains(e.target)) {
this.isActiveEl = false;
}
},
},
人們經常想知道用戶是否離開根組件(適用於任何級別的組件)
Vue({ data: {}, methods: { unfocused : function() { alert('good bye'); } } })
<template> <div tabindex="1" @blur="unfocused">Content inside</div> </template>
就像有人在尋找如何在模式外單擊時隱藏模式一樣。 由於 modal 的包裝器通常具有modal-wrap
類或您命名的任何內容,因此您可以將@click="closeModal"
放在包裝器上。 使用 vuejs 文檔中所述的事件處理,您可以檢查單擊的目標是在包裝器上還是在模式上。
methods: { closeModal(e) { this.event = function(event) { if (event.target.className == 'modal-wrap') { // close modal here this.$store.commit("catalog/hideModal"); document.body.removeEventListener("click", this.event); } }.bind(this); document.body.addEventListener("click", this.event); }, }
<div class="modal-wrap" @click="closeModal"> <div class="modal"> ... </div> <div>
我正在使用這個包: https ://www.npmjs.com/package/vue-click-outside
這對我來說可以
HTML:
<div class="__card-content" v-click-outside="hide" v-if="cardContentVisible">
<div class="card-header">
<input class="subject-input" placeholder="Subject" name=""/>
</div>
<div class="card-body">
<textarea class="conversation-textarea" placeholder="Start a conversation"></textarea>
</div>
</div>
我的腳本代碼:
import ClickOutside from 'vue-click-outside'
export default
{
data(){
return {
cardContentVisible:false
}
},
created()
{
},
methods:
{
openCardContent()
{
this.cardContentVisible = true;
}, hide () {
this.cardContentVisible = false
}
},
directives: {
ClickOutside
}
}
@Denis Danilenko 解決方案對我有用,這就是我所做的:順便說一下,我在這里使用 VueJS CLI3 和 NuxtJS 以及 Bootstrap4,但它也適用於沒有 NuxtJS 的 VueJS:
<div
class="dropdown ml-auto"
:class="showDropdown ? null : 'show'">
<a
href="#"
class="nav-link"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
@click="showDropdown = !showDropdown"
@blur="unfocused">
<i class="fas fa-bars"></i>
</a>
<div
class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownMenuLink"
:class="showDropdown ? null : 'show'">
<nuxt-link class="dropdown-item" to="/contact">Contact</nuxt-link>
<nuxt-link class="dropdown-item" to="/faq">FAQ</nuxt-link>
</div>
</div>
export default {
data() {
return {
showDropdown: true
}
},
methods: {
unfocused() {
this.showDropdown = !this.showDropdown;
}
}
}
它簡單可靠,目前被許多其他軟件包使用。 您還可以通過僅在所需組件中調用包來減少 javascript 包的大小(請參見下面的示例)。
npm install vue-click-outside
<template>
<div>
<div v-click-outside="hide" @click="toggle">Toggle</div>
<div v-show="opened">Popup item</div>
</div>
</template>
<script>
import ClickOutside from 'vue-click-outside'
export default {
data () {
return {
opened: false
}
},
methods: {
toggle () {
this.opened = true
},
hide () {
this.opened = false
}
},
mounted () {
// prevent click outside event with popupItem.
this.popupItem = this.$el
},
// do not forget this section
directives: {
ClickOutside
}
}
</script>
不要重新發明輪子,使用這個包v-click-outside
您可以創建處理外部點擊的新組件
Vue.component('click-outside', {
created: function () {
document.body.addEventListener('click', (e) => {
if (!this.$el.contains(e.target)) {
this.$emit('clickOutside');
})
},
template: `
<template>
<div>
<slot/>
</div>
</template>
`
})
並使用這個組件:
<template>
<click-outside @clickOutside="console.log('Click outside Worked!')">
<div> Your code...</div>
</click-outside>
</template>
我不確定是否有人會看到這個答案,但就是這樣。 這里的想法是簡單地檢測是否在元素本身之外完成了任何點擊。
我首先給我的“下拉菜單”的主 div 提供一個 id。
<template>
<div class="relative" id="dropdown">
<div @click="openDropdown = !openDropdown" class="cursor-pointer">
<slot name="trigger" />
</div>
<div
class="absolute mt-2 w-48 origin-top-right right-0 text-red bg-tertiary text-sm text-black"
v-show="openDropdown"
@click="openDropdown = false"
>
<slot name="content" />
</div>
</div>
</template>
然后我只是遍歷鼠標事件的路徑,看看是否有我的 id 為“dropdown”的 div。 如果是,那么我們很好,如果不是,那么我們關閉下拉菜單。
<script>
export default {
data() {
return {
openDropdown: false,
};
},
created() {
document.addEventListener("click", (e) => {
let me = false;
for (let index = 0; index < e.path.length; index++) {
const element = e.path[index];
if (element.id == "dropdown") {
me = true;
return;
}
}
if (!me) this.openDropdown = false;
});
}
};
</script>
如果您有許多嵌套元素,我很確定這會帶來性能問題,但我發現這是最懶惰的做法。
許多這些答案似乎過於復雜,這讓我想知道我是否遺漏了一些東西。 但解決方案似乎很簡單:只需將this.$el
(或其子之一)與e.target
進行比較。 不需要非 Vue 事件監聽器——只需 Vue @click
監聽器。
在這個例子中,我有一個渲染模式的 Vue 組件。 如果有人單擊.modal
元素(這是模態背后的疊加層),我想關閉模態,但如果他們單擊其任何子元素(即模態的內容)則不會。 這是執行此操作的代碼:
// MyComponent.js
export default {
methods: {
handleClickModal(e) {
// Prevent clicks on child elements (e.g., `.modal-dialog`) from
// closing the modal.
if (this.$el === e.target) {
closeMyModal()
}
},
},
template: `
<div class="modal" style="display:initial" @click="handleClickModal">
<div class="modal-dialog">
<!-- modal content here... -->
</div>
</div>
`,
}
它只是工作™。
如果你使用vue 3
和script setup
,你可以這樣做:
<script setup>
import {ref} from 'vue'
const elementRef = ref(null)
window.addEventListener('click', (event) => {
if (!elementRef.value.contains(event.target)){
console.log('click outside element')
}
})
</script>
<template>
<div ref="elementRef">your element</div>
</template>
盡管在這個線程下有這么多的答案,我仍然很難完全實現點擊外部事件。 您可以按照以下步驟查看完整的實施過程。
main.js -- 注冊一個全局指令(只需復制和粘貼)
import Vue from 'vue';
Vue.directive('click-outside', {
bind(el, binding) {
const handler = (e) => {
if ((!el.contains(e.target) && el !== e.target)) {
binding.value(e);
}
}
el.__vueClickOutside__ = handler;
document.addEventListener('click', handler);
},
unbind(el) {
document.removeEventListener('click', el.__vueClickOutside__);
el.__vueClickOutside__ = null;
}
});
YourComponent.vue -- 指令的使用
<template>
<div>
<button @click.stop="showCalloutMenu()">Callout</button>
<section v-if="callout" v-click-outside="hideCalloutMenu">
Your callout menu content
</section>
</div>
<script>
export default {
data:() => ({
callout: false,
}),
methods: {
showCalloutMenu() {
this.callout = true;
// or if you prefer toggle effect, use
// this.callout = !this.callout;
},
hideCalloutMenu() {
this.callout = false;
},
}
};
注意事項
確保在標注元素上使用.stop
事件修飾符,否則標注菜單將不會顯示。
@click.stop="showCalloutMenu()"
如果有人想使用 Vue 3可組合組件來完成此任務,您可以使用它。 如果您想自己編寫代碼,請參見下文:
事件.js
import { onMounted, onUnmounted } from 'vue';
export function useEventListener(target, event, callback) {
onMounted(() => target.addEventListener(event, callback));
onUnmounted(() => target.removeEventListener(event, callback));
}
點擊外部.js
import { unref } from 'vue';
import { useEventListener } from './event';
export function useClickOutside(ref, cb) {
let target = null;
const listener = (event) => {
if (!target) target = unref(ref);
if (!(target === event.target || target.contains(event.target))) cb();
};
useEventListener(document.body, 'click', listener);
}
你的組件
<script setup>
import { ref } from 'vue';
import { useClickOutside } from 'path/to/click-outside';
const myCallback = () => {
console.log('You clicked outside the container!');
};
const container = ref(null);
useClickOutside(container, myCallback);
</script>
<template>
<div ref="container"></div>
</template>
請注意, useEventListener
是一種常見模式,可以獨立於useClickOutside
使用。
除了所有很棒的答案之外,我只想提一下,您還可以使用VueUse 可組合的 onClickOutside 。
這是作為指令的示例用法
<template>
<component v-if="showDialog" v-on-click-outside="() =>showDialog = false"/>
</template>
<script>
import { vOnClickOutside } from '@vueuse/components'
const showDialog = ref(true)
</script>
您也可以直接將其用作組件方法或無渲染組件。
您可以使用幾行腳本來實現它。 我為此使用 nuxt3,但我認為它也應該適用於 vue3
const showDropdown = ref( false )
onMounted( () => {
window.addEventListener( 'click', ( e ) => {
if ( document.getElementById( 'dropdown' ).contains( e.target ) ) {
showDropdown.value = !showDropdown.value
}
else {
showDropdown.value = false
}
} )
} )
在我的模板中。
<div>
<div
class="flex gap-2 items-center relative px-5"
id="dropdown"
>
<div class="w-5 h-5">
<IconsCog />
</div>
<div class="relative">Settings</div>
<div
v-if="showDropdown"
class="absolute bg-white w-full top-[40px] left-0 shadow-md rounded"
>
<ul>
<li class="px-4 text-sm py-3">Account</li>
<li class="px-4 text-sm pb-3">Logout</li>
</ul>
</div>
</div>
</div>
Stackblitz 示例https://stackblitz.com/edit/nuxt-starter-cnn3qc?file=app.vue
https://jsbin.com/yomimudahi/edit?html,css,js,output
document.addEventListener("click", printMousePos); function printMousePos(event) { const list = document.getElementsByTagName("nav")[0]; var ssdd = list.classList.contains("test2"); if (ssdd === false && (event.clientX > list.clientWidth || event.clientY > list.clientHeight)) { list.classList.add("test2"); } } function openNav() { const list = document.getElementsByTagName("nav")[0]; list.classList.remove("test2"); }
@media only screen and (min-width: 701px) { main { display: grid; width: 100%; grid-template-areas: "nav main"; grid-template-columns: 150px 1fr; position: relative; } main > nav { grid-area: nav; background-color: #ffa08c; } main > section { grid-area: main; background-color: #ffff64; } main > section > aside { grid-area: head; background-color: #8ca0ff; }.test { display: none; } } @media only screen and (max-width: 700px) { main { display: grid; width: 100%; grid-template-areas: "main"; grid-template-columns: 1fr; position: relative; } main > nav { height: 300px; background-color: coral; position: fixed; } main > section { grid-area: main; background-color: #ffff64; } main > section > aside { grid-area: head; background-color: #8ca0ff; }.test2 { display: none; } }
<:DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> </head> <body> <main> <nav class="test2"> <header> <img src="https.//encrypted-tbn0.gstatic?com/images:q=tbn:ANd9GcTCPQ5XLyAjzq-NHitpSKVIsF8LorhPxewY_vERnurZLA&s" width="50" height="50" /> </header> <div> Navigation </div> </nav> <section> <aside> Aside <div class="test" onclick="openNav()"> <img src="https.//cdn-icons-png.flaticon.com/512/1752/1752735.png" width="50" height="50" /> </div> </aside> <article> Content<br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> <br/> </article> </section> </main> </body> </html>
我為 Vue 2 創建了 mixin:
混合文件:
export const detectClickOutside = {
data() {
return {
statusElement: false,
nameElement: undefined,
ref: undefined,
};
},
watch: {
statusElement(isShow) {
if (isShow) document.addEventListener('mousedown', this.handleClickOutside);
else this.stopEventListener();
},
},
beforeDestroy() {
this.stopEventListener();
},
methods: {
stopEventListener() {
this.statusElement = false;
this.nameElement = undefined;
this.ref = undefined;
document.removeEventListener('mousedown', this.handleClickOutside);
},
setStatusShowingElement(status, name, refElement) {
this.statusElement = status;
this.nameElement = name;
this.ref = refElement;
},
handleClickOutside({ target }) {
if (this.ref && !this.ref.contains(target)) this[this.nameElement] = false;
},
},
};
以及如何在組件中使用它:
<button @click="openModal = !openModal">Click</button> // button for open modal
將打開的組件:
<ExampleComponent v-show="openModal" ref="modal" />
和 watcher 將數據發送到 mixin:
watch: {
openModal(newValue) {
this.setStatusShowingElement(
newValue,
'openModal',
this.$refs.modal.$el,
);
},
},
我有一個處理切換下拉菜單的解決方案:
export default {
data() {
return {
dropdownOpen: false,
}
},
methods: {
showDropdown() {
console.log('clicked...')
this.dropdownOpen = !this.dropdownOpen
// this will control show or hide the menu
$(document).one('click.status', (e)=> {
this.dropdownOpen = false
})
},
}
現在您應該為此使用 vue-click-outside 插件。
當您在 div 之外單擊時,您可以運行一個事件。
NPM 插件: https ://www.npmjs.com/package/v-click-outside
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.