Как определить щелчок за пределами элемента?

У меня есть несколько меню HTML, которые я полностью показываю, когда пользователь нажимает на заголовок этих меню. Я хотел бы скрыть эти элементы, когда пользователь щелкает мышью за пределами области меню.

Возможно ли что-то подобное с jQuery?

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

Вот пример этой стратегии: jsfiddle.net/tedp/aL7Xe/1

Ted 09.11.2011 21:02

Как упоминал Том, вы захотите прочитать css-tricks.com/dangers-stopping-event-propagation, прежде чем использовать этот подход. Хотя этот инструмент jsfiddle довольно крутой.

Jon Coombs 03.01.2015 00:08

получить ссылку на элемент, а затем event.target и, наконец,! = или == оба они, затем выполнить код соответственно ..

Rohit Kumar 07.07.2015 22:43

Попробуйте использовать event.path. http://stackoverflow.com/questions/152975/how-do-i-detect-a-‌ click-outside-an-element / 43405204 # 434052‌ 04

Dan Philip 14.04.2017 09:03
Решение Vanilla JS with event.target and безevent.stopPropagation.
lowtechsun 23.04.2017 02:14

Поскольку об этом не упоминается везде в ответах, но он полезен для меня в этом контексте: когда мышь выходит из окна, event.relatedTarget является null в событии mouseout, и в противном случае это элемент, над которым мышь сейчас находится.

Andrew 25.05.2020 22:21
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2 618
6
1 355 033
85
Перейти к ответу Данный вопрос помечен как решенный

Ответы 85

Проверьте цель события щелчка окна (оно должно распространяться на окно, если оно не зафиксировано где-либо еще) и убедитесь, что это не какой-либо из элементов меню. Если это не так, значит, вы вне своего меню.

Или проверьте положение щелчка и посмотрите, содержится ли он в области меню.

Ответ принят как подходящий

NOTE: Using stopEventPropagation() is something that should be avoided as it breaks normal event flow in the DOM. See this article for more information. Consider using this method instead

Прикрепите событие щелчка к тексту документа, закрывающее окно. Прикрепите к контейнеру отдельное событие щелчка, которое останавливает распространение в тексте документа.

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

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

Я предпочитаю привязать документ к событию щелчка, а затем при необходимости отвязать событие. это более эффективно.

vsync 17.08.2009 20:47

Это нарушает стандартное поведение многих вещей, включая кнопки и ссылки, содержащихся в #menucontainer. Я удивлен, что этот ответ так популярен.

Art 12.06.2010 12:00

Я опубликовал альтернативное решение, которое не нарушает поведение stackoverflow.com/questions/152975/…

Art 12.06.2010 12:38

Это не нарушает поведения чего-либо внутри #menucontainer, поскольку он находится в нижней части цепочки распространения для всего, что внутри него.

Eran Galperin 16.06.2010 23:55

это очень красиво, но вы должны использовать $('html').click(), а не корпус. Тело всегда имеет высоту своего содержимого. Если контента мало или экран очень высокий, работает только на той части, которая заполнена телом.

meo 25.02.2011 18:35

meo + 1. Вместо body должен быть html. Тот же аргумент для горизонтального пространства; если на странице есть плавные поля, а монитор шире, чем содержимое, меню не скроется при щелчке за пределами области, если вы используете текст.

Frederik Wordenskjold 19.03.2011 05:43

@medo Это действительно должен быть $(document).

Mathias Bynens 27.09.2011 16:18

@Art простой обходной путь для этого - остановить распространение обертки div, которая содержит контент для меню.

Prusprus 18.11.2011 19:59

@ user751564, как бы мы это сделали? Вложить еще один div с содержимым внутри #menucontainer?

tundoopani 31.12.2011 10:00

Эван, Серджио, Джо, Арт, Мео и все остальные (+ Джоэл и Джефф) Я вас всех люблю. В этом случае мне потребовался бы день, чтобы наконец реализовать event.stopPropigation.

umassthrower 08.04.2012 03:22

Эээ, на самом деле я пошел с решением Art, потому что я не хотел останавливать распространение, когда кто-то щелкает второе меню, когда первое все еще открыто.

umassthrower 08.04.2012 03:44

Я также удивлен, что это решение набрало столько голосов. Это не удастся для любого внешнего элемента, у которого есть stopPropagation jsfiddle.net/Flandre/vaNFw/3

Andre 08.05.2012 16:32
stackoverflow.com/a/12920446/470159 I've answered a similar one with what i feel is a nice solution
Owen 16.10.2012 21:45

Если вы показываете меню из события щелчка (например, кнопки или ссылки), то обработчик события щелчка «открытое меню» также должен вызывать event.stopPropagation().

Diogo Kollross 19.01.2013 03:49

Красивое решение, маленькое и легкое. Но ... Не принимает во внимание оверлеи, такие как датпикеры, всплывающие окна и т. д. По моему мнению, такие элементы не должны вызывать спрятанность. Я добавил ответ на этот вопрос, который решает эту проблему.

Anders 18.06.2013 17:35

Не работает, если у вас есть более одного меню на странице с использованием этой техники. Щелчок по любому меню остановит закрытие всех остальных меню.

Stephen Corwin 27.10.2013 06:09

Не могу сказать, что удивлен, что этот ответ получил столько голосов, во многих случаях он работает. Но это не лучшее решение данной проблемы! Лично мне нравится передавать вещи внутри функции в области видимости функции: stackoverflow.com/questions/152975/…stackoverflow.com/a/7385673/986862

Andre Figueiredo 29.11.2013 17:13
stackoverflow.com/questions/1403615/… complete this answer as pointed by @AndréFigueiredo:
Adrien Be 16.04.2014 13:32

Филип Уолтон очень хорошо объясняет, почему этот ответ не лучшее решение: css-tricks.com/dangers-stopping-event-propagation

Tom 20.05.2014 14:52

Я голосую против этого, потому что это не совсем то решение, которое мы должны использовать. Это опасно, потому что имеет далеко идущие последствия. Как отметил Том в комментарии выше, на css-tricks.org есть очень подробная статья о том, почему этот ответ хорош.

Andy Mercer 23.05.2014 23:07

Это решение нарушает другие разделы моего кода. Извините, но мне придется проголосовать против этого. Я также удивлен, что это так популярно.

Ian S 11.07.2014 23:54

Это решение, как правило, ужасно и опасно для обслуживания. В основном это просто хитрость, которая иногда срабатывает.

AlexG 25.02.2015 15:09

Этот ответ нарушает многие стандартные правила поведения. Его следует занижать!

Christian Rolle 21.04.2015 17:13

Я думаю, что это опасно для производительности страницы, подробности в этой статье: css-tricks.com/dangers-stopping-event-propagation

Belal mazlom 10.08.2015 12:25

Вот собственно хорошее объяснение, почему не использовать event.stopPropagation()css-tricks.com/dangers-stopping-event-propagation

codeepic 25.07.2016 12:01

Попробуйте использовать event.path. Здесь есть ответ http://stackoverflow.com/questions/152975/how-do-i-detect-a-‌ click-outside-an-element / 43405204 # 434052‌ 04

Dan Philip 14.04.2017 14:20

Я с сожалением использовал модальный плагин, который шокирующе использовал этот метод, чтобы разрешить щелчок за пределами модального окна, чтобы закрыть его, и потребовалась целая вечность, чтобы выяснить, почему я не мог делегировать события чему-либо внутри модального окна !! Я изменил код плагина, чтобы использовать его: $(document).on('click', function(e) { if (!$(e.target).closest('.modal').length && !$(e.target).is('.modal')) modal.close(); });, что является лучшим решением.

Martin James 04.07.2018 18:54

Я бы сказал: не используйте событие щелчка, вместо этого используйте событие mouseup, которое меньше используется другой логикой

grant sun 20.11.2018 20:46

Только это решение сработало для меня. Ни один из приведенных ниже "лучше" не сделал. Большое спасибо

Sharpey 12.12.2018 03:24

У меня есть приложение, которое работает аналогично примеру Эрана, за исключением того, что я прикрепляю событие щелчка к телу, когда открываю меню ... Примерно так:

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

  event.stopPropagation();
});

Подробнее о Функция jQuery one()

но то если щелкнуть по самому меню, то снаружи не получится :)

vsync 17.08.2009 20:46

Это помогает поместить event.stopProgagantion () перед привязкой прослушивателя кликов к телу.

Jasper Kennis 19.04.2012 14:05

Проблема в том, что «один» применяется к методу jQuery для многократного добавления событий в массив. Поэтому, если вы нажимаете на меню, чтобы открыть его более одного раза, событие снова привязывается к телу и пытается скрыть меню несколько раз. Для решения этой проблемы необходимо применить отказоустойчивый режим.

marksyzm 24.06.2013 15:01

После привязки с использованием .one - внутри обработчика $('html') - напишите $('html').off('click').

Cody 13.07.2015 21:01

@ Коди, я не думаю, что это поможет. Обработчик one автоматически вызовет off (как показано в документации jQuery).

Mariano Desanze 04.12.2015 21:12

Возможно, я неправильно понял реализацию. Моя собственная реализация этого требует, чтобы у вас было 2 действия, каждое из которых коррелирует с переключение прослушивателя событий. Одно действие устанавливает его, одно удаляет. Это просто зависит от где в вашем коде, который вы фактически устанавливаете слушателя - в противном случае вы все равно можете вызывать несколько обработчиков в зависимости от количества вызовов постановка действий.

Cody 23.12.2015 10:13

Это проблематично, так как нажатие на #menucontainer также скроет его.

Nathan B 12.10.2019 12:22

Другие решения здесь не сработали для меня, поэтому мне пришлось использовать:

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

Я опубликовал еще один практический пример того, как использовать event.target, чтобы избежать запуска других виджетов JQuery UI с внешними щелчками по обработчикам html при встраивании их в собственное всплывающее окно: Лучший способ получить исходную цель

Joey T 02.07.2012 22:13

Это сработало для меня, за исключением того, что я добавил && !$(event.target).parents("#foo").is("#foo") внутри оператора IF, чтобы любые дочерние элементы не закрывали меню при нажатии.

honyovk 26.09.2012 23:57

@Frug Ваш ответ может быть ценным, поэтому вы получили 5, но я запутался вашим ответом. а кто такой МБЖ. Я искал лучшее решение на основе вашего ответа.

Satya Prakash 30.09.2013 18:25

MBJ, вероятно, было старым именем человека, который разместил комментарий чуть выше моего. Пользователи меняют имена, это может затруднить работу, но обратите внимание, что в комментарии над моим добавлен .parents("#foo").

Frug 05.11.2013 04:52

Не могу найти лучше, чем: // Мы за пределами $ (event.target) .parents ('# foo'). Length == 0

AlexG 15.12.2013 15:12

Лучше всего в случае скрытия выпадающих списков по щелчку их снаружи

Farzad Yousefzadeh 23.03.2016 08:48

Краткое улучшение обработки глубокого вложения - использование .is('#foo, #foo *'), но Я не рекомендую связывать обработчики кликов для решения этой проблемы..

zzzzBov 19.09.2016 19:03

!$(event.target).closest("#foo").length будет лучше и избавит от необходимости добавлять @honyovk.

tvanc 25.05.2018 02:30

Если вы пишете сценарии для IE и FF 3. * и просто хотите знать, произошел ли щелчок в определенной области окна, вы также можете использовать что-то вроде:

this.outsideElementClick = function(objEvent, objElement){   
var objCurrentElement = objEvent.target || objEvent.srcElement;
var blnInsideX = false;
var blnInsideY = false;

if (objCurrentElement.getBoundingClientRect().left >= objElement.getBoundingClientRect().left && objCurrentElement.getBoundingClientRect().right <= objElement.getBoundingClientRect().right)
    blnInsideX = true;

if (objCurrentElement.getBoundingClientRect().top >= objElement.getBoundingClientRect().top && objCurrentElement.getBoundingClientRect().bottom <= objElement.getBoundingClientRect().bottom)
    blnInsideY = true;

if (blnInsideX && blnInsideY)
    return false;
else
    return true;}

$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

У меня работает нормально.

Это тот, который я бы использовал. Возможно, он не идеален, но, как программист-любитель, его достаточно легко понять.

kevtrout 08.02.2010 19:05

размытие - это ход за пределами #menucontainer, вопрос был о щелчке

borrel 06.08.2013 17:22

@borrel blur - это нет выход за пределы контейнера. Размытие противоположно фокусу, вы думаете о наведении курсора мыши. Это решение сработало для меня особенно хорошо, когда я создавал текст «щелкните для редактирования», где я переключался между обычным текстом и полями ввода при щелчках.

parker.sikand 11.10.2013 02:08

Мне пришлось добавить tabindex = "-1" к #menuscontainer, чтобы он заработал. Кажется, что если вы поместите тег ввода внутрь контейнера и щелкните по нему, контейнер будет скрыт.

tyrion 12.08.2014 17:17

событие mouseleave больше подходит для меню и контейнеров (ссылка: w3schools.com/jquery/…)

Evgeniya Manolova 19.10.2015 04:35

Теперь для этого есть плагин: внешние события (Сообщение блога)

Когда обработчик щелчок (WLOG) привязан к элементу, происходит следующее:

  • элемент добавляется в массив, содержащий все элементы с обработчиками щелчок
  • обработчик (пространство имен) щелкнуть привязан к документу (если еще не существует)
  • на любом щелкнуть в документе событие щелчок запускается для тех элементов в этом массиве, которые не равны или не являются родительскими для цели щелкнуть-events
  • кроме того, event.target для события щелчок устанавливается на элемент, на который щелкнул пользователь (так что вы даже знаете, что щелкнул пользователь, а не только то, что он щелкнул снаружи)

Таким образом, распространение событий не прекращается, и дополнительные обработчики щелкнуть могут использоваться «над» элементом с внешним обработчиком.

Хороший плагин. Ссылка на «внешние события» мертва, в то время как ссылка на сообщение в блоге вместо этого жива и предоставляет очень полезный плагин для событий типа «clickoutside». Он также имеет лицензию MIT.

TechNyquist 11.04.2017 09:44

Отличный плагин. Сработало отлично. Использование такое: $( '#element' ).on( 'clickoutside', function( e ) { .. } );

Gavin 21.03.2018 16:48

Для меня это отлично сработало:

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

Проблема в том, что независимо от того, где вы щелкаете, вы всегда будете иметь пузырь событий вниз к телу. поэтому вы должны использовать event.stopPropagation, но это нарушит естественный способ поведения DOM. Я не думаю, что это хорошая практика.

ncubica 21.11.2014 03:43

Вы можете прослушать событие щелкнуть на document, а затем убедиться, что #menucontainer не является предком или целью элемента, по которому щелкнули мышью, используя .closest().

Если это не так, значит, выбранный элемент находится за пределами #menucontainer, и вы можете безопасно скрыть его.

$(document).click(function(event) { 
  var $target = $(event.target);
  if (!$target.closest('#menucontainer').length && 
  $('#menucontainer').is(":visible")) {
    $('#menucontainer').hide();
  }        
});

Изменить - 2017-06-23

Вы также можете очистить после прослушивателя событий, если планируете закрыть меню и хотите прекратить прослушивание событий. Эта функция очистит только недавно созданный прослушиватель, сохранив все остальные прослушиватели щелчков на document. С синтаксисом ES2015:

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    const $target = $(event.target);
    if (!$target.closest(selector).length && $(selector).is(':visible')) {
        $(selector).hide();
        removeClickListener();
    }
  }

  const removeClickListener = () => {
    document.removeEventListener('click', outsideClickListener)
  }

  document.addEventListener('click', outsideClickListener)
}

Изменить - 2018-03-11

Для тех, кто не хочет использовать jQuery. Вот приведенный выше код на обычном vanillaJS (ECMAScript6).

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
        }
    }

    const removeClickListener = () => {
        document.removeEventListener('click', outsideClickListener)
    }

    document.addEventListener('click', outsideClickListener)
}

const isVisible = elem => !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js 

ПРИМЕЧАНИЕ: Это основано на комментарии Алекса просто использовать !element.contains(event.target) вместо части jQuery.

Но element.closest() теперь также доступен во всех основных браузерах (версия W3C немного отличается от версии jQuery). Полифиллы можно найти здесь: Элемент.closest ()

Изменить - 2020-05-21

В случае, если вы хотите, чтобы пользователь мог щелкать и перетаскивать внутри элемента, а затем отпускать мышь за пределами элемента, не закрывая элемент:

      ...
      let lastMouseDownX = 0;
      let lastMouseDownY = 0;
      let lastMouseDownWasOutside = false;

      const mouseDownListener = (event: MouseEvent) => {
        lastMouseDownX = event.offsetX
        lastMouseDownY = event.offsetY
        lastMouseDownWasOutside = !$(event.target).closest(element).length
      }
      document.addEventListener('mousedown', mouseDownListener);

А в outsideClickListener:

const outsideClickListener = event => {
        const deltaX = event.offsetX - lastMouseDownX
        const deltaY = event.offsetY - lastMouseDownY
        const distSq = (deltaX * deltaX) + (deltaY * deltaY)
        const isDrag = distSq > 3
        const isDragException = isDrag && !lastMouseDownWasOutside

        if (!element.contains(event.target) && isVisible(element) && !isDragException) { // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
          document.removeEventListener('mousedown', mouseDownListener); // Or add this line to removeClickListener()
        }
    }

Хотя ваш метод тоже работает, ваше утверждение полностью ошибочно. #menucontainer - это нижний уровень в цепочке распространения для всех содержащихся в нем элементов, поэтому он не меняет своего поведения. Вы должны попробовать и убедиться в этом сами.

Eran Galperin 17.06.2010 00:14

Я пробовал многие другие ответы, но сработал только этот. Спасибо. В итоге я использовал следующий код: $ (document) .click (function (event) {if ($ (event.target) .closest ('. Window'). Length == 0) {$ ('. Window' ) .fadeOut ('быстро');}});

Pistos 05.04.2012 23:30

На самом деле я остановился на этом решении, потому что оно лучше поддерживает несколько меню на одной странице, где нажатие на второе меню при открытом первом оставит первое открытым в решении stopPropagation.

umassthrower 08.04.2012 03:41

Отличный ответ. Это правильный вариант, когда у вас есть несколько элементов, которые вы хотите закрыть.

John 08.05.2013 19:23

Это должен быть принятый ответ из-за недостатка другого решения с event.stopPropagation ().

tomato 25.04.2014 06:45

Это должен быть принятый ответ, более подробный ответ см. На этой странице: css-tricks.com/dangers-stopping-event-propagation

Bill 29.05.2014 04:02

как вариант: var $ menu = $ ('# menucontainer'); $ (document) .on ('click', function (e) {// если элемент открыт и цель щелчка находится за его пределами, // закрыть его if ($ menu.is (': visible') &&! $ menu. is (e.target) &&! $ menu.has (e.target) .length) {$ menu.hide ();}});

Bohdan Lyzanets 24.07.2014 13:32

Это лучшее решение, чем любое, использующее $ ('html'). StopPropagation (), так как они могут повлиять на функциональность других частей сайта. Я надеюсь, что кто-то отметит это как ответ.

James Heston 14.09.2014 21:05
Пример одной строки - !$(e.target).closest('.menu').length && $('.menu').is(":visible") && $('.menu').hide();
vsync 18.11.2014 16:31

почему никто не проверил tagName var $target = $(event.target); if ($target.get(0).tagName.toUpperCase() === "BODY"){ console.info("you click the body"); }

ncubica 21.11.2014 03:45

closest не работает для динамически загружаемых элементов (например, элементов, загружаемых с помощью CollectionView backbone.marionettejs).

AliBZ 24.02.2015 01:56

У меня это не сработало, когда на странице был только один элемент. Если бы у меня было несколько элементов на странице, все работало бы нормально.

BadHorsie 12.03.2015 16:35

намного лучше, особенно с веб-приложениями, которые перехватывают поведение внутренних ссылок для использования HTML5 History API. (так, все они)

ProblemsOfSumit 13.06.2015 16:23
Без jQuery - !element.contains(event.target) using Node.contains ()
Alex Ross 31.07.2015 04:56

На мой взгляд, это лучший выбор. Остановить распространение события очень сложно, если в div, который вы хотите скрыть, есть что-то внутри себя.

pablito.aven 20.08.2015 17:37

Делаю так: $(event.target).closest('#menucontainer').size() == 0

Wallace Maxters 21.09.2015 22:50

Для адаптивного дизайна используйте вместо щелчка следующее: $ (document) .on ('touchstart click', function (event) {...

Craig Jacobs 24.11.2015 00:16

Это отлично работает, однако иногда $ (event.target) появляется как пустая строка ... есть идеи, почему? Я каждый раз нажимаю на одну и ту же цель.

Caio Mar 07.01.2016 17:07

«Метод .closest() начинает поиск с самого элемента перед продвижением вверх по дереву DOM» - api.jquery.com/closest. Таким образом, должно быть возможно удалить `&&! $ (Event.target) .is ('# menucontainer')`.

Bjørn Stenfeldt 08.02.2016 10:50

У меня было 2 события щелчка по документу, потому что 2 контейнера нужно было закрывать отдельно при щелчке за пределами них. На самом деле это вызвало у меня некоторые проблемы, потому что щелчок по ссылке, чтобы открыть второй контейнер, не закрывал первый, и наоборот. Но по отдельности работали нормально. Исправление оказалось событием mouseup, а не событием щелчка в документе.

Bjørn Stenfeldt 08.02.2016 11:32

Кажется, это работает, но не на мобильных устройствах, можно ли это подтвердить?

Ryan 09.02.2016 03:10

Я согласен с @ Bjørn Stenfeldt. Нет необходимости в части !$(event.target).is('#menucontainer') при использовании .closest()

Khalid T. 23.02.2016 16:46

Фантастическое решение и отлично работает. Я только что адаптировал $ ('# menucontainer'). Hide (); раздел в $ ('# menucontainer'). removeClass ('showMenu'); работать с моим меню всех продуктов.

Sprose 06.06.2016 12:41

Я продвигаю это, потому что в нем есть проверка !$(event.target).closest('#element').length, которая была именно тем, что мне было нужно в моем собственном приложении.

bdb.jack 17.01.2017 19:28

Это идеальное решение - оно делает то, что нужно, без нарушения других элементов в контейнере. У меня возникла проблема, когда ссылки Ajax не работали с stopPropagation ().

markthewizard1234 07.04.2017 13:05

работает как шарм! Я использовал .parents () вместо .closest (), потому что у моих элементов есть несколько уровней внутри.

Chris Chalmers 29.07.2017 02:49

Если вы хотите ограничить эту функцию только обнаружением щелчка, но не воздействовать на него, вы можете обернуть всю функцию в обещание, удалить скрытие элемента и разрешить обещание после вызова removeEventListener. Вы можете использовать .then (...), чтобы определить щелчок, из которого вы вызвали эту функцию. Лучшая инкапсуляция, более ясное понимание того, что происходит.

Robert Munn 19.03.2019 18:00

спасибо за ваше решение. Отлично работает. Но я заметил, что когда я добавляю этот обработчик во время другого события щелчка, слушатель вызывается во время фазы всплытия события. Чтобы решить эту проблему, я придумал такое решение, как setTimeout((x)=>{ document.addEventListener('click', outsideClickListener);},1);.

Shmalex 01.04.2019 21:03

Функция:

$(function() {
    $.fn.click_inout = function(clickin_handler, clickout_handler) {
        var item = this;
        var is_me = false;
        item.click(function(event) {
            clickin_handler(event);
            is_me = true;
        });
        $(document).click(function(event) {
            if (is_me) {
                is_me = false;
            } else {
                clickout_handler(event);
            }
        });
        return this;
    }
});

Использование:

this.input = $('<input>')
    .click_inout(
        function(event) { me.ShowTree(event); },
        function() { me.Hide(); }
    )
    .appendTo(this.node);

А функции очень простые:

ShowTree: function(event) {
    this.data_span.show();
}
Hide: function() {
    this.data_span.hide();
}

Разве это не вызовет событие щелчка также в случае щелчка по дочернему элементу контейнера?

Jānis Elmeris 20.08.2012 18:28

Я добился успеха с чем-то вроде этого:

var $menuscontainer = ...;

$('#trigger').click(function() {
  $menuscontainer.show();

  $('body').click(function(event) {
    var $target = $(event.target);

    if ($target.parents('#menuscontainer').length == 0) {
      $menuscontainer.hide();
    }
  });
});

Логика такова: когда отображается #menuscontainer, привяжите обработчик щелчка к телу, которое скрывает #menuscontainer, только если цель (щелчка) не является его дочерним элементом.

Использовать:

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

Я не думаю, что вам действительно нужно закрывать меню, когда пользователь щелкает наружу; вам нужно, чтобы меню закрывалось, когда пользователь щелкает в любом месте страницы. Если вы нажмете на меню или вне меню, оно должно закрываться, верно?

Отсутствие удовлетворительных ответов выше побудило меня написать это сообщение в блоге на днях. Для более педантичных есть ряд подводных камней, на которые следует обратить внимание:

  1. Если вы прикрепляете обработчик события щелчка к элементу тела во время щелчка, обязательно дождитесь второго щелчка перед закрытием меню и отменой привязки события. В противном случае событие щелчка, открывшее меню, всплывет до слушателя, который должен закрыть меню.
  2. Если вы используете event.stopPropogation () для события щелчка, никакие другие элементы на вашей странице не могут иметь функцию «щелкнуть в любом месте, чтобы закрыть».
  3. Прикрепление обработчика события щелчка к элементу body на неопределенный срок не является эффективным решением.
  4. Сравнение цели события и его родителей с создателем обработчика предполагает, что вы хотите закрыть меню, когда вы щелкнете по нему, тогда как на самом деле вам нужно закрыть его, когда вы щелкнете в любом месте страницы.
  5. Прослушивание событий в элементе body сделает ваш код более хрупким. Такой невинный стиль, как он, сломал бы его: body { margin-left:auto; margin-right: auto; width:960px;}

«Если вы нажмете на меню или вне меню, оно должно закрываться, верно?» не всегда. Отмена щелчка путем перетаскивания элемента по-прежнему вызовет щелчок на уровне документа, но намерение не будет заключаться в продолжении закрытия меню. Существует также множество других типов диалогов, которые могут использовать поведение «щелчка мышью», что позволяет выполнять внутренний щелчок.

zzzzBov 12.07.2016 02:18

Я нашел этот метод в каком-то плагине календаря jQuery.

function ClickOutsideCheck(e)
{
  var el = e.target;
  var popup = $('.popup:visible')[0];
  if (popup==undefined)
    return true;

  while (true){
    if (el == popup ) {
      return true;
    } else if (el == document) {
      $(".popup").hide();
      return false;
    } else {
      el = $(el).parent()[0];
    }
  }
};

$(document).bind('mousedown.popup', ClickOutsideCheck);

$(document).click(function() {
    $(".overlay-window").hide();
});
$(".overlay-window").click(function() {
    return false;
});

Если вы нажмете на документ, скройте данный элемент, если вы не нажмете на тот же элемент.

Я так делал в Юй 3:

// Detect the click anywhere other than the overlay element to close it.
Y.one(document).on('click', function (e) {
    if (e.target.ancestor('#overlay') === null && e.target.get('id') != 'show' && overlay.get('visible') == true) {
        overlay.hide();
    }
});

Я проверяю, не является ли предок контейнером элемента виджета,
если целью не является открывающий виджет / элемент,
если виджет / элемент, который я хочу закрыть, уже открыт (не так важно).

Как сказал другой плакат, есть много ошибок, особенно если отображаемый элемент (в данном случае меню) имеет интерактивные элементы. Я нашел следующий метод довольно надежным:

$('#menuscontainer').click(function(event) {
    //your code that shows the menus fully

    //now set up an event listener so that clicking anywhere outside will close the menu
    $('html').click(function(event) {
        //check up the tree of the click target to check whether user has clicked outside of menu
        if ($(event.target).parents('#menuscontainer').length==0) {
            // your code to hide menu

            //this event listener has done its job so we can unbind it.
            $(this).unbind(event);
        }

    })
});

Вот мой код:

// 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();
});

Это мое решение этой проблемы:

$(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();
  });
});

jQuery().ready(function(){
    $('#nav').click(function (event) {
        $(this).addClass('activ');
        event.stopPropagation();
    });

    $('html').click(function () {
        if ( $('#nav').hasClass('activ') ){
            $('#nav').removeClass('activ');
        }
    });
});

Подключите к документу прослушиватель событий щелчка. Внутри прослушивателя событий вы можете посмотреть на объект события, в частности на event.target, чтобы узнать, какой элемент был нажат:

$(document).click(function(e){
    if ($(e.target).closest("#menuscontainer").length == 0) {
        // .closest can help you determine if the element 
        // or one of its ancestors is #menuscontainer
        console.info("hide");
    }
});

Это сработало для меня отлично !!

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});

Это решение хорошо сработало для того, что я делал, где мне все еще нужно было всплыть событие щелчка, спасибо.

Mike 23.07.2012 06:10

Если честно, ни одно из предыдущих решений мне не понравилось.

Лучший способ сделать это - привязать событие «щелчок» к документу и сравнить, действительно ли этот щелчок находится за пределами элемента (как сказал Арт в своем предложении).

Однако там у вас возникнут некоторые проблемы: вы никогда не сможете его отвязать, и у вас не может быть внешней кнопки для открытия / закрытия этого элемента.

Вот почему я написал этот небольшой плагин (щелкните здесь, чтобы перейти по ссылке), чтобы упростить эти задачи. Что может быть проще?

<a id='theButton' href = "#">Toggle the menu</a><br/>
<div id='theMenu'>
    I should be toggled when the above menu is clicked,
    and hidden when user clicks outside.
</div>

<script>
$('#theButton').click(function(){
    $('#theMenu').slideDown();
});
$("#theMenu").dClickOutside({ ignoreList: $("#theButton") }, function(clickedObj){
    $(this).slideUp();
});
</script>

Просто предупреждение, что с помощью этого:

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

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

Это предотвращает драйвер Рубин на рельсах UJS не работает должным образом. Например, link_to 'click', '/url', :method => :delete не подойдет.

Это может быть обходной путь:

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

$('#menucontainer').click(function(event){
  if (!$(event.target).data('method')) {
    event.stopPropagation();
  }
});

Это должно работать:

$('body').click(function (event) {
    var obj = $(event.target);
    obj = obj['context']; // context : clicked element inside body
    if ($(obj).attr('id') != "menuscontainer" && $('#menuscontainer').is(':visible') == true) {
        //hide menu
    }
});

$ ("body> div: not (#dvid)"). click (function (e) {// ваш код});

Mahesh Gaikwad 29.10.2014 14:57

 <div class = "feedbackCont" onblur = "hidefeedback();">
        <div class = "feedbackb" onclick = "showfeedback();" ></div>
        <div class = "feedbackhide" tabindex = "1"> </div>
 </div>

function hidefeedback(){
    $j(".feedbackhide").hide();
}

function showfeedback(){
    $j(".feedbackhide").show();
    $j(".feedbackCont").attr("tabindex",1).focus();
}

Это самое простое решение, которое я придумал.

Еще одно решение здесь:

http://jsfiddle.net/zR76D/

Использование:

<div onClick = "$('#menu').toggle();$('#menu').clickOutside(function() { $(this).hide(); $(this).clickOutside('disable'); });">Open / Close Menu</div>
<div id = "menu" style = "display: none; border: 1px solid #000000; background: #660000;">I am a menu, whoa is me.</div>

Плагин:

(function($) {
    var clickOutsideElements = [];
    var clickListener = false;

    $.fn.clickOutside = function(options, ignoreFirstClick) {
        var that = this;
        if (ignoreFirstClick == null) ignoreFirstClick = true;

        if (options != "disable") {
            for (var i in clickOutsideElements) {
                if (clickOutsideElements[i].element[0] == $(this)[0]) return this;
            }

            clickOutsideElements.push({ element : this, clickDetected : ignoreFirstClick, fnc : (typeof(options) != "function") ? function() {} : options });

            $(this).on("click.clickOutside", function(event) {
                for (var i in clickOutsideElements) {
                    if (clickOutsideElements[i].element[0] == $(this)[0]) {
                        clickOutsideElements[i].clickDetected = true;
                    }
                }
            });

            if (!clickListener) {
                if (options != null && typeof(options) == "function") {
                    $('html').click(function() {
                        for (var i in clickOutsideElements) {
                            if (!clickOutsideElements[i].clickDetected) {
                                clickOutsideElements[i].fnc.call(that);
                            }
                            if (clickOutsideElements[i] != null) clickOutsideElements[i].clickDetected = false;
                        }
                    });
                    clickListener = true;
                }
            }
        }
        else {
            $(this).off("click.clickoutside");
            for (var i = 0; i < clickOutsideElements.length; ++i) {
                if (clickOutsideElements[i].element[0] == $(this)[0]) {
                    clickOutsideElements.splice(i, 1);
                }
            }
        }

        return this;
    }
})(jQuery);

Самый широкий способ сделать это - выбрать все на веб-странице, кроме элемента, в котором вы не хотите обнаруживать клики, и привязать событие щелчка к событию при открытии меню.

Потом при закрытии меню убираем привязку.

Используйте .stopPropagation, чтобы событие не повлияло на любую часть контейнера меню.

$("*").not($("#menuscontainer")).bind("click.OutsideMenus", function ()
{
    // hide the menus

    //then remove all of the handlers
    $("*").unbind(".OutsideMenus");
});

$("#menuscontainer").bind("click.OutsideMenus", function (event) 
{
    event.stopPropagation(); 
});

Решения здесь работают нормально когда нужно управлять только одним элементом. Однако, если элементов несколько, проблема намного сложнее. Уловки с e.stopPropagation () и всеми остальными не сработают.

Я придумал решение, может быть, это не так просто, но это лучше, чем ничего. Посмотри:

$view.on("click", function(e) {

    if (model.isActivated()) return;

        var watchUnclick = function() {
            rootView.one("mouseleave", function() {
                $(document).one("click", function() {
                    model.deactivate();
                });
                rootView.one("mouseenter", function() {
                    watchUnclick();
                });
            });
        };
        watchUnclick();
        model.activate();
    });

Я думаю, что onmouseenter и onmouseleave - это решение, проверьте это jsfiddle.net/1r73jm8m

Puerto AGP 01.12.2017 02:29

Вместо использования прерывания потока, события размытия / фокуса или любых других сложных приемов просто сопоставьте поток событий с родством элемента:

$(document).on("click.menu-outside", function(event){
    // Test if target and it's parent aren't #menuscontainer
    // That means the click event occur on other branch of document tree
    if (!$(event.target).parents().andSelf().is("#menuscontainer")){
        // Click outisde #menuscontainer
        // Hide the menus (but test if menus aren't already hidden)
    }
});

Чтобы удалить внешний прослушиватель событий щелчка, просто:

$(document).off("click.menu-outside");

Для меня это было лучшее решение. Использовал его в обратном вызове после анимации, поэтому мне нужно было как-то отключить событие. +1

qwertzman 29.05.2014 15:09

Здесь есть избыточная проверка. если элемент действительно #menuscontainer, вы все еще проходите через его родителей. вы должны сначала это проверить, а если это не тот элемент, то подняться по дереву DOM.

vsync 25.08.2014 18:25

Верно ! Вы можете изменить условие на if (!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){. Это небольшая оптимизация, но она происходит всего несколько раз за время существования вашей программы: для каждого щелчка, если зарегистрировано событие click.menu-outside. Он длиннее (+32 символа) и не использует цепочку методов

mems 25.08.2014 18:40

Ответ, помеченный как принятый, не учитывает возможность наложения поверх элемента, например диалогов, всплывающих окон, датпикеров и т. д. Щелчки в них не должны скрывать элемент.

Я сделал свою версию, которая учитывает это. Он создается как привязка KnockoutJS, но его можно легко преобразовать только в jQuery.

Он работает по первому запросу для всех видимых элементов с z-индексом или абсолютной позицией. Затем он проверяет эти элементы на предмет, который я хочу скрыть, если щелкнуть снаружи. Если это попадание, я вычисляю новый связанный прямоугольник, который учитывает границы наложения.

ko.bindingHandlers.clickedIn = (function () {
    function getBounds(element) {
        var pos = element.offset();
        return {
            x: pos.left,
            x2: pos.left + element.outerWidth(),
            y: pos.top,
            y2: pos.top + element.outerHeight()
        };
    }

    function hitTest(o, l) {
        function getOffset(o) {
            for (var r = { l: o.offsetLeft, t: o.offsetTop, r: o.offsetWidth, b: o.offsetHeight };
                o = o.offsetParent; r.l += o.offsetLeft, r.t += o.offsetTop);
            return r.r += r.l, r.b += r.t, r;
        }

        for (var b, s, r = [], a = getOffset(o), j = isNaN(l.length), i = (j ? l = [l] : l).length; i;
            b = getOffset(l[--i]), (a.l == b.l || (a.l > b.l ? a.l <= b.r : b.l <= a.r))
                && (a.t == b.t || (a.t > b.t ? a.t <= b.b : b.t <= a.b)) && (r[r.length] = l[i]));
        return j ? !!r.length : r;
    }

    return {
        init: function (element, valueAccessor) {
            var target = valueAccessor();
            $(document).click(function (e) {
                if (element._clickedInElementShowing === false && target()) {
                    var $element = $(element);
                    var bounds = getBounds($element);

                    var possibleOverlays = $("[style*=z-index],[style*=absolute]").not(":hidden");
                    $.each(possibleOverlays, function () {
                        if (hitTest(element, this)) {
                            var b = getBounds($(this));
                            bounds.x = Math.min(bounds.x, b.x);
                            bounds.x2 = Math.max(bounds.x2, b.x2);
                            bounds.y = Math.min(bounds.y, b.y);
                            bounds.y2 = Math.max(bounds.y2, b.y2);
                        }
                    });

                    if (e.clientX < bounds.x || e.clientX > bounds.x2 ||
                        e.clientY < bounds.y || e.clientY > bounds.y2) {

                        target(false);
                    }
                }
                element._clickedInElementShowing = false;
            });

            $(element).click(function (e) {
                e.stopPropagation();
            });
        },
        update: function (element, valueAccessor) {
            var showing = ko.utils.unwrapObservable(valueAccessor());
            if (showing) {
                element._clickedInElementShowing = true;
            }
        }
    };
})();

Это более общее решение, чем позволяет наблюдать за несколькими элементами, а также динамически добавлять и удалять элементы из очереди.

Он содержит глобальную очередь (autoCloseQueue) - объект-контейнер для элементов, которые должны быть закрыты при внешних щелчках.

Каждый ключ объекта очереди должен быть идентификатором элемента DOM, а значение должно быть объектом с двумя функциями обратного вызова:

 {onPress: someCallbackFunction, onOutsidePress: anotherCallbackFunction}

Поместите это в свой обратный вызов готового документа:

window.autoCloseQueue = {}  

$(document).click(function(event) {
    for (id in autoCloseQueue){
        var element = autoCloseQueue[id];
        if ( ($(e.target).parents('#' + id).length) > 0) { // This is a click on the element (or its child element)
            console.info('This is a click on an element (or its child element) with  id: ' + id);
            if (typeof element.onPress == 'function') element.onPress(event, id);
        } else { //This is a click outside the element
            console.info('This is a click outside the element with id: ' + id);
            if (typeof element.onOutsidePress == 'function') element.onOutsidePress(event, id); //call the outside callback
            delete autoCloseQueue[id]; //remove the element from the queue
        }
    }
});

Затем, когда будет создан элемент DOM с идентификатором 'менюконтейнер', просто добавьте этот объект в очередь:

window.autoCloseQueue['menuscontainer'] = {onOutsidePress: clickOutsideThisElement}

В итоге я сделал что-то вроде этого:

$(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;
});

У меня есть кнопка закрытия в новом контейнере для удобного пользовательского интерфейса. Мне пришлось использовать return false, чтобы не пройти. Конечно, было бы неплохо иметь там HREF, который отвезет вас куда-нибудь, или вы могли бы вместо этого вызвать какой-нибудь материал ajax. В любом случае, у меня это работает нормально. Как раз то, что я хотел.

Попробуйте этот код:

if ($(event.target).parents().index($('#searchFormEdit')) == -1 &&
    $(event.target).parents().index($('.DynarchCalendar-topCont')) == -1 &&
    (_x < os.left || _x > (os.left + 570) || _y < os.top || _y > (os.top + 155)) &&
    isShowEditForm) {

    setVisibleEditForm(false);
}

Вы можете установить tabindex для элемента ДОМ. Это вызовет событие размытия, когда пользователь щелкнет за пределами элемента DOM.

Демо

<div tabindex = "1">
    Focus me
</div>

document.querySelector("div").onblur = function(){
   console.info('clicked outside')
}
document.querySelector("div").onfocus = function(){
   console.info('clicked inside')
}

Это переключит меню Nav при нажатии / выключении элемента.

$(document).on('click', function(e) {
    var elem = $(e.target).closest('#menu'),
    box = $(e.target).closest('#nav');
 if (elem.length) {
    e.preventDefault();
    $('#nav').toggle();
  } else if (!box.length) {
    $('#nav').hide();
 }
});



<li id = "menu"><a></a></li>
<ul id = "nav" >  //Nav will toggle when you Click on Menu(it can be an icon in this example)
        <li class = "page"><a>Page1</a></li>
        <li class = "page"><a>Pag2</a></li>
        <li class = "page"><a>Page3</a></li>            
        <li class = "page"><a>Page4</a></li>
</ul>

Стандартный HTML:

Окружите меню <label> и получите изменения состояния фокуса.

http://jsfiddle.net/bK3gL/

Плюс: разворачивать меню можно по Tab.

плюс: меню можно развернуть с помощью <kbd> вкладки </kbd>

Grim 10.06.2014 17:21

В качестве обертка к этому замечательному ответу от Art и для использования синтаксиса, изначально запрошенного OP, вот расширение jQuery, которое может записывать, произошел ли щелчок за пределами установленного элемента.

$.fn.clickOutsideThisElement = function (callback) {
    return this.each(function () {
        var self = this;
        $(document).click(function (e) {
            if (!$(e.target).closest(self).length) {
                callback.call(self, e)
            }
        })
    });
};

Тогда вы можете вызвать так:

$("#menuscontainer").clickOutsideThisElement(function() {
    // handle menu toggle
});

Вот демонстрация скрипки

Как вариант:

var $menu = $('#menucontainer');
$(document).on('click', function (e) {

    // If element is opened and click target is outside it, hide it
    if ($menu.is(':visible') && !$menu.is(e.target) && !$menu.has(e.target).length) {
        $menu.hide();
    }
});

У него нет проблем с остановка распространения события, и он лучше поддерживает несколько меню на одной странице, где нажатие на второе меню, когда первое открыто, оставит первое открытым в решении stopPropagation.

Для сенсорных устройств, таких как iPad и iPhone, мы можем использовать этот код:

$(document).on('touchstart', function (event) {
    var container = $("YOUR CONTAINER SELECTOR");

    if (!container.is(e.target) &&            // If the target of the click isn't the container...
        container.has(e.target).length === 0) // ... nor a descendant of the container
    {
        container.hide();
    }
});

Использование not ():

$("#id").not().click(function() {
    alert('Clicked other that #id');
});

Это не работает. not() удаляет элемент из списка выбранных элементов (api.jquery.com/not). Без селектора в качестве параметра он ничего не делает, возвращая $('#id'), что является полной противоположностью тому, что мы пытаемся выполнить.

flu 04.12.2014 21:20

$("body > div:not(#dvid)").click(function (e) {
    //your code
}); 

Добавить обработчик кликов ко всем остальным элементам? Вы убьете производительность. Есть гораздо лучшие способы сделать это.

bguiz 29.10.2014 15:48

Это добавляет обработчик щелчка ко всем элементам div, кроме #dvid, это очень дорого для страницы, и если у вас есть какой-то div внутри #divid, также у них есть обработчик, и это может быть неожиданным эффектом.

Migio B 28.02.2015 23:31

$("html").click(function(){
    if ($('#info').css("opacity")>0.9) {
        $('#info').fadeOut('fast');
    }
});

Решение1

Вместо использования event.stopPropagation (), который может иметь некоторые побочные эффекты, просто определите простую переменную флага и добавьте одно условие if. Я протестировал это и работал правильно без каких-либо побочных эффектов stopPropagation:

var flag = "1";
$('#menucontainer').click(function(event){
    flag = "0"; // flag 0 means click happened in the area where we should not do any action
});

$('html').click(function() {
    if (flag != "0"){
        // Hide the menus if visible
    }
    else {
        flag = "1";
    }
});

Решение2

С простым условием if:

$(document).on('click', function(event){
    var container = $("#menucontainer");
    if (!container.is(event.target) &&            // If the target of the click isn't the container...
        container.has(event.target).length === 0) // ... nor a descendant of the container
    {
        // Do whatever you want to do when click is outside the element
    }
});

Я использовал это решение с логическим флагом, и это хорошо также с артикулированным DOm, а также, если внутри #menucontainer есть много других элементов

Migio B 28.02.2015 23:28

Решение 1 работает лучше, потому что оно обрабатывает случаи, когда цель клика удаляется из DOM к моменту распространения события в документ.

Alice 18.02.2020 17:23

Вот ванильное решение JavaScript для будущих зрителей.

При щелчке любого элемента в документе, если идентификатор элемента, по которому был выполнен щелчок, переключается или скрытый элемент не скрыт, а скрытый элемент не содержит элемент, по которому щелкнули, переключите элемент.

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();

(function () {
    "use strict";
    var hidden = document.getElementById('hidden');
    document.addEventListener('click', function (e) {
        if (e.target.id == 'toggle' || (hidden.style.display != 'none' && !hidden.contains(e.target))) hidden.style.display = hidden.style.display == 'none' ? 'block' : 'none';
    }, false);
})();
<a href = "javascript:void(0)" id = "toggle">Toggle Hidden Div</a>
<div id = "hidden" style = "display: none;">This content is normally hidden. click anywhere other than this content to make me disappear</div>

Если у вас будет несколько переключателей на одной странице, вы можете использовать что-то вроде этого:

  1. Добавьте имя класса hidden в складной элемент.
  2. После щелчка по документу закройте все скрытые элементы, которые не содержат выбранный элемент и не скрыты.
  3. Если выбранный элемент является переключателем, переключите указанный элемент.

(function () {
    "use strict";
    var hiddenItems = document.getElementsByClassName('hidden'), hidden;
    document.addEventListener('click', function (e) {
        for (var i = 0; hidden = hiddenItems[i]; i++) {
            if (!hidden.contains(e.target) && hidden.style.display != 'none')
                hidden.style.display = 'none';
        }
        if (e.target.getAttribute('data-toggle')) {
            var toggle = document.querySelector(e.target.getAttribute('data-toggle'));
            toggle.style.display = toggle.style.display == 'none' ? 'block' : 'none';
        }
    }, false);
})();
<a href = "javascript:void(0)" data-toggle = "#hidden1">Toggle Hidden Div</a>
<div class = "hidden" id = "hidden1" style = "display: none;" data-hidden = "true">This content is normally hidden</div>
<a href = "javascript:void(0)" data-toggle = "#hidden2">Toggle Hidden Div</a>
<div class = "hidden" id = "hidden2" style = "display: none;" data-hidden = "true">This content is normally hidden</div>
<a href = "javascript:void(0)" data-toggle = "#hidden3">Toggle Hidden Div</a>
<div class = "hidden" id = "hidden3" style = "display: none;" data-hidden = "true">This content is normally hidden</div>

Мы реализовали решение, частично основанное на комментарии пользователя выше, которое идеально подходит для нас. Мы используем его, чтобы скрыть окно поиска / результаты при нажатии за пределами этих элементов, за исключением элемента, который изначально.

// HIDE SEARCH BOX IF CLICKING OUTSIDE
$(document).click(function(event){ 
    // IF NOT CLICKING THE SEARCH BOX OR ITS CONTENTS OR SEARCH ICON 
    if ($("#search-holder").is(":visible") && !$(event.target).is("#search-holder *, #search")) {
        $("#search-holder").fadeOut('fast');
        $("#search").removeClass('active');
    }
});

Сначала он также проверяет, отображается ли уже поле поиска, и в нашем случае он также удаляет активный класс на кнопке скрытия / отображения поиска.

Проголосуйте за самый популярный ответ, но добавьте

&& (e.target != $('html').get(0)) // ignore the scrollbar

Итак, щелчок по полосе прокрутки не [скрывает или что-то еще] ваш целевой элемент.

Это классический случай, когда изменение HTML было бы лучшим решением. Почему бы не установить щелчок по элементам, не содержащим пункта меню? Тогда добавлять размножение не нужно.

$('.header, .footer, .main-content').click(function() {
//Hide the menus if visible
});

Это плохая идея, потому что, если позже вы решите добавить какой-то нижний колонтитул2 или что-то еще между ними, вам придется не забыть добавить и его. $ ('html'). click () проще и позволяет работать в любом случае.

Marcio 21.10.2015 12:32

Плагин внешнего клика!

Использование:

$('.target-element').outsideClick(function(event){
    //code that fires when user clicks outside the element
    //event = the click event
    //$(this) = the '.target-element' that is firing this function 
}, '.excluded-element')

Код для этого:

(function($) {

//when the user hits the escape key, it will trigger all outsideClick functions
$(document).on("keyup", function (e) {
    if (e.which == 27) $('body').click(); //escape key
});

//The actual plugin
$.fn.outsideClick = function(callback, exclusions) {
    var subject = this;

    //test if exclusions have been set
    var hasExclusions = typeof exclusions !== 'undefined';

    //switches click event with touch event if on a touch device
    var ClickOrTouchEvent = "ontouchend" in document ? "touchend" : "click";

    $('body').on(ClickOrTouchEvent, function(event) {
        //click target does not contain subject as a parent
        var clickedOutside = !$(event.target).closest(subject).length;

        //click target was on one of the excluded elements
        var clickedExclusion = $(event.target).closest(exclusions).length;

        var testSuccessful;

        if (hasExclusions) {
            testSuccessful = clickedOutside && !clickedExclusion;
        } else {
            testSuccessful = clickedOutside;
        }

        if (testSuccessful) {
            callback.call(subject, event);
        }
    });

    return this;
};

}(jQuery));

Взято из этого ответа https://stackoverflow.com/a/3028037/1611058

После исследования я нашел три рабочих решения (я забыл ссылки на страницы для справки)

Первое решение

<script>
    //The good thing about this solution is it doesn't stop event propagation.

    var clickFlag = 0;
    $('body').on('click', function () {
        if (clickFlag == 0) {
            console.info('hide element here');
            /* Hide element here */
        }
        else {
            clickFlag=0;
        }
    });
    $('body').on('click','#testDiv', function (event) {
        clickFlag = 1;
        console.info('showed the element');
        /* Show the element */
    });
</script>

Второе решение

<script>
    $('body').on('click', function(e) {
        if ($(e.target).closest('#testDiv').length == 0) {
           /* Hide dropdown here */
        }
    });
</script>

Третье решение

<script>
    var specifiedElement = document.getElementById('testDiv');
    document.addEventListener('click', function(event) {
        var isClickInside = specifiedElement.contains(event.target);
        if (isClickInside) {
          console.info('You clicked inside')
        }
        else {
          console.info('You clicked outside')
        }
    });
</script>

Третье решение - безусловно, самый элегантный способ проверки. Это также не связано с накладными расходами jQuery. Очень хорошо. Это очень помогло. Спасибо.

dbarth 27.07.2016 17:49

Я пытаюсь получить третье решение с несколькими элементами document.getElementsByClassName, если у кого-то есть подсказка, поделитесь.

lowtechsun 16.01.2017 11:42

@lowtechsun Вам нужно будет пройти через цикл, чтобы проверить каждый.

Donnie D'Amato 13.02.2017 20:28

Мне действительно понравилось третье решение, однако триггеры щелчка до того, как мой div начал показывать, что скрывает его снова, есть идеи, почему?

indiehjaerta 14.04.2018 03:49

Третий, конечно, позволяет использовать console.info, но он не позволяет вам закрыться, установив для display значение none - потому что он сделает это до того, как отобразится меню. У вас есть решение для этого?

RolandiXor 09.10.2018 14:32

Метод DOMTokenList.contains () - это решение.

Saabbir 25.09.2019 16:22

Третье решение не позволяет мне открыть свой div.

KylianMbappe 18.10.2020 19:56

Может ты что-то упустил ?? поделитесь своим минимальным кодом с помощью jsfiddle. Я могу разобраться в этом, когда буду свободен

Rameez Rami 19.10.2020 21:08

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

    $(document).on('click',  function(e){
        // code
    });

Простое решение ситуации:

$(document).mouseup(function (e)
{
    var container = $("YOUR SELECTOR"); // Give you class or ID

    if (!container.is(e.target) &&            // If the target of the click is not the desired div or section
        container.has(e.target).length === 0) // ... nor a descendant-child of the container
    {
        container.hide();
    }
});

Приведенный выше сценарий скроет div, если событие щелчка вне div сработает.

Дополнительную информацию можно найти в следующем блоге: http://www.codecanal.com/detect-click-outside-div-using-javascript/

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

$('#menucontainer').click(function(event){
    event.stopPropagation();
});
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
 <button id='#menucontainer'>Ok</button> 
</html>

Попробуйте это:

$('html').click(function(e) {
  if ($(e.target).parents('#menuscontainer').length == 0) {
    $('#menuscontainer').hide();
  }
});

https://jsfiddle.net/4cj4jxy0/

Но обратите внимание, что это не может работать, если событие щелчка не может достичь тега html. (Может у других элементов есть stopPropagation()).

Подпишитесь на фаза захвата щелчка, чтобы обработать щелчок по элементам, которые вызывают preventDefault.
Повторно запускайте его на элементе документа, используя другое имя click-anywhere.

document.addEventListener('click', function (event) {
  event = $.event.fix(event);
  event.type = 'click-anywhere';
  $document.trigger(event);
}, true);

Затем, если вам нужно щелкнуть внешнюю функциональность, подпишитесь на событие click-anywhere на document и проверьте, был ли щелчок за пределами интересующего вас элемента:

$(document).on('click-anywhere', function (event) {
  if (!$(event.target).closest('#smth').length) {
    // Do anything you need here
  }
});

Некоторые примечания:

  • Вы должны использовать document, поскольку запуск события для всех элементов, за пределами которых произошел щелчок, приведет к ошибке производительности.

  • Эту функциональность можно обернуть в специальный плагин, который вызывает некоторый обратный вызов при внешнем клике.

  • Вы не можете подписаться на фазу захвата, используя сам jQuery.

  • Вам не нужно загружать документы для подписки, так как подписка есть на document, даже не на body, поэтому она существует всегда независимо от размещения скриптов и статуса загрузки.

+1 за упоминание фазы capture - определенно снижает риск того, что stopPropagation() убьет вещи (в зависимости от того, куда вы прикрепите слушателя).

Nate Whittaker 16.06.2016 19:59

Чтобы упростить использование и сделать код более выразительным, я создал для этого плагин jQuery:

$('div.my-element').clickOut(function(target) { 
    //do something here... 
});

Примечание. цель - это элемент, на который фактически щелкнул пользователь. Но обратный вызов по-прежнему выполняется в контексте исходного элемента, поэтому вы можете использовать это, как и следовало ожидать, в обратном вызове jQuery.

Плагин:

$.fn.clickOut = function (parent, fn) {
    var context = this;
    fn = (typeof parent === 'function') ? parent : fn;
    parent = (parent instanceof jQuery) ? parent : $(document);

    context.each(function () {
        var that = this;
        parent.on('click', function (e) {
            var clicked = $(e.target);
            if (!clicked.is(that) && !clicked.parents().is(that)) {
                if (typeof fn === 'function') {
                    fn.call(that, clicked);
                }
            }
        });

    });
    return context;
};

По умолчанию прослушиватель событий щелчка размещается в документе. Однако, если вы хотите ограничить область прослушивания событий, вы можете передать объект jQuery, представляющий элемент родительского уровня, который будет верхним родительским элементом, на котором будут прослушиваться клики. Это предотвращает ненужные прослушиватели событий на уровне документа. Очевидно, это не сработает, если предоставленный родительский элемент не является родителем вашего исходного элемента.

Используйте так:

$('div.my-element').clickOut($('div.my-parent'), function(target) { 
    //do something here...
});

$(document).on('click.menu.hide', function(e){
  if ( !$(e.target).closest('#my_menu').length ) {
    $('#my_menu').find('ul').toggleClass('active', false);
  }
});

$(document).on('click.menu.show', '#my_menu li', function(e){
  $(this).find('ul').toggleClass('active');
});
div {
  float: left;
}

ul {
  padding: 0;
  position: relative;
}
ul li {
  padding: 5px 25px 5px 10px;
  border: 1px solid silver;
  cursor: pointer;
  list-style: none;
  margin-top: -1px;
  white-space: nowrap;
}
ul li ul:before {
  margin-right: -20px;
  position: absolute;
  top: -17px;
  right: 0;
  content: "BC";
}
ul li ul li {
  visibility: hidden;
  height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-width: 0 0 1px 0;
}
ul li ul li:last-child {
  border: none;
}
ul li ul.active:before {
  content: "B2";
}
ul li ul.active li {
  display: list-item;
  visibility: visible;
  height: inherit;
  padding: 5px 25px 5px 10px;
}
<script src = "https://code.jquery.com/jquery-2.1.4.js"></script>
<div>
  <ul id = "my_menu">
    <li>Menu 1
      <ul>
        <li>subMenu 1</li>
        <li>subMenu 2</li>
        <li>subMenu 3</li>
        <li>subMenu 4</li>
      </ul>
    </li>
    <li>Menu 2
      <ul>
        <li>subMenu 1</li>
        <li>subMenu 2</li>
        <li>subMenu 3</li>
        <li>subMenu 4</li>
      </ul>
    </li>
    <li>Menu 3</li>
    <li>Menu 4</li>
    <li>Menu 5</li>
    <li>Menu 6</li>
  </ul>
</div>

Вот версия jsbin http://jsbin.com/xopacadeni/edit?html,css,js,output

How to detect a click outside an element?

Причина, по которой этот вопрос так популярен и на него так много ответов, заключается в том, что он обманчиво сложен. После почти восьми лет и десятков ответов я искренне удивлен, увидев, как мало внимания уделяется доступности.

I would like to hide these elements when the user clicks outside the menus' area.

Это благородная причина и проблема действительный. Заголовок вопроса, на который, похоже, пытается ответить большинство ответов, содержит отвлекающий маневр.

Подсказка: это слово "щелкнуть"!

На самом деле вы не хотите связывать обработчики кликов.

Если вы привязываете обработчики кликов к закрытию диалогового окна, значит, вы уже потерпели неудачу. Причина, по которой вы потерпели неудачу, заключается в том, что не все запускают события click. Пользователи, не использующие мышь, смогут выйти из вашего диалога (и ваше всплывающее меню, возможно, является типом диалога), нажав Tab, и тогда они не смогут прочитать содержимое диалогового окна без последующего запуска click. мероприятие.

Итак, давайте перефразируем вопрос.

How does one close a dialog when a user is finished with it?

Это цель. К сожалению, теперь нам нужно привязать событие userisfinishedwiththedialog, и эта привязка не так проста.

Итак, как мы можем определить, что пользователь завершил использование диалогового окна?

focusout событие

Хорошее начало - определить, покинул ли диалог фокус.

Подсказка: будьте осторожны с событием blur, blur не распространяется, если событие было привязано к фазе восходящей цепочки!

JQuery focusout прекрасно подойдет. Если вы не можете использовать jQuery, вы можете использовать blur на этапе захвата:

element.addEventListener('blur', ..., true);
//                       use capture: ^^^^

Кроме того, для многих диалогов вам необходимо разрешить контейнеру получить фокус. Добавьте tabindex = "-1", чтобы диалоговое окно могло получать фокус динамически, не прерывая другим образом поток табуляции.

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on('focusout', function () {
  $(this).removeClass('active');
});
div {
  display: none;
}
.active {
  display: block;
}
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href = "#example">Example</a>
<div id = "example" tabindex = "-1">
  Lorem ipsum <a href = "http://example.com">dolor</a> sit amet.
</div>

Если вы играете с этой демонстрацией больше минуты, вы должны быстро заметить проблемы.

Во-первых, ссылка в диалоговом окне неактивна. Попытка щелкнуть по нему или перейти к нему приведет к закрытию диалогового окна до того, как произойдет взаимодействие. Это связано с тем, что фокусировка внутреннего элемента запускает событие focusout перед повторным запуском события focusin.

Исправление состоит в том, чтобы поставить в очередь изменение состояния в цикле событий. Это можно сделать с помощью setImmediate(...) или setTimeout(..., 0) для браузеров, не поддерживающих setImmediate. После постановки в очередь он может быть отменен последующим focusin:

$('.submenu').on({
  focusout: function (e) {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function (e) {
    clearTimeout($(this).data('submenuTimer'));
  }
});

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href = "#example">Example</a>
<div id = "example" tabindex = "-1">
  Lorem ipsum <a href = "http://example.com">dolor</a> sit amet.
</div>

Вторая проблема заключается в том, что диалоговое окно не закрывается при повторном нажатии ссылки. Это связано с тем, что диалоговое окно теряет фокус, вызывая закрытие, после чего щелчок по ссылке запускает диалоговое окно для повторного открытия.

Как и в предыдущем выпуске, необходимо управлять состоянием фокуса. Учитывая, что изменение состояния уже поставлено в очередь, это просто вопрос обработки событий фокуса на триггерах диалогового окна:

This should look familiar
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href = "#example">Example</a>
<div id = "example" tabindex = "-1">
  Lorem ipsum <a href = "http://example.com">dolor</a> sit amet.
</div>

Клавиша Esc

Если вы думали, что справились с обработкой состояний фокуса, вы можете сделать больше, чтобы упростить взаимодействие с пользователем.

Это часто бывает «приятно иметь», но обычно, когда у вас есть модальное окно или всплывающее окно любого вида, клавиша Esc закрывает его.

keydown: function (e) {
  if (e.which === 27) {
    $(this).removeClass('active');
    e.preventDefault();
  }
}

$('a').on('click', function () {
  $(this.hash).toggleClass('active').focus();
});

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('active');
      e.preventDefault();
    }
  }
});

$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});
div {
  display: none;
}
.active {
  display: block;
}
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href = "#example">Example</a>
<div id = "example" tabindex = "-1">
  Lorem ipsum <a href = "http://example.com">dolor</a> sit amet.
</div>

Если вы знаете, что у вас есть фокусируемые элементы в диалоге, вам не нужно фокусировать диалог напрямую. Если вы создаете меню, вы можете вместо этого сфокусироваться на первом пункте меню.

click: function (e) {
  $(this.hash)
    .toggleClass('submenu--active')
    .find('a:first')
    .focus();
  e.preventDefault();
}

$('.menu__link').on({
  click: function (e) {
    $(this.hash)
      .toggleClass('submenu--active')
      .find('a:first')
      .focus();
    e.preventDefault();
  },
  focusout: function () {
    $(this.hash).data('submenuTimer', setTimeout(function () {
      $(this.hash).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('submenuTimer'));  
  }
});

$('.submenu').on({
  focusout: function () {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('submenuTimer'));
  },
  keydown: function (e) {
    if (e.which === 27) {
      $(this).removeClass('submenu--active');
      e.preventDefault();
    }
  }
});
.menu {
  list-style: none;
  margin: 0;
  padding: 0;
}
.menu:after {
  clear: both;
  content: '';
  display: table;
}
.menu__item {
  float: left;
  position: relative;
}

.menu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}
.menu__link:hover,
.menu__link:focus {
  background-color: black;
  color: lightblue;
}

.submenu {
  border: 1px solid black;
  display: none;
  left: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  position: absolute;
  top: 100%;
}
.submenu--active {
  display: block;
}

.submenu__item {
  width: 150px;
}

.submenu__link {
  background-color: lightblue;
  color: black;
  display: block;
  padding: 0.5em 1em;
  text-decoration: none;
}

.submenu__link:hover,
.submenu__link:focus {
  background-color: black;
  color: lightblue;
}
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class = "menu">
  <li class = "menu__item">
    <a class = "menu__link" href = "#menu-1">Menu 1</a>
    <ul class = "submenu" id = "menu-1" tabindex = "-1">
      <li class = "submenu__item"><a class = "submenu__link" href = "http://example.com/#1">Example 1</a></li>
      <li class = "submenu__item"><a class = "submenu__link" href = "http://example.com/#2">Example 2</a></li>
      <li class = "submenu__item"><a class = "submenu__link" href = "http://example.com/#3">Example 3</a></li>
      <li class = "submenu__item"><a class = "submenu__link" href = "http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
  <li class = "menu__item">
    <a  class = "menu__link" href = "#menu-2">Menu 2</a>
    <ul class = "submenu" id = "menu-2" tabindex = "-1">
      <li class = "submenu__item"><a class = "submenu__link" href = "http://example.com/#1">Example 1</a></li>
      <li class = "submenu__item"><a class = "submenu__link" href = "http://example.com/#2">Example 2</a></li>
      <li class = "submenu__item"><a class = "submenu__link" href = "http://example.com/#3">Example 3</a></li>
      <li class = "submenu__item"><a class = "submenu__link" href = "http://example.com/#4">Example 4</a></li>
    </ul>
  </li>
</ul>
lorem ipsum <a href = "http://example.com/">dolor</a> sit amet.

Роли WAI-ARIA и другая поддержка специальных возможностей

Этот ответ, надеюсь, охватывает основы поддержки доступной клавиатуры и мыши для этой функции, но, поскольку она уже довольно значительна, я собираюсь избегать любого обсуждения Роли и атрибуты WAI-ARIA, однако я высоко рекомендую, чтобы разработчики обращались к спецификации для получения подробной информации о том, какие роли они должны use и любые другие подходящие атрибуты.

Это наиболее полный ответ с учетом пояснений и доступности. Я думаю, что это должен быть принятый ответ, поскольку большинство других ответов обрабатывают только щелчок и представляют собой просто фрагмент кода, сброшенный без каких-либо объяснений.

Cyrille 29.10.2016 17:39

@ Сирил, спасибо за добрые слова. Вам придется убедить OP изменить принятый ответ, если вы так сильно чувствуете.

zzzzBov 29.10.2016 19:09

Удивительно, хорошо объяснено. Я просто использовал этот подход для React Component и отлично работал, спасибо

David Lavieri 18.11.2016 06:26

@zzzzBov спасибо за подробный ответ, я пытаюсь добиться этого в ванильном JS, и я немного потерялся со всем материалом jquery. есть ли что-нибудь подобное в vanilla js?

HendrikEng 04.01.2017 06:33

@ Карле, тебе нужно задать более конкретный вопрос. jQuery написан на ванильном JS, поэтому в этом отношении: да, безусловно, есть способы сделать это без jQuery. Не могу представить, чтобы этот ответ вам хоть как-то помог. Если вы ищете, чтобы я написал вам версию без jQuery, мне придется заранее отказаться.

zzzzBov 04.01.2017 06:55

@zzzzBov, нет, я, конечно, не ищу, чтобы вы написали версию без jQuery, я постараюсь это сделать, и я думаю, что лучше всего задать здесь новый вопрос, если я действительно застряну. Еще раз большое спасибо.

HendrikEng 04.01.2017 15:19

Я хотел бы добавить крайний случай, который помешал этому работать для меня, о котором люди должны знать. Я создавал всплывающее окно для функций аутентификации, таких как вход в систему и регистрация. Я также заядлый пользователь 1Password. Когда я нажал «Войти», появилось расширение 1Password с вопросом, хочу ли я сохранить учетные данные. Это означало, что всплывающее окно закрылось, когда были запущены связанные события focusIn / Out. В своей настройке я заметил, что элемент, который в противном случае был в фокусе, всегда был body, поэтому я заключил логику в проверку на activeElement.tagName === 'BODY'. Лучшее, что я мог сделать, я думаю

horyd 08.05.2017 01:51

Хотя это отличный метод обнаружения щелчка из настраиваемых раскрывающихся списков или других полей ввода, он никогда не должен быть предпочтительным методом для модальных окон или всплывающих окон по двум причинам. Модальное окно закроется, когда пользователь переключится на другую вкладку или окно или откроет контекстное меню, что очень раздражает. Кроме того, событие «щелчок» срабатывает при поднятии курсора мыши, а событие «фокусировка» запускается в тот момент, когда вы нажимаете кнопку мыши. Обычно кнопка выполняет действие, только если вы нажмете кнопку мыши, а затем отпустите ее. Правильный и доступный способ сделать это для модальных окон - добавить кнопку закрытия с вкладками.

Kevin 06.08.2017 16:11

@Kevin, «Модальное окно закроется, когда пользователь переключится на другую вкладку или окно или откроет контекстное меню», есть события, которые можно использовать для обработки этих случаев. "что действительно чертовски раздражает". Я нахожу модальные окна в целом раздражающими, почти никогда не стоящими хлопот. «событие 'click' срабатывает при поднятии мыши ... Обычно кнопка выполняет действие, только если вы нажмете кнопку мыши, а затем отпустите ее». Я согласен с тем, что это решение технически не ожидает щелчка и поэтому не может отменить щелчок с помощью действия перетаскивания. На практике я не видел, чтобы это представляло проблему.

zzzzBov 08.08.2017 01:37

@Kevin: «Правильный и доступный способ сделать это для модальных окон - добавить кнопку закрытия с вкладками». Я абсолютно согласен с тем, что в большинстве модальных окон должна быть кнопка закрытия. Есть определенные случаи, когда кнопки закрытия нецелесообразны. Я в основном использую эту технику для создания раскрывающихся списков ссылок, которые обычно не нуждаются в специальных кнопках закрытия, почти так же, как элемент <select> не нуждается в кнопке закрытия. В любом случае я пытался дать общий ответ на этот вопрос и не пишу его как исчерпывающее руководство по созданию модальных окон.

zzzzBov 08.08.2017 01:40

Нам действительно нужен e.preventDefault(); для события клавиатуры ESC?

KimchiMan 17.12.2017 12:25

Иногда сигара - это просто сигара; иногда вы ДЕЙСТВИТЕЛЬНО хотите сделать что-то при щелчке мышью, и этот ответ не касается этого случая (в чем весь смысл вопроса). Вызов кадра также должен отвечать на исходный вопрос.

GreySage 28.02.2018 22:35

Вы понимаете, что вам придется обрабатывать щелчки и нажатия клавиш на каждом элементе внутри контейнера, из которого вы хотите обнаруживать щелчки? Это безумие ...

Israel Cruz 10.04.2018 07:03

@IsraelCruz, достаточно одного слушателя-делегата, и он будет работать достаточно эффективно.

zzzzBov 10.04.2018 07:07

Это наиболее точный ответ на этот вопрос, не понимаю, почему это не выбранный ответ.

Daniel Barde 11.09.2018 20:34

Чтобы скрыть fileTreeClass, если щелкнуть за его пределами

 jQuery(document).mouseup(function (e) {
            var container = $(".fileTreeClass");
            if (!container.is(e.target) // if the target of the click isn't the container...
                && container.has(e.target).length === 0) // ... nor a descendant of the container
            {
                container.hide();
            }
        });

Простой плагин:

$.fn.clickOff = function(callback, selfDestroy) {
    var clicked = false;
    var parent = this;
    var destroy = selfDestroy || true;

    parent.click(function() {
        clicked = true;
    });

    $(document).click(function(event) {
        if (!clicked && parent.is(':visible')) {
            if (callback) callback.call(parent, event)
        }
        if (destroy) {
            //parent.clickOff = function() {};
            //parent.off("click");
            //$(document).off("click");
            parent.off("clickOff");
        }
        clicked = false;
    });
};

Использовать:

$("#myDiv").clickOff(function() {
   alert('clickOff');
});

Для некоторых это может быть лучшим решением.

$(".menu_link").click(function(){
    // show menu code
});

$(".menu_link").mouseleave(function(){
    //hide menu code, you may add a timer for 3 seconds before code to be run
});

Я знаю, что mouseleave означает не только щелчок снаружи, но и выход из области этого элемента.

Когда само меню находится внутри элемента menu_link, само меню не должно вызывать проблем при нажатии или перемещении.

mouseleave и какой-то хак может решить эту проблему для некоторых людей, вот тест jsfiddle.net/1r73jm8m

Puerto AGP 01.12.2017 02:40

Я считаю, что лучше всего это сделать примерно так.

$(document).on("click", function(event) {
  clickedtarget = $(event.target).closest('#menuscontainer');
  $("#menuscontainer").not(clickedtarget).hide();
});

Этот тип решения можно легко заставить работать с несколькими меню, а также с меню, которые динамически добавляются через javascript. По сути, он просто позволяет вам щелкнуть в любом месте документа, проверить, какой элемент вы щелкнули, и выбрать его ближайший "#menuscontainer". Затем он скрывает все контейнеры меню, но исключает тот, в котором вы щелкнули.

Не уверен, как именно построены ваши меню, но не стесняйтесь копировать мой код в JSFiddle. Это очень простое, но полностью функциональное меню / модальная система. Все, что вам нужно сделать, это создать html-меню, и код сделает всю работу за вас.

https://jsfiddle.net/zs6anrn7/

Я знаю, что на этот вопрос есть миллион ответов, но я всегда был поклонником использования HTML и CSS для выполнения большей части работы. В этом случае z-index и positioning. Самый простой способ, который я нашел для этого, заключается в следующем:

$("#show-trigger").click(function(){
  $("#element").animate({width: 'toggle'});
  $("#outside-element").show();
});
$("#outside-element").click(function(){
  $("#element").hide();
  $("#outside-element").hide();
});
#outside-element {
  position:fixed;
  width:100%;
  height:100%;
  z-index:1;
  display:none;
}
#element {
  display:none;
  padding:20px;
  background-color:#ccc;
  width:300px;
  z-index:2;
  position:relative;
}
#show-trigger {
  padding:20px;
  background-color:#ccc;
  margin:20px auto;
  z-index:2;
}
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id = "outside-element"></div>
<div id = "element">
  <div class = "menu-item"><a href = "#1">Menu Item 1</a></div>
  <div class = "menu-item"><a href = "#2">Menu Item 1</a></div>
  <div class = "menu-item"><a href = "#3">Menu Item 1</a></div>
  <div class = "menu-item"><a href = "#4">Menu Item 1</a></div>
</div>
<div id = "show-trigger">Show Menu</div>

Это создает безопасную среду, так как ничего не будет запускаться, если меню действительно не открыто, а z-index защищает любой контент внутри элемента от создания каких-либо пропусков зажигания при нажатии.

Кроме того, вам не требуется, чтобы jQuery покрыл все ваши базы вызовами распространения и очищал все внутренние элементы от пропусков зажигания.

$(document).on("click",function (event)   
 {   
     console.info(event);
   if ($(event.target).closest('.element').length == 0)
     {
    //your code here
      if ($(".element").hasClass("active"))
      {
        $(".element").removeClass("active");
      }
     }
 });

Попробуйте эту кодировку, чтобы получить решение.

Событие имеет свойство event.path элемента, который является "статический упорядоченный список всех его предков в древовидном порядке". Чтобы проверить, возникло ли событие из определенного элемента DOM или одного из его дочерних элементов, просто проверьте путь к этому конкретному элементу DOM. Его также можно использовать для проверки нескольких элементов, логически OR выполняя проверку элемента в функции some.

$("body").click(function() {
  target = document.getElementById("main");
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  })
  if (flag) {
    console.info("Inside")
  } else {
    console.info("Outside")
  }
});
#main {
  display: inline-block;
  background:yellow;
}
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id = "main">
  <ul>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
    <li>Test-Main</li>
  </ul>
</div>
<div id = "main2">
  Outside Main
</div>

Так что для вашего случая это должно быть

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});

event.path это не вещь.

Andrew 25.05.2020 22:09

Если кому-то интересно, вот решение javascript (es6):

window.addEventListener('mouseup', e => {
        if (e.target != yourDiv && e.target.parentNode != yourDiv) {
            yourDiv.classList.remove('show-menu');
            //or yourDiv.style.display = 'none';
        }
    })

и es5, на всякий случай:

window.addEventListener('mouseup', function (e) {
if (e.target != yourDiv && e.target.parentNode != yourDiv) {
    yourDiv.classList.remove('show-menu'); 
    //or yourDiv.style.display = 'none';
}

});

Вот что я делаю, чтобы решить проблему.

$(window).click(function (event) {
    //To improve performance add a checklike 
    //if (myElement.isClosed) return;
    var isClickedElementChildOfMyBox = isChildOfElement(event,'#id-of-my-element');

    if (isClickedElementChildOfMyBox)
        return;

    //your code to hide the element 
});

var isChildOfElement = function (event, selector) {
    if (event.originalEvent.path) {
        return event.originalEvent.path[0].closest(selector) !== null;
    }

    return event.originalEvent.originalTarget.closest(selector) !== null;
}

Это работает для меня

$("body").mouseup(function(e) {
    var subject = $(".main-menu");
    if (e.target.id != subject.attr('id') && !subject.has(e.target).length) {
        $('.sub-menu').hide();
    }
});

Вот простое решение на чистом javascript. Это актуален с 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;
})

«Актуальность с ES6» - довольно смелое заявление, когда единственное, что обновлено в ES6, - это () => {} вместо function() {}. То, что у вас есть, классифицируется как простой JavaScript с изюминкой ES6.

MortenMoulder 17.11.2017 15:41

@MortenMoulder: Я. Это просто для внимания, хотя на самом деле это ES6. Но посмотрите на решение. Думаю, это хорошо.

Duannx 18.11.2017 05:40

Это ванильный JS и работает для цели события, удаленной из DOM (например, когда выбрано значение из внутреннего всплывающего окна, всплывающее окно сразу закрывается). +1 от меня!

Alice 18.02.2020 17:29

если вы просто хотите отображать окно, когда вы нажимаете кнопку, и отключаете это окно, когда вы нажимаете снаружи. (или снова на кнопку), этот ниже работает хорошо

document.body.onclick = function() { undisp_menu(); };
var menu_on = 0;

function menu_trigger(event){

    if (menu_on == 0)
    {
        // otherwise u will call the undisp on body when 
        // click on the button
        event.stopPropagation(); 

        disp_menu();
    }

    else{
        undisp_menu();
    }

}


function disp_menu(){

    menu_on = 1;
    var e = document.getElementsByClassName("menu")[0];
    e.className = "menu on";

}

function undisp_menu(){

    menu_on = 0;
    var e = document.getElementsByClassName("menu")[0];
    e.className = "menu";

}

не забудь это для кнопки

<div class = "button" onclick = "menu_trigger(event)">

<div class = "menu">

и css:

.menu{
    display: none;
}

.on {
    display: inline-block;
}

Если вы используете такие инструменты, как «Всплывающее окно», вы можете использовать событие «onFocusOut».

window.onload=function(){
document.getElementById("inside-div").focus();
}
function loseFocus(){
alert("Clicked outside");
}
#container{
background-color:lightblue;
width:200px;
height:200px;
}

#inside-div{
background-color:lightgray;
width:100px;
height:100px;

}
<div id = "container">
<input type = "text" id = "inside-div" onfocusout = "loseFocus()">
</div>

$('#propertyType').on("click",function(e){
          self.propertyTypeDialog = !self.propertyTypeDialog;
          b = true;
          e.stopPropagation();
          console.info("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.info("outside clicked");
              }
          }
        // console.info($(e.target).closest("#configuration").length);
      });

Я использовал приведенный ниже сценарий и сделал это с помощью 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
    }
})

Ниже вы найдете HTML-код

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

Вы можете прочитать учебник здесь

Я удивлен, что никто на самом деле не подтвердил событие focusout:

var button = document.getElementById('button');
button.addEventListener('click', function(e){
  e.target.style.backgroundColor = 'green';
});
button.addEventListener('focusout', function(e){
  e.target.style.backgroundColor = '';
});
<!DOCTYPE html>
<html>
<head>
  <meta charset = "utf-8">
</head>
<body>
  <button id = "button">Click</button>
</body>
</html>

Думаю, тебе не хватает моего ответа. stackoverflow.com/a/47755925/6478359

Muhammet Can TONBUL 14.06.2019 16:35

Думаю, никто не предложил фокусировку, потому что вопрос в том, чтобы щелкнуть снаружи, а не навести курсор мыши. Как и в случае с другими, мне особенно нужен щелчок, а также наведение. Но кому-то это пригодится :)

James 06.07.2020 15:51

Если пользователь открывает меню и использует клавишу TAB для перехода к пунктам меню (не нажимая на них), использование вашего кода приводит к закрытию меню только из-за того, что фокусируется на пунктах меню. И я думаю, что это плохая идея.

Mohsen Haeri 10.01.2021 11:01

Я просто хочу сделать ответ @Pistos более очевидным, поскольку он скрыт в комментариях.

Это решение отлично сработало для меня. Обычный JS:

var elementToToggle = $('.some-element');
$(document).click( function(event) {
  if ( $(event.target).closest(elementToToggle).length === 0 ) {
    elementToToggle.hide();
  }
});

в CoffeeScript:

elementToToggle = $('.some-element')
$(document).click (event) ->
  if $(event.target).closest(elementToToggle).length == 0
    elementToToggle.hide()

Скажем, div, который вы хотите определить, если пользователь щелкнул снаружи или внутри, имеет идентификатор, например: «my-special-widget».

Слушайте события нажатия на тело:

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

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

В этом случае вы не нарушите нормальный поток кликов по какому-либо элементу на вашей странице, поскольку вы не используете метод stopPropagation.

Сначала вы должны отслеживать, находится ли мышь внутри или вне вашего element1, используя события mouseenter и mouseleave. Затем вы можете создать element2, который покрывает весь экран, чтобы обнаруживать любые щелчки и реагировать соответственно в зависимости от того, находитесь ли вы внутри или снаружи element1.

Я настоятельно рекомендую обрабатывать как инициализацию, так и очистку, и сделать element2 как можно более временным по очевидным причинам.

В приведенном ниже примере наложение - это элемент, расположенный где-то, который можно выбрать, щелкнув внутри, и снять выделение, щелкнув снаружи. Методы _init и _release вызываются как часть процесса автоматической инициализации / очистки. Класс унаследован от ClickOverlay, у которого есть inner и externalElement, не беспокойтесь об этом. Я использовал externalElement.parentNode.appendChild, чтобы избежать конфликтов.

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')
    }
}

Это слишком много кода для уже данного ответа ... Зачем вы это делаете? Потеря большого количества слушателей событий, только для того, что добавляло бы 2 onclicks, один на элемент, другой на тело, закрывающий элемент, который вы хотите закрыть

Alejandro Vales 25.07.2019 14:16

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

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

    box.classList.toggle('active');

    box.classList.contains('active')
      ? document.addEventListener('click', toggle)
      : document.removeEventListener('click', toggle);
  } else {
    console.info('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>

Для тех, кто задается вопросом, как это работает, обратный вызов toggle полагается на распространение событий браузера по умолчанию. Если щелчок находится внутри «окна», не скрывайте его. В противном случае переключите класс active. Видите, нам нужно ввести условие, если пользователь щелкает за пределами поля ИЛИ на кнопке. Если мы включили активную регистрацию того же обратного вызова в корне документа, ELSE удалит обратный вызов из корня.

Harry Moreno 20.02.2020 08:56

Самый простой способ: mouseleave(function())

Подробнее: https://www.w3schools.com/jquery/jquery_events.asp

Ссылка на решение приветствуется, но убедитесь, что ваш ответ полезен и без нее: добавить контекст вокруг ссылки, чтобы ваши коллеги-пользователи имели некоторое представление о том, что это такое и почему оно есть, а затем процитируйте наиболее релевантную часть страницы, на которую вы ссылаетесь. если целевая страница недоступна. Ответы, которые представляют собой не более чем ссылку, могут быть удалены.

Xnero 19.05.2020 17:00

@Daniil, это нет ответ только по ссылке. Если бы ссылку удалили, первое предложение все равно было бы ответом.

Paul Roub 19.05.2020 23:13

Я согласен, что это не только ответ по ссылке, но это не ответ на этот вопрос, поскольку этот ответ возникает, когда мышь покидает элемент, а не при щелчке, как было предложено;)

James 06.07.2020 13:23

2020 с использованием собственного метода JS API ближайший.

document.addEventListener('click', ({ target }) => {
  if (!target.closest('.el1, .el2, #el3')) {
    alert('click outside')
  }
})

closest не поддерживается IE

Steven Mark Ford 10.09.2020 04:19

Все еще ищете идеальное решение для обнаружения щелчков снаружи? Не смотрите дальше! Представляем Clickout-Событие, пакет, который обеспечивает универсальную поддержку clickout и других подобных событий, и он работает в сценариях все: простые атрибуты HTML onclickout, .addEventListener('clickout') ванильного JavaScript, .on('clickout') из jQuery, директивы v-on:clickout из Vue.js, вы называете это. Пока интерфейсная среда внутренне использует addEventListener для обработки событий, Clickout-Event работает для нее. Просто добавьте тег скрипта в любом месте своей страницы, и он будет работать как по волшебству.

Атрибут HTML

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

Ванильный JavaScript

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

jQuery

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

Vue.js

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

Угловой

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

Все эти ответы решают проблему, но я хотел бы внести свой вклад в решение moders es6, которое делает именно то, что необходимо. Я просто надеюсь порадовать кого-нибудь этой работоспособной демонстрацией.

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>

это отлично работает для меня. Я не эксперт.

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

Сейчас 2020 год, и вы можете использовать event.composedPath()

От: 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>

Ух ты, никогда не знал об этом методе для объекта события, да и в ванильном JS тоже. Спасибо, что поделился!

lislis 15.11.2020 09:09

если вы хотите сделать это встроенным, вы можете просто <div style = "width:100%;border:1px solid black" onclick = "console.info('Outside?',event.composedPath()[0]===th‌​is)"><span>Inside</s‌​pan> Outside</div>

YoniXw 13.12.2020 21:37

Я прочитал все по 2021 году, но, если не ошибаюсь, никто не предлагал что-то легкое вроде этого, чтобы отвязать и удалить событие. Используя два из вышеперечисленных ответов и еще один небольшой трюк, поместите все в один (также можно добавить параметр к функции для передачи селекторов, для большего количества всплывающих окон). Пусть кому-то будет полезно знать, что шутку можно было бы проделать и так:

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

ваше здоровье

Другие вопросы по теме