Skip to content

Instantly share code, notes, and snippets.

@AnalyzePlatypus
Last active December 1, 2023 05:45
Show Gist options
  • Star 33 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AnalyzePlatypus/22ca31c8f953db92eedadfe930bce31f to your computer and use it in GitHub Desktop.
Save AnalyzePlatypus/22ca31c8f953db92eedadfe930bce31f to your computer and use it in GitHub Desktop.
Vue.js 2.7: Detect clicks outside an element (Close modals, popups, etc.)

Detecting outside clicks in Vue.js

See this StackOverflow thread

First off, include the directive at the end of this gist.

  1. On your open button, make sure to use @click.stop to prevent the open click event from closing your modal.
  2. On your modal, add the v-click-outside directive and points it at a function to call when clicked outside.

Example:

<template>
  <button @click.stop="openPopup” />
  <div v-if="shouldShowModal” v-click-outside="hidePopup” />
</template>

<script>

export default {
  data() {
    return {
      shouldShowModal: false
    };
  },
  methods: {
    showPopup() {
      this.shouldShowModal = true;
    },
    hidePopup() {
      this.shouldShowModal = false;
    }
  },
};
</script>

Directive:

Based on this SO answer

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');
  },
});
@fransstudio2
Copy link

fransstudio2 commented Apr 4, 2020

Thanks for this. Found this page by googling.
Btw is closePopup meant to be hidePopup here? :)

@AnalyzePlatypus
Copy link
Author

👍 Good catch, fixed.

@igd-tom
Copy link

igd-tom commented Jul 5, 2020

Thanks for your solution, works great on Desktop! However on my Android phone browser it doesn't quite behave properly. If you click the button to open the dialog box it opens, but if you click the button again the dialog box attempts to close but then reopens again. Though clicking outside the button will close the dialog properly. Any ideas on what that could be?

@noeleo25
Copy link

Thanks !! great workaround :) I was doing something similar but on each component, definitely better with this custom vue directive.

@AnalyzePlatypus
Copy link
Author

@igd-tom, I'd guess it's probably something off in the code that opens the dialog. Can you post a fiddle or CodeSandbox demonstrating the problem?

@fmzad
Copy link

fmzad commented Jan 5, 2021

Nice work

@JoeyKinch
Copy link

This is the ONLY one I've found that actually works, you're a gem!

@shershen
Copy link

Thanks you! 👍 🎉 😎

@dbramwell
Copy link

dbramwell commented Jun 11, 2021

Update for Vue 3:

{
  beforeMount(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
              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');
  }
}

@mrtrimble
Copy link

Thanks! This worked perfectly!

@camrobjones
Copy link

"On your open button, make sure to use @click.stop to prevent the open click event from closing your modal." Thanks so much, this was my issue with another solution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment