Питання Як визначити клік за межами елемента?


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

Щось подібне до цього можливо за допомогою jQuery?

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

2027


походження


Ось приклад цієї стратегії: jsfiddle.net/tedp/aL7Xe/1 - Ted
Як згадував Том, ви хочете прочитати css-tricks.com/dangers-stopping-event-propagation перед використанням такого підходу. Цей інструмент jsfiddle досить приємний, хоча. - Jon Coombs
отримати посилання на елемент, а потім event.target, і нарешті! = або == обидва вони потім виконують код відповідно .. - Rohit Kumar
Я рекомендую використовувати github.com/gsantiago/jquery-clickout :) - Rômulo M. Farias
Спробуйте використати event.path. http://stackoverflow.com/questions/152975/how-do-i-detect-a-click-outside-an-element/43405204#43405204 - Dan Philip


Відповіді:


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

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

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

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

1620



Це порушує стандартне поведінку багатьох речей, включаючи кнопки та посилання, що містяться в #menucontainer. Я здивований, що ця відповідь настільки популярна. - Art
Це не порушує поведінки щось всередині #menucontainer, оскільки воно знаходиться в нижній частині ланцюга поширення для чогось всередині нього. - Eran Galperin
його дуже красиво, але ви повинні використовувати $('html').click() не тіло Тіло завжди має висоту його змісту. Це не дуже багато контенту, або екран дуже високий, він працює лише з тієї частини, яка заповнена тілом. - meo
Я також здивований, що це рішення отримало стільки голосів. Це не вдасться для будь-якого елемента назовні, який має stopPropagation jsfiddle.net/Flandre/vaNFw/3 - Andre
Філіп Уолтон дуже добре пояснює, чому ця відповідь не є найкращим рішенням: css-tricks.com/dangers-stopping-event-propagation - Tom


Ви можете слухати а клацніть подія на document а потім переконайтеся #menucontainer не є предком або ціллю натиснутого елемента, використовуючи .closest().

Якщо це не так, клацнутий елемент знаходиться поза межами #menucontainer і ви можете безпечно сховати його.

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

Редагувати - 2017-06-23

Ви також можете очистити після слухача події, якщо ви плануєте відхилити меню і хочете припинити прослуховування подій. Ця функція очистить тільки щойно створений слухач, зберігаючи на інших слухачів клацання document. З синтаксисом ES2015:

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    if (!$(event.target).closest(selector).length) {
      if ($(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)) { // or use: event.target.closest(selector) === null
            if (isVisible(element)) {
                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 

ПРИМІТКА: Це засновано на коментарі Alex, щоб просто використовувати !element.contains(event.target) замість частини jQuery.

Але element.closest() тепер також доступний у всіх основних браузерах (версія W3C дещо відрізняється від jQuery). Поліфайли можна знайти тут: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest


1127



Я спробував багато інших відповідей, але тільки цей працював. Дякую. Код я в кінцевому підсумку використовував був такий: $ (document) .click (function (event) {if ($ (event.target) .closest ('. Window') length == 0) {$ ('. Window' ). fadeOut ('fast');}}); - Pistos
Я насправді закінчив роботу з цим рішенням, оскільки краще підтримує кілька меню на тій же сторінці, де натискання на друге меню, коли перший відкритий, залишить перший відкритий у рішенні stopPropagation. - umassthrower
Це дуже хороше рішення для кількох елементів на сторінці. - jsgroove
Відмінна відповідь. Це шлях, коли ви маєте кілька елементів, які ви хочете закрити. - John
Без jQuery - !element.contains(event.target) використовуючи Node.contains () - Alex Ross


Як визначити клік за межами елемента?

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

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

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

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

Ви насправді не бажаєте зв'язувати обробників кліків.

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


Якщо ви граєте з цією демонстрацією протягом більше однієї хвилини, ви повинні швидко почати бачити проблеми.

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

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

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

Це має виглядати знайомо
$('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>


вихід ключ

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

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

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


188



Це найбільш повна відповідь, враховуючи пояснення та доступність. Я думаю, що це повинна бути прийнятною відповіддю, оскільки більшість інших відповідей стосуються лише клацання і лише фрагмент коду випав без будь-яких пояснень. - Cyrille
Дивовижний, добре пояснений. Я просто використав цей підхід на Компоненті React і чудово працював спасибі - David Lavieri
@zzzzBov Дякую за глибоку відповідь, я намагаюся досягти цього в vanilla JS, і я трохи втратив з усіма матеріалами jquery. чи є щось подібне у ванілі js? - HendrikEng
@zzzzBov ні, я не хочу, щоб ви написали версію, вільну від jQuery, звичайно, я спробую це зробити, і я думаю, що найкраще це поставити тут нове запитання, якщо я дійсно застряг. Ще раз спасибі знову. - HendrikEng
Хоча це відмінний спосіб виявлення натискання користувацьких спадних меню або інших введень, він ніколи не повинен бути кращим методом для модаль або спливаючих вікон з двох причин. Модаль закривається, коли користувач переходить на іншу вкладку або вікно або відкриває контекстне меню, що дійсно є дратує. Крім того, подія "клік" спрацьовує за допомогою миші, тоді як подія "focusout" викликає мить, коли ви натискаєте мишу. Зазвичай, кнопка виконує лише дію, якщо натискати кнопку миші вниз, а потім відпустити. Правильний, доступний спосіб зробити це для модалів - це додавання кнопки закриття на вкладці. - Kevin


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

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

127



Я розмістив ще один практичний приклад використання event.target, щоб уникнути запуску іншого віджета Jquery UI за межами клацання html обробників, коли вставляти їх у ваш власний спливний ящик: Найкращий спосіб отримати оригінальний ціль - Joey T
Це працювало для мене, хіба що я додав && !$(event.target).parents("#foo").is("#foo") всередині IF твердження, щоб будь-які дочірні елементи не закривали меню при натисканні. - honyovk
@Frug Ваша відповідь може бути цінною, тому що ви отримали 5, але я заплутався за вашу відповідь. і хто є MBJ. Я хотів створити краще рішення на основі вашої відповіді. - Satya Prakash
MBJ, мабуть, було старою назвою людини, яка опублікувала коментар над моїм. Користувачі змінюють імена, це може зробити це важким, але помітити коментар над моїм додано .parents("#foo") - Frug
Коротке вдосконалення для обробки глибокого гніздування - це використання .is('#foo, #foo *'), але Я не рекомендую обов'язкові механізми обробки кліків для вирішення цієї проблеми. - zzzzBov


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

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

  event.stopPropagation();
});

Більше інформації про jQuery's one() функція


118



але тоді, коли ви натискаєте на сам меню, то на вулиці це не спрацює :) - vsync
Це допомагає поставити event.stopProgagantion () перед тим, як прив'язувати прослуховувач кліків до тіла. - Jasper Kennis
Проблема з цим полягає в тому, що "один" застосовується до методу jQuery для додавання подій у масив декілька разів. Тож якщо ви натискаєте на меню, щоб відкрити його кілька разів, подія знову пов'язана з тілом і спробує сховати меню кілька разів. Необхідно застосувати захист від помилок, щоб виправити цю проблему. - marksyzm
Після обов'язкового використання .one - всередині $('html') обробник - напишіть a $('html').off('click'). - Cody
@ Коді Я не думаю, що допомагає. The one обробник буде автоматично викликати off (як це показано в документах jQuery). - Mariano Desanze


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

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


32



Це те, що я користуюсь. Це може бути не ідеальним, але як програміст хобі, його простий, щоб зрозуміло зрозуміти. - kevtrout
розмиття - це рух за межами #menucontainer питання було про клік - borrel
@порожній розмиття є ні переміщення поза контейнером. Розмивання - це протилежність фокусу, ви думаєте про переміщення курсору. Цей варіант працював особливо добре для мене, коли я створював текст "клацніть для редагування", де я міняв назад і вперед між звичайним текстом та полями введення на кліки. - parker.sikand
Мені довелося додати tabindex="-1" до #menuscontainer щоб він працював. Здається, що якщо ви помістите тег введення в контейнер і натиснете його, контейнер буде приховано. - tyrion
подія mouseleave більше підходить для меню та контейнерів (ref: w3schools.com/jquery/...) - Evgenia Manolova


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

Наступне відбувається, коли a clickoutside обробник (WLOG) пов'язаний з елементом:

  • елемент додається до масиву, який містить всі елементи з clickoutside оброблювачі
  • a (назви) клацніть Обробник пов'язаний з документом (якщо його ще не було)
  • на будь-якому клацніть в документі clickoutside подія спрацьовує для тих елементів цього масиву, які не дорівнюють або батьківській клацніть-приймає мети
  • Крім того, event.target для clickoutside подія встановлюється на елемент, на який користувач натиснув (так що ви навіть знаєте, що користувач натиснув, а не тільки, що він натиснув назовні)

Отже, жодні події не перестають поширюватися і додатково клацніть обробники можуть бути використані "вище" елемент з зовнішнім обробником.


31



Гарний плагін. Посилання на "зовнішні події" є мертвим, тоді як пошта публікації в блозі є живим і надає дуже корисний плагін для подій, пов'язаних із "викликом". Це також MIT ліцензовано. - TechNyquist
Великий плагін. Працював чудово. Використання виглядає так: $( '#element' ).on( 'clickoutside', function( e ) { .. } ); - Gavin