How do I detect a click outside an element?

Viewed 1583661

I have some HTML menus, which I show completely when a user clicks on the head of these menus. I would like to hide these elements when the user clicks outside the menus' area.

Is something like this possible with jQuery?

$("#menuscontainer").clickOutsideThisElement(function() {
    // Hide the menus
});
90 Answers

Note: Using stopPropagation is something that should be avoided as it breaks normal event flow in the DOM. See this CSS Tricks article for more information. Consider using this method instead.

Attach a click event to the document body which closes the window. Attach a separate click event to the container which stops propagation to the document body.

$(window).click(function() {
  //Hide the menus if visible
});

$('#menucontainer').click(function(event){
  event.stopPropagation();
});

The other solutions here didn't work for me so I had to use:

if(!$(event.target).is('#foo'))
{
    // hide menu
}

Edit: Plain Javascript variant (2021-03-31)

I used this method to handle closing a drop down menu when clicking outside of it.

First, I created a custom class name for all the elements of the component. This class name will be added to all elements that make up the menu widget.

const className = `dropdown-${Date.now()}-${Math.random() * 100}`;

I create a function to check for clicks and the class name of the clicked element. If clicked element does not contain the custom class name I generated above, it should set the show flag to false and the menu will close.

const onClickOutside = (e) => {
  if (!e.target.className.includes(className)) {
    show = false;
  }
};

Then I attached the click handler to the window object.

// add when widget loads
window.addEventListener("click", onClickOutside);

... and finally some housekeeping

// remove listener when destroying the widget
window.removeEventListener("click", onClickOutside);

I have an application that works similarly to Eran's example, except I attach the click event to the body when I open the menu... Kinda like this:

$('#menucontainer').click(function(event) {
  $('html').one('click',function() {
    // Hide the menus
  });

  event.stopPropagation();
});

More information on jQuery's one() function

It's 2020 and you can use event.composedPath()

From: https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath

The composedPath() method of the Event interface returns the event’s path, which is an array of the objects on which listeners will be invoked.

const target = document.querySelector('#myTarget')

document.addEventListener('click', (event) => {
  const withinBoundaries = event.composedPath().includes(target)

  if (withinBoundaries) {
    target.innerText = 'Click happened inside element'
  } else {
    target.innerText = 'Click happened **OUTSIDE** element'
  } 
})
/* just to make it good looking. you don't need this */
#myTarget {
  margin: 50px auto;
  width: 500px;
  height: 500px;
  background: gray;
  border: 10px solid black;
}
<div id="myTarget">
  click me (or not!)
</div>

Check the window click event target (it should propagate to the window, as long as it's not captured anywhere else), and ensure that it's not any of the menu elements. If it's not, then you're outside your menu.

Or check the position of the click, and see if it's contained within the menu area.

Use focusout for accessability

There is one answer here that says (quite correctly) that focusing on click events is an accessibility problem since we want to cater for keyboard users. The focusout event is the correct thing to use here, but it can be done much more simply than in the other answer (and in pure javascript too):

A simpler way of doing it:

The 'problem' with using focusout is that if an element inside your dialog/modal/menu loses focus, to something also 'inside' the event will still get fired. We can check that this isn't the case by looking at event.relatedTarget (which tells us what element will have gained focus).

dialog = document.getElementById("dialogElement")

dialog.addEventListener("focusout", function (event) {
    if (
        // we are still inside the dialog so don't close
        dialog.contains(event.relatedTarget) ||
        // we have switched to another tab so probably don't want to close 
        !document.hasFocus()  
    ) {
        return;
    }
    dialog.close();  // or whatever logic you want to use to close
});

There is one slight gotcha to the above, which is that relatedTarget may be null. This is fine if the user is clicking outside the dialog, but will be a problem if unless the user clicks inside the dialog and the dialog happens to not be focusable. To fix this you have to make sure to set tabIndex=0 so your dialog is focusable.

2020 solution using native JS API closest method.

document.addEventListener('click', ({ target }) => {
  if (!target.closest('#menupop')) {
    document.querySelector('#menupop').style.display = 'none'
  }
})
#menupop {
    width: 300px;
    height: 300px;
    background-color: red;
}
<div id="menupop">
clicking outside will close this
</div>

Use:

var go = false;
$(document).click(function(){
    if(go){
        $('#divID').hide();
        go = false;
    }
})

$("#divID").mouseover(function(){
    go = false;
});

$("#divID").mouseout(function (){
    go = true;
});

$("btnID").click( function(){
    if($("#divID:visible").length==1)
        $("#divID").hide(); // Toggle
    $("#divID").show();
});

Here is a simple solution by pure javascript. It is up-to-date with ES6:

var isMenuClick = false;
var menu = document.getElementById('menuscontainer');
document.addEventListener('click',()=>{
    if(!isMenuClick){
       //Hide the menu here
    }
    //Reset isMenuClick 
    isMenuClick = false;
})
menu.addEventListener('click',()=>{
    isMenuClick = true;
})

I have used below script and done with jQuery.

jQuery(document).click(function(e) {
    var target = e.target; //target div recorded
    if (!jQuery(target).is('#tobehide') ) {
        jQuery(this).fadeOut(); //if the click element is not the above id will hide
    }
})

Below find the HTML code

<div class="main-container">
<div> Hello I am the title</div>
<div class="tobehide">I will hide when you click outside of me</div>
</div>

You can read the tutorial here

This is my solution to this problem:

$(document).ready(function() {
  $('#user-toggle').click(function(e) {
    $('#user-nav').toggle();
    e.stopPropagation();
  });

  $('body').click(function() {
    $('#user-nav').hide(); 
  });

  $('#user-nav').click(function(e){
    e.stopPropagation();
  });
});

I ended up doing something like this:

$(document).on('click', 'body, #msg_count_results .close',function() {
    $(document).find('#msg_count_results').remove();
});
$(document).on('click','#msg_count_results',function(e) {
    e.preventDefault();
    return false;
});

I have a close button within the new container for end users friendly UI purposes. I had to use return false in order to not go through. Of course, having an A HREF on there to take you somewhere would be nice, or you could call some ajax stuff instead. Either way, it works ok for me. Just what I wanted.

Let's say the div you want to detect if the user clicked outside or inside has an id, for example: "my-special-widget".

Listen to body click events:

document.body.addEventListener('click', (e) => {
    if (isInsideMySpecialWidget(e.target, "my-special-widget")) {
        console.log("user clicked INSIDE the widget");
    }
    console.log("user clicked OUTSIDE the widget");
});

function isInsideMySpecialWidget(elem, mySpecialWidgetId){
    while (elem.parentElement) {
        if (elem.id === mySpecialWidgetId) {
            return true;
        }
        elem = elem.parentElement;
    }
    return false;
}

In this case, you won't break the normal flow of click on some element in your page, since you are not using the "stopPropagation" method.

All of these answers solve the problem, but I would like to contribute with a moders es6 solution that does exactly what is needed. I just hope to make someone happy with this runnable demo.

window.clickOutSide = (element, clickOutside, clickInside) => {
  document.addEventListener('click', (event) => {
    if (!element.contains(event.target)) {
      if (typeof clickInside === 'function') {
        clickOutside();
      }
    } else {
      if (typeof clickInside === 'function') {
        clickInside();
      }
    }
  });
};

window.clickOutSide(document.querySelector('.block'), () => alert('clicked outside'), () => alert('clicked inside'));
.block {
  width: 400px;
  height: 400px;
  background-color: red;
}
<div class="block"></div>

This is the simplest answer I have found to this question:

window.addEventListener('click', close_window = function () {
  if(event.target !== windowEl){
    windowEl.style.display = "none";
    window.removeEventListener('click', close_window, false);
  }
});

And you will see I named the function "close_window" so that I could remove the event listener when the window closes.

A way to write in pure JavaScript

let menu = document.getElementById("menu");

document.addEventListener("click", function(){
    // Hide the menus
    menu.style.display = "none";
}, false);

document.getElementById("menuscontainer").addEventListener("click", function(e){
    // Show the menus
    menu.style.display = "block";
    e.stopPropagation();
}, false);

Here is my code:

// Listen to every click
$('html').click(function(event) {
    if ( $('#mypopupmenu').is(':visible') ) {
        if (event.target.id != 'click_this_to_show_mypopupmenu') {
            $('#mypopupmenu').hide();
        }
    }
});

// Listen to selector's clicks
$('#click_this_to_show_mypopupmenu').click(function() {

  // If the menu is visible, and you clicked the selector again we need to hide
  if ( $('#mypopupmenu').is(':visible') {
      $('#mypopupmenu').hide();
      return true;
  }

  // Else we need to show the popup menu
  $('#mypopupmenu').show();
});
jQuery().ready(function(){
    $('#nav').click(function (event) {
        $(this).addClass('activ');
        event.stopPropagation();
    });

    $('html').click(function () {
        if( $('#nav').hasClass('activ') ){
            $('#nav').removeClass('activ');
        }
    });
});
$('#propertyType').on("click",function(e){
          self.propertyTypeDialog = !self.propertyTypeDialog;
          b = true;
          e.stopPropagation();
          console.log("input clicked");
      });

      $(document).on('click','body:not(#propertyType)',function (e) {
          e.stopPropagation();
          if(b == true)  {
              if ($(e.target).closest("#configuration").length == 0) {
                  b = false;
                  self.propertyTypeDialog = false;
                  console.log("outside clicked");
              }
          }
        // console.log($(e.target).closest("#configuration").length);
      });

const button = document.querySelector('button')
const box = document.querySelector('.box');

const toggle = event => {
  event.stopPropagation();
  
  if (!event.target.closest('.box')) {
    console.log('Click outside');

    box.classList.toggle('active');

    box.classList.contains('active')
      ? document.addEventListener('click', toggle)
      : document.removeEventListener('click', toggle);
  } else {
    console.log('Click inside');
  }
}

button.addEventListener('click', toggle);
.box {
  position: absolute;
  display: none;
  margin-top: 8px;
  padding: 20px;
  background: lightgray;
}

.box.active {
  display: block;
}
<button>Toggle box</button>

<div class="box">
  <form action="">
    <input type="text">
    <button type="button">Search</button>
  </form>
</div>

Still looking for that perfect solution for detecting clicking outside? Look no further! Introducing Clickout-Event, a package that provides universal support for clickout and other similar events, and it works in all scenarios: plain HTML onclickout attributes, .addEventListener('clickout') of vanilla JavaScript, .on('clickout') of jQuery, v-on:clickout directives of Vue.js, you name it. As long as a front-end framework internally uses addEventListener to handle events, Clickout-Event works for it. Just add the script tag anywhere in your page, and it simply works like magic.

HTML attribute

<div onclickout="console.log('clickout detected')">...</div>

Vanilla JavaScript

document.getElementById('myId').addEventListener('clickout', myListener);

jQuery

$('#myId').on('clickout', myListener);

Vue.js

<div v-on:clickout="open=false">...</div>

Angular

<div (clickout)="close()">...</div>

this works fine for me. i am not an expert.

$(document).click(function(event) {
  var $target = $(event.target);
  if(!$target.closest('#hamburger, a').length &&
  $('#hamburger, a').is(":visible")) {
    $('nav').slideToggle();
  }
});

I've read all on 2021, but if not wrong, nobody suggested something easy like this, to unbind and remove event. Using two of the above answers and a more little trick so put all in one (could also be added param to the function to pass selectors, for more popups). May it is useful for someone to know that the joke could be done also this way:

<div id="container" style="display:none"><h1>my menu is nice but disappear if i click outside it</h1></div>

<script>
 function printPopup(){
  $("#container").css({ "display":"block" });
  var remListener = $(document).mouseup(function (e) {
   if ($(e.target).closest("#container").length === 0 && (e.target != $('html').get(0))) 
   {
    //alert('closest call');
    $("#container").css({ "display":"none" });
    remListener.unbind('mouseup'); // isn't it?
   } 
  });
 }

 printPopup();

</script>

cheers

You don't need (much) JavaScript, just the :focus-within selector:

  • Use .sidebar:focus-within to display your sidebar.
  • Set tabindex=-1 on your sidebar and body elements to make them focussable.
  • Set the sidebar visibility with sidebarEl.focus() and document.body.focus().

const menuButton = document.querySelector('.menu-button');
const sidebar = document.querySelector('.sidebar');

menuButton.onmousedown = ev => {
  ev.preventDefault();
  (sidebar.contains(document.activeElement) ?
    document.body : sidebar).focus();
};
* { box-sizing: border-box; }

.sidebar {
  position: fixed;
  width: 15em;
  left: -15em;
  top: 0;
  bottom: 0;
  transition: left 0.3s ease-in-out;
  background-color: #eef;
  padding: 3em 1em;
}

.sidebar:focus-within {
  left: 0;
}

.sidebar:focus {
  outline: 0;
}

.menu-button {
  position: fixed;
  top: 0;
  left: 0;
  padding: 1em;
  background-color: #eef;
  border: 0;
}

body {
  max-width: 30em;
  margin: 3em;
}
<body tabindex='-1'>
  <nav class='sidebar' tabindex='-1'>
    Sidebar content
    <input type="text"/>
  </nav>
  <button class="menu-button">☰</button>
  Body content goes here, Lorem ipsum sit amet, etc
</body>

For those who want a short solution to integrate into their JS code - a small library without JQuery:

Usage:

// demo code
var htmlElem = document.getElementById('my-element')
function doSomething(){ console.log('outside click') }

// use the lib
var removeListener = new elemOutsideClickListener(htmlElem, doSomething);

// deregister on your wished event
$scope.$on('$destroy', removeListener);

Here is the lib:


function elemOutsideClickListener (element, outsideClickFunc, insideClickFunc) {
   function onClickOutside (e) {
      var targetEl = e.target; // clicked element
      do {
         // click inside
         if (targetEl === element) {
            if (insideClickFunc) insideClickFunc();
            return;

         // Go up the DOM
         } else {
            targetEl = targetEl.parentNode;
         }
      } while (targetEl);

      // click outside
      if (!targetEl && outsideClickFunc) outsideClickFunc();
   }

   window.addEventListener('click', onClickOutside);

   return function () {
      window.removeEventListener('click', onClickOutside);
   };
}

I took the code from here and put it in a function: https://www.w3docs.com/snippets/javascript/how-to-detect-a-click-outside-an-element.html

First you have to track wether the mouse is inside or outside your element1, using the mouseenter and mouseleave events. Then you can create an element2 which covers the whole screen to detect any clicks, and react accordingly depending on wether you are inside or outside element1.

I strongly recommend to handle both initialization and cleanup, and that the element2 is made as temporary as possible, for obvious reasons.

In the example below, the overlay is an element positionned somewhere, which can be selected by clicking inside, and unselected by clicking outside. The _init and _release methods are called as part of an automatic initialisation/cleanup process. The class inherits from a ClickOverlay which has an inner and outerElement, don't worry about it. I used outerElement.parentNode.appendChild to avoid conflicts.

import ClickOverlay from './ClickOverlay.js'

/* CSS */
// .unselect-helper {
//  position: fixed; left: -100vw; top: -100vh;
//  width: 200vw; height: 200vh;
// }
// .selected {outline: 1px solid black}

export default class ResizeOverlay extends ClickOverlay {
    _init(_opts) {
        this.enterListener = () => this.onEnter()
        this.innerElement.addEventListener('mouseenter', this.enterListener)
        this.leaveListener = () => this.onLeave()
        this.innerElement.addEventListener('mouseleave', this.leaveListener)
        this.selectListener = () => {
            if (this.unselectHelper)
                return
            this.unselectHelper = document.createElement('div')
            this.unselectHelper.classList.add('unselect-helper')
            this.unselectListener = () => {
                if (this.mouseInside)
                    return
                this.clearUnselectHelper()
                this.onUnselect()
            }
            this.unselectHelper.addEventListener('pointerdown'
                , this.unselectListener)
            this.outerElement.parentNode.appendChild(this.unselectHelper)
            this.onSelect()
        }
        this.innerElement.addEventListener('pointerup', this.selectListener)
    }

    _release() {
        this.innerElement.removeEventListener('mouseenter', this.enterListener)
        this.innerElement.removeEventListener('mouseleave', this.leaveListener)
        this.innerElement.removeEventListener('pointerup', this.selectListener)
        this.clearUnselectHelper()
    }

    clearUnselectHelper() {
        if (!this.unselectHelper)
            return
        this.unselectHelper.removeEventListener('pointerdown'
            , this.unselectListener)
        this.unselectHelper.remove()
        delete this.unselectListener
        delete this.unselectHelper
    }

    onEnter() {
        this.mouseInside = true
    }

    onLeave() {
        delete this.mouseInside
    }

    onSelect() {
        this.innerElement.classList.add('selected')
    }

    onUnselect() {
        this.innerElement.classList.remove('selected')
    }
}
Related