Как я могу обнаружить щелчок за пределами элемента?

2489

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

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

$("#menuscontainer").clickOutsideThisElement(function() {
    // Hide the menus
});
Sergio del Amo
источник
47
Вот пример этой стратегии: jsfiddle.net/tedp/aL7Xe/1
Тед
18
Как упоминал Том, вы должны прочитать css-tricks.com/dangers-stopping-event-propagation перед использованием этого подхода. Этот инструмент jsfiddle довольно крутой.
Джон Кумбс
3
получить ссылку на элемент, а затем event.target и, наконец,! = или == оба из них затем выполнить соответствующий код ..
Рохит Кумар
Я рекомендую использовать github.com/gsantiago/jquery-clickout :)
Rômulo M. Farias
2
Ванильное решение JS с event.targetи без event.stopPropagation .
lowtechsun

Ответы:

1813

ПРИМЕЧАНИЕ. stopEventPropagation()Следует избегать использования, поскольку оно нарушает нормальный поток событий в DOM. Смотрите эту статью для получения дополнительной информации. Попробуйте использовать этот метод вместо

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

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

$('#menucontainer').click(function(event){
    event.stopPropagation();
});
Eran Galperin
источник
708
Это нарушает стандартное поведение многих вещей, включая кнопки и ссылки, содержащиеся в #menucontainer. Я удивлен, что этот ответ так популярен.
Art
75
Это не нарушает поведение чего-либо внутри #menucontainer, так как оно находится в нижней части цепочки распространения для чего-либо внутри него.
Эран Гальперин
94
это очень красиво, но вы должны использовать $('html').click()не тело. Тело всегда имеет высоту своего содержания. Если контента не так много или экран очень высокий, он работает только на части, заполненной телом.
meo
103
Я также удивлен, что это решение получило так много голосов. Это не удастся для любого элемента снаружи, который имеет stopPropagation jsfiddle.net/Flandre/vaNFw/3
Андре
140
Филип Уолтон очень хорошо объясняет, почему этот ответ не является лучшим решением: css-tricks.com/dangers-stopping-event-propagation
Том
1388

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

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

$(document).click(function(event) { 
  $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) => {
    $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). Полифилы можно найти здесь: Element.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()
        }
    }
Art
источник
28
Я пробовал многие другие ответы, но сработал только этот. Спасибо. Код, который я использовал, был следующим: $ (document) .click (function (event) {if ($ (event.target) .closest ('. Window'). Length == 0) {$ ('. Window' ) .fadeOut ('fast');}});
Pistos
39
Я на самом деле закончил с этим решением, потому что оно лучше поддерживает несколько меню на одной странице, где нажатие на второе меню, когда открыто первое, оставляет первое открытие в решении stopPropagation.
Umassthrower
13
Отличный ответ. Это путь, когда у вас есть несколько элементов, которые вы хотите закрыть.
Джон
5
Это должен быть принятый ответ из-за недостатка, который есть в другом решении с помощью event.stopPropagation ().
помидор
20
Без jQuery - !element.contains(event.target)с использованием Node.contains ()
Алекс Росс
305

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

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

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

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

Подсказка: это слово «клик» !

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

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

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

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

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

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

focusout событие

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

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

JQuery's 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({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});


Esc ключ

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

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

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


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

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


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

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

zzzzBov
источник
30
Это наиболее полный ответ, с объяснениями и доступностью. Я думаю, что это должен быть принятый ответ, так как большинство других ответов обрабатывают только клики и просто отбрасывают фрагмент кода без каких-либо объяснений.
Сирил
4
Удивительно, хорошо объяснил. Я только что использовал этот подход на React Component и отлично работал, спасибо
Дэвид Лавьери
2
@zzzzBov спасибо за глубокий ответ, я пытаюсь добиться этого в vanilla JS, и я немного растерялся со всеми вещами jquery. есть ли что-нибудь похожее в ванильном JS?
HendrikEng
2
@zzzzBov Нет, я, конечно, не ищу, чтобы вы написали версию без jQuery, я постараюсь сделать это, и я думаю, что лучше всего задать новый вопрос здесь, если я действительно застряну. Еще раз большое спасибо.
HendrikEng
7
Несмотря на то, что это отличный метод для определения отсутствия пользовательских выпадающих списков или других входных данных, он никогда не должен быть предпочтительным для модальных или всплывающих окон по двум причинам. Модальное окно закроется, когда пользователь переключится на другую вкладку или окно или откроет контекстное меню, которое действительно раздражает. Кроме того, событие «click» срабатывает при наведении мыши вверх, а событие «focusout» срабатывает в тот момент, когда вы нажимаете кнопку мыши. Обычно кнопка выполняет действие, только если вы нажмете кнопку мыши, а затем отпустите. Правильный и доступный способ сделать это для модалов - добавить вкладку закрывающую кнопку.
Кевин
144

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

if(!$(event.target).is('#foo'))
{
    // hide menu
}
Деннис
источник
Я опубликовал еще один практический пример того, как использовать event.target, чтобы не запускать другие обработчики HTML внешнего клика виджета Jquery при вставке их в собственное всплывающее окно: Лучший способ получить оригинальную цель
Джои Т.
43
Это сработало для меня, за исключением того, что я добавил && !$(event.target).parents("#foo").is("#foo")внутри IFоператора, чтобы любые дочерние элементы не закрывали меню при нажатии.
honyovk
1
Не могу найти лучше, чем: // Мы за пределами $ (event.target) .parents ('# foo').
Length
3
Кратким улучшением для обработки глубокого вложения является использование .is('#foo, #foo *'), но я не рекомендую связывать обработчики щелчков для решения этой проблемы .
zzzzBov
1
!$(event.target).closest("#foo").lengthбыло бы лучше и избавляет от необходимости добавления @ honyovk.
tvanc
127

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

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

  event.stopPropagation();
});

Больше информации о функции jQueryone()

Joe Lencioni
источник
9
но потом, если вы нажмете на само меню, то снаружи оно не будет работать :)
vsync
3
Это помогает поместить event.stopProgagantion () перед привязкой слушателя клика к телу.
Джаспер Кеннис
4
Проблема заключается в том, что «один» применяется к методу jQuery для добавления событий в массив несколько раз. Поэтому, если вы щелкнете по меню, чтобы открыть его более одного раза, событие снова привязывается к телу и пытается скрыть меню несколько раз. Для устранения этой проблемы следует применять отказоустойчивый.
markyzm
После связывания с использованием .one- внутри $('html')обработчика - напишите a $('html').off('click').
Коди
4
@ Коди, я не думаю, что это помогает. oneОбработчик будет автоматически вызывать off(как это показано в документации JQuery).
Мариано Дезанце
44

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

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

<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.log('hide element here');
            /* Hide element here */
        }
        else {
            clickFlag=0;
        }
    });
    $('body').on('click','#testDiv', function (event) {
        clickFlag = 1;
        console.log('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.log('You clicked inside')
        }
        else {
          console.log('You clicked outside')
        }
    });
</script>
Rameez Rami
источник
8
Третье решение - безусловно, самый элегантный способ проверки. Это также не связано с накладными расходами jQuery. Очень хорошо. Это очень помогло. Спасибо.
Дбарт
Я пытаюсь получить третье решение с несколькими элементами document.getElementsByClassName, если кто-то знает, пожалуйста, поделитесь.
lowtechsun
@lowtechsun Вы должны были бы пройтись, чтобы проверить каждый.
Донни Д'Амато
Очень понравилось третье решение, однако щелчок срабатывает до того, как мой div начинает показывать, что скрывает его снова, есть идеи, почему?
indiehjaerta
Третий допускает console.log, конечно, но он не позволяет вам на самом деле закрыться, установив display в none - потому что он сделает это до того, как отобразится меню. У вас есть решение для этого?
RolandiXor
40
$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

У меня работает просто отлично.

user212621
источник
3
Это тот, который я бы использовал. Возможно, он не идеален, но как программист-хобби его достаточно просто понять.
kevtrout
Размытие - это движение вне #menucontainerвопроса, было о щелчке
borrel
4
@borrel размытости не двигаться за пределы контейнера. Размытие - это противоположность фокуса, вы думаете о мышлении. Это решение работало особенно хорошо для меня, когда я создавал текст «клик для редактирования», где я переключался между простым текстом и полями ввода по щелчкам.
parker.sikand
Я должен был добавить tabindex="-1"к , #menuscontainerчтобы заставить его работать. Кажется, что если вы поместите тег ввода в контейнер и нажмете на него, контейнер будет скрыт.
Тирион
событие mouseleave больше подходит для меню и контейнеров (ссылка: w3schools.com/jquery/… )
Евгения Манолова
38

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

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

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

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

Вольфрам
источник
Хороший плагин. Ссылка на "внешние события" неактивна, а ссылка на пост в блоге жива, и предоставляет очень полезный плагин для событий типа "clickoutside". Это также MIT по лицензии.
TechNyquist
Отличный плагин. Работал отлично. Использование примерно так:$( '#element' ).on( 'clickoutside', function( e ) { .. } );
Гэвин
33

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

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});
srinath
источник
Это решение хорошо сработало для того, что я делал, где мне все еще нужно было всплывающее событие, спасибо.
Майк
26

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

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

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

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

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

    })
});
benb
источник
25

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

$(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/

Jitendra Damor
источник
22

Solution1

Вместо использования 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";
    }
});

Solution2

С простым 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
Решение 1 работает лучше, потому что оно обрабатывает случаи, когда цель щелчка удаляется из DOM к тому времени, когда событие распространяется на документ.
Алиса
21

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

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

Крис Макдональд
источник
18

У меня был успех с чем-то вроде этого:

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только если цель (щелчка) не является его дочерней.

Chu Yeow
источник
17

Как вариант:

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.

Богдан Лизанец
источник
16

Событие имеет свойство с именем 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.log("Inside")
  } else {
    console.log("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
  }
});
Dan Philip
источник
1
Не работает в Edge.
Легенды
event.pathэто не вещь.
Андрей
14

Я нашел этот метод в некотором плагине календаря 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);
назар кулиев
источник
13

Вот ванильное решение 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);
})();

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

  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>

user4639281
источник
13

Я удивлен, что никто на самом деле не признал 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
Мухаммет Кан ТОНБУЛ
Это должен быть принятый ответ, прямо к делу. Спасибо!!!
Лаки Лэм
8

Если вы пишете сценарии для 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;}
Erik
источник
8

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

$(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");
MEMS
источник
Для меня это было лучшее решение. Использовал его в обратном вызове после анимации, поэтому мне нужно было событие отсоединения. +1
qwertzman
Здесь есть избыточная проверка. если элемент действительно, #menuscontainerвы все еще переживаете его родителей. Вы должны сначала проверить это, и если это не тот элемент, тогда поднимитесь по дереву DOM.
vsync
Правильно ! Вы можете изменить условие на if(!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){. Это крошечная оптимизация, но она происходит всего несколько раз за время жизни вашей программы: для каждого клика, если click.menu-outsideсобытие зарегистрировано. Это длиннее (+32 символа) и не использует цепочку методов
мемы
8

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

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();
});
webenformasyon
источник
7

Если кому-то интересно, вот решение 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';
}

});

Уолт
источник
7

Вот простое решение с помощью чистого 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;
})
Duannx
источник
«Современный ES6» - довольно смелое утверждение, когда () => {}вместо ES6 делается единственное, что актуально для ES6 function() {}. То, что у вас есть, классифицируется как простой JavaScript с изюминкой ES6.
MortenMoulder
@MortenMoulder: Я. Это просто для внимания, хотя на самом деле это ES6. Но просто посмотрите на решение. Я думаю, что это хорошо.
Duannx
Это ванильный JS и работает для цели события, удаленной из DOM (например, когда выбрано значение из внутреннего всплывающего окна, немедленно закрывающее всплывающее окно). +1 от меня!
Алиса
6

Подцепите слушателя события щелчка на документе. Внутри слушателя событий вы можете посмотреть на объект события , в частности, на 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.log("hide");
    }
});
Салман А
источник
6

Я использовал скрипт ниже и сделал с 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>

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

Ринто Джордж
источник
5
$(document).click(function() {
    $(".overlay-window").hide();
});
$(".overlay-window").click(function() {
    return false;
});

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

рябина
источник
5

Upvote для самого популярного ответа, но добавить

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

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

BBE
источник
5

Для более легкого использования и более выразительного кода я создал для этого плагин 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...
});
Мэтт Гудвин
источник