Comment détecter un clic en dehors de mon élément? J'utilise Vue.js donc ça va être en dehors de mon élément templates. Je sais comment le faire dans Vanilla JS, mais je ne sais pas s'il existe un moyen plus approprié de le faire lorsque j'utilise Vue.js?
Voici la solution pour Vanilla JS: Evénement Javascript Detect Click en dehors de div
Je suppose que je peux utiliser un meilleur moyen d'accéder à l'élément?
Peut être résolu de manière simple en configurant une directive personnalisée une fois:
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() }
})
Utilisation:
<div v-click-outside="nameOfCustomEventToCall">
Some content
</div>
Dans le composant:
events: {
nameOfCustomEventToCall: function (event) {
// do something - probably hide the dropdown menu / modal etc.
}
}
Démonstration de travail sur JSFiddle avec des informations supplémentaires sur les avertissements:
Il ya la solution que j’ai utilisée, qui répond à Linus Borg et fonctionne bien avec 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 childrens
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)
},
});
Il y a petit démo
Vous pouvez trouver plus d’informations sur les directives personnalisées et ce que el, binding, vnode signifie dans https://vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments
Il existe deux packages disponibles dans la communauté pour cette tâche (les deux sont maintenus):
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
}
}
Cela fonctionnait pour moi avec 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.removeEventListener('click', el.__vueClickOutside__)
el.__vueClickOutside__ = null
}
}
Ajoutez l'attribut tabindex
à votre composant afin qu'il puisse être ciblé et procédez comme suit:
<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>
J'utilise ce code:
tag "bouton"
<a @click="visualSwitch()"> show hide </a>
Afficher/masquer la balise:
<div class="dialog-popup" v-if="visualState" @click.stop=""></div>
état variable
data () { return {
visualState: false,
}},
methods: {
visualSwitch() {
document.removeEventListener('click', this.visualSwitch);
this.visualState = !this.visualState;
},
},
regarder
watch: {
visualState() {
if (this.visualState)
document.addEventListener('click', this.visualSwitch);
}
}
Vous pouvez inscrire deux écouteurs d'événement pour l'événement click comme celui-ci.
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!");
}
);
J'ai mis à jour la réponse de MadisonTrash pour qu'elle prenne en charge Mobile Safari (qui n'a pas d'événement click
, il faut utiliser touchend
) Cela intègre également une vérification pour que l'événement ne soit pas déclenché par un glisser-déposer sur les appareils mobiles.
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');
},
});
Juste si quelqu'un cherche comment cacher le modal en cliquant en dehors du modal. Comme modal a généralement son wrapper avec la classe modal-wrap
ou tout ce que vous avez nommé, vous pouvez mettre @click="closeModal"
sur le wrapper. À l’aide de event treatment déclaré dans la documentation de Vuejs, vous pouvez vérifier si la cible sur laquelle vous avez cliqué se trouve sur le wrapper ou sur le modal.
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>
Si vous avez un composant avec plusieurs éléments à l'intérieur de l'élément racine, vous pouvez utiliser cette solution Ça marche ™ avec un booléen.
<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>
@Denis Danilenko solutions fonctionne pour moi, voici ce que j’ai fait:
<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;
}
}
}
J'ai une solution pour gérer le menu déroulant à bascule:
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
})
},
}
Vous pouvez émettre un événement javascript natif personnalisé à partir d'une directive. Créez une directive qui distribue un événement à partir du nœud, à l'aide de 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)
}
})
Qui peut être utilisé comme ça
h3( v-out-click @click="$emit('show')" @out-click="$emit('hide')" )
J'ai combiné toutes les réponses (y compris une ligne de vue-clickaway) et proposé cette solution qui me convient
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() {
document.body.removeEventListener('click', el.clickOutsideEvent);
} })
Utilisation en composant:
<li v-click-outside="closeSearch">
<!-- your component here -->
</li>
Il y a déjà beaucoup de réponses à cette question, et la plupart d'entre elles sont basées sur l'idée similaire de directive personnalisée. Le problème avec cette approche est qu’il faut passer une fonction de méthode à la directive et ne peut pas écrire directement du code comme dans d’autres événements.
J'ai créé un nouveau package vue-on-clickout
qui est différent. Vérifiez-le à:
Cela permet d'écrire v-on:clickout
comme n'importe quel autre événement. Par exemple, vous pouvez écrire
<div v-on:clickout="myField=value" v-on:click="myField=otherValue">...</div>
et il fonctionne.
Je crée une div au bout du corps comme ça:
<div v-if="isPopup" class="outside" v-on:click="away()"></div>
Où est à l'extérieur:
.outside {
width: 100vw;
height: 100vh;
position: fixed;
top: 0px;
left: 0px;
}
Et away () est une méthode dans Vue instance:
away() {
this.isPopup = false;
}
Facile, fonctionne bien.
<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;
}
},
},
C'est simple et fiable, utilisé actuellement par beaucoup d'autres paquets. Vous pouvez également réduire la taille de votre ensemble javascript en appelant le package uniquement dans les composants requis (voir exemple ci-dessous).
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>