Як виявити клацання поза елементом?


2486

У мене є деякі меню HTML, які я показую повністю, коли користувач натискає на голову цих меню. Я хотів би приховати ці елементи, коли користувач клацає за межі області меню.

Чи можливо щось подібне з jQuery?

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

47
Ось зразок цієї стратегії: jsfiddle.net/tedp/aL7Xe/1
Тед

18
Як згадував Том, вам потрібно прочитати css-tricks.com/dangers-stopping-event-propagation, перш ніж використовувати цей підхід. Цей інструмент jsfiddle досить класний.
Джон Кумбс

3
отримайте посилання на елемент, а потім на event.target, і нарешті! = або == обидва потім виконайте відповідний код відповідно.
Rohit Kumar

Рекомендую використовувати github.com/gsantiago/jquery-clickout :)
Rômulo M. Farias

2
Розчин ванілі JS з event.targetі без event.stopPropagation .
lowtechsun

Відповіді:


1812

ПРИМІТКА. Використання stopEventPropagation()- це те, чого слід уникати, оскільки воно порушує нормальний потік подій у DOM. Дивіться цю статтю для отримання додаткової інформації. Розглянемо замість цього метод

Приєднайте подію клацання до органу документа, який закриває вікно. До контейнера приєднайте окрему подію клацання, яка зупиняє розповсюдження до тіла документа.

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

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

708
Це порушує стандартну поведінку багатьох речей, включаючи кнопки та посилання, що містяться в #menucontainer. Я здивований, що ця відповідь така популярна.
Мистецтво

75
Це не порушує поведінку нічого в #menucontainer, оскільки воно знаходиться внизу ланцюга поширення для всього, що знаходиться всередині нього.
Еран Гальперін

94
дуже красиво, але вам не слід використовувати $('html').click()тіло. Тіло завжди має висоту свого вмісту. У ньому не так багато контенту або екран дуже високий, він працює лише на тій частині, яка заповнена тілом.
мео

103
Я також здивований, що за це рішення набрали стільки голосів. Це не вдасться до жодного елемента поза, який має stopPropagation jsfiddle.net/Flandre/vaNFw/3
Andre

140
Філіп Уолтон дуже добре пояснює, чому ця відповідь не є найкращим рішенням: css-tricks.com/dangers-stopping-event-propagation
Tom

1386

Ви можете прослухати подію натискання,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. Ось наведений вище код у звичайній ваніліJS (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()
        }
    }

28
Я перепробував багато інших відповідей, але тільки ця спрацювала. Дякую. Код, який я в кінцевому підсумку використав, був такий: $ (document) .click (функція (подія) {if ($ (event.target) .closest ('. Window'). Length == 0) {$ ('. Window' ) .fadeOut ('швидкий');}});
Пістос

39
Я фактично закінчився цим рішенням, оскільки він краще підтримує декілька меню на одній сторінці, де натискання другого меню, а перше відкрите, перше відкриє в рішенні stopPropagation.
umassthrower

13
Відмінна відповідь. Це шлях, коли у вас є кілька предметів, які ви хочете закрити.
Іван

5
Це має бути прийнятою відповіддю через недолік, який має інше рішення з event.stopPropagation ().
помідор

20
Без jQuery - !element.contains(event.target)за допомогою Node.contains ()
Alex Ross

303

Як виявити клацання поза елементом?

Причина того, що це питання настільки популярне і на нього так багато відповідей, це те, що воно оманливо складне. Після майже восьми років і десятків відповідей я щиро здивований, побачивши, як мало уваги приділяється доступності.

Я хотів би приховати ці елементи, коли користувач клацає за межі області меню.

Це благородна справа і є актуальним питанням. Назва питання - саме так, як видається, більшість відповідей намагається вирішити - містить нещасну червону оселедець.

Підказка: це слово "натиснути" !

Ви насправді не хочете зв’язувати обробники кліків.

Якщо ви прив'язуєте обробники кліків, щоб закрити діалогове вікно, ви вже не виконали помилку. Причина невдачі полягає в тому, що не всі викликають 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подію перед запуском afocusin знову подію.

Виправлення полягає в черзі зміни стану в циклі подій. Це може бути зроблено за допомогою 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 , проте настійно рекомендую, щоб реалізатори зверталися до специфікації для деталей про те, які ролі вони повинні використовувати та які інші відповідні атрибути.


29
Це найповніша відповідь, на увазі пояснення та доступність. Я думаю, що це має бути прийнятою відповіддю, оскільки більшість інших відповідей обробляють лише клацання, і лише фрагмент коду викидається без будь-яких пояснень.
Кирила

4
Дивовижно, добре пояснено. Я просто використав цей підхід на Реактивний компонент і прекрасно працював завдяки
Девід Лав'єрі

2
@zzzzBov спасибі за глибоку відповідь, я намагаюся досягти цього у ванільному JS, і я трохи втратив усі матеріали jquery. чи є щось подібне у ванілі js?
HendrikEng

2
@zzzzBov ні, я не шукаю, щоб ви написали jQuery-версію, звичайно, я спробую це зробити, і я думаю, що найкраще - це задати нове запитання тут, якщо я дійсно застряг. Ще раз дякую.
HendrikEng

7
Хоча це чудовий метод виявлення натискання користувальницьких спадових меню або інших входів, він ніколи не повинен бути кращим методом для модалів або спливаючих вікон з двох причин. Модальний режим закриється, коли користувач перейде на іншу вкладку чи вікно або відкриє контекстне меню, яке насправді дратує. Крім того, подія "клацання" спрацьовує на миші вгору, тоді як подія "фокусування" запускає мить, коли ви натискаєте мишу вниз. Зазвичай кнопка виконує дію, лише якщо натиснути кнопку миші вниз, а потім відпустити. Правильний, доступний спосіб зробити це для модалів - це додати кнопку закриття табуляції.
Кевін

144

Інші рішення тут не спрацювали для мене, тому мені довелося використовувати:

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

Я опублікував ще один практичний приклад того, як використовувати event.target, щоб уникнути запуску інших віджетів інтерфейсу Jquery UI, що надсилаються за допомогою клацання HTML, під час вбудовування їх у власне спливаюче вікно: Найкращий спосіб отримати оригінальну ціль
Joey T

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

У мене є додаток, який працює аналогічно прикладу Ерана, за винятком того, що я прикріплюю подія клацання до тіла, коли я відкриваю меню ... Начебто так:

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

  event.stopPropagation();
});

Детальніше про функцію jQueryone()


9
але тоді, якщо ви натиснете на саме меню, то зовні, воно не вийде :)
vsync

3
Це допомагає поставити event.stopProgagantion () перед прив'язкою слухача кліків до тіла.
Джаспер Кенніс

4
Проблема з цим полягає в тому, що "один" застосовується до методу jQuery додавання подій до масиву кілька разів. Тож якщо ви натискаєте на меню, щоб відкрити його не один раз, подія знову прив'язується до тіла і намагається приховати меню кілька разів. Щоб виправити цю проблему, слід застосувати помилку.
marksyzm

Після прив’язки за допомогою .one- всередині $('html')обробника - напишіть a $('html').off('click').
Коді

4
@Cody Я не думаю, що це допомагає. 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>

8
Третє рішення - це на сьогодні найвишуканіший спосіб перевірки. Він також не передбачає накладних витрат jQuery. Дуже хороший. Це дуже допомогло. Дякую.
дбарт

Я намагаюся отримати третє рішення з декількома елементами document.getElementsByClassName, якщо хтось має підказку, будь ласка, поділіться.
lowtechsun

@lowtechsun Вам доведеться пройти цикл, щоб перевірити кожен.
Donnie D'Amato

Третє рішення сподобалось, але натискання спрацьовує, перш ніж мій дів почав показувати, що приховує його знову, будь-яка ідея чому?
indiehjaerta

Третій дозволяє забезпечити console.log, звичайно, але він не дозволяє вам фактично закритись, встановивши дисплей на "жодних" - тому що це буде зроблено до появи меню. Чи є у вас рішення для цього?
RolandiXor

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

Працює для мене просто чудово.


3
Це я б використовував. Це може бути не ідеально, але як хобі-програміст, його досить просто, щоб зрозуміти чітко.
kevtrout

розмиття - це поза межами #menucontainerпитання про клік
боррель

4
@borrel blur - це не переміщення за межі контейнера. Розмиття - це протилежне фокусу, ви думаєте про мише. Це рішення особливо добре спрацювало для мене, коли я створював текст "натиснути для редагування", де я мінявся вперед і назад між простим текстом та полями введення кліками.
parker.sikand

Мені довелося додати, tabindex="-1"щоб #menuscontainerвін працював. Здається, що якщо помістити вхідний тег всередину контейнера і натиснути на нього, контейнер буде приховано.
тиріон

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

38

Тепер для цього є плагін: поза подіями ( повідомлення в блозі )

Це трапляється, коли обробник, який перебуває за допомогою кліку (WLOG), прив’язаний до елемента:

  • елемент додається до масиву, який містить усі елементи з обробниками clickoutside
  • a ( оброблюваний іменами ) обробник кліків пов'язаний з документом (якщо він ще не існує)
  • при будь-якому клацанні в документі подія clickoutside запускається для тих елементів у цьому масиві, які не рівні або батьківського клацання -події мети
  • крім того, 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
    }
});

Це рішення добре працювало для того, що я робив, де мені ще потрібна подія натискання, щоб піднятися, дякую
Майк

26

Я не думаю, що вам насправді потрібно закрити меню, коли користувач натискає назовні; Вам потрібно закрити меню, коли користувач взагалі натискає будь-яку сторінку сторінки. Якщо натиснути меню або вимкнути меню, воно має закритися правильно?

Не знайшовши задовільних відповідей вище, спонукало мене до написання цього допису в блозі днями. Для більш педантичного, є декілька готчей, які слід взяти до відома:

  1. Якщо ви приєднаєте обробник подій клацання до елемента тіла в момент натискання, не забудьте зачекати 2-го клацання, перш ніж закрити меню та від’єднати подію. Інакше подія клацання, яка відкрила меню, перетворюється на слухача, який повинен закрити меню.
  2. Якщо ви використовуєте event.stopPropogation () для події клацання, жоден інший елемент на вашій сторінці не може мати функцію "клацнути будь-де", щоб закрити.
  3. Прив’язання обробника подій клацання до елемента тіла нескінченно не є ефективним рішенням
  4. Порівнюючи ціль події та її батьків із творцем обробника, передбачається, що ви хочете закрити меню, коли ви клацаєте його, коли ви дійсно хочете - це закрити його, коли клацніть будь-де на сторінці.
  5. Прослуховування подій на елементі тіла зробить ваш код більш крихким. Стиль такий невинний, як це порушить його:body { margin-left:auto; margin-right: auto; width:960px;}

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

    })
});

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/


22

Рішення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

Рішення 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лише якщо ціль (клацання) не є його дитиною.


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

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>


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
Muhammet Can TONBUL

Це має бути прийнята відповідь, прямо до суті. Дякую!!!
Lucky Lam

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

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");

Для мене це було найкращим рішенням. Використовували його в зворотному дзвінку після анімації, тому мені потрібно було дещо відключити подію. +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();
});

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

"Оновлення 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

Підтвердьте найпопулярнішу відповідь, але додайте

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

Таким чином, клацання на смузі прокрутки не [приховує чи що завгодно] ваш цільовий елемент.


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...
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.