Skip to content

Instantly share code, notes, and snippets.

@AnalyzePlatypus
Last active April 27, 2024 05:32
Show Gist options
  • Select an option

  • Save AnalyzePlatypus/22ca31c8f953db92eedadfe930bce31f to your computer and use it in GitHub Desktop.

Select an option

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

fransstudio2 commented Apr 4, 2020

Copy link
Copy Markdown

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

@AnalyzePlatypus

Copy link
Copy Markdown
Author

πŸ‘ Good catch, fixed.

@igd-tom

igd-tom commented Jul 5, 2020

Copy link
Copy Markdown

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
Copy Markdown

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

@AnalyzePlatypus

Copy link
Copy Markdown
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?

@cksnsoft

cksnsoft commented Jan 5, 2021

Copy link
Copy Markdown

Nice work

@JoeyKinch

Copy link
Copy Markdown

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

@shershen

Copy link
Copy Markdown

Thanks you! πŸ‘ πŸŽ‰ 😎

@dbramwell

dbramwell commented Jun 11, 2021

Copy link
Copy Markdown

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
Copy Markdown

Thanks! This worked perfectly!

@camrobjones

Copy link
Copy Markdown

"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