web-dev-qa-db-fra.com

Détecter un clic à l'extérieur de l'élément

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?

58
user6102188

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:

https://jsfiddle.net/Linusborg/yzm8t8jq/

49
Linus Borg

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

99
MadisonTrash

Il existe deux packages disponibles dans la communauté pour cette tâche (les deux sont maintenus):

11
Julien Le Coupanec
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

  }
}
4
xiaoyu2er

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
  }
}
3
yann_yinn

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>
2
G'ofur N

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);
    }
}
1
Pax Exterminatus

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!");
         }
);
1
saravanakumar

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');
    },
});
0
benrwb

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>

0
jedi

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>
0
A1rPun

@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;
    }
  }
}
0
alfieindesigns

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
        })
      },
}
0
Nicolas S.Xu

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')" )
0
Pedro Torchio

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>
0
BogdanG

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.

0
Mu-Tsun Tsai

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.

0
Arnaud LiDz
  <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;
      }
    },
  },
0
Dmytro Lishtvan

Utilisez ce paquetage vue-click-outside

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

Utilisation:

<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>
0
Smit Patel