элемента - скрыть элемент при клике за его пределами javascript




Как определить клик вне элемента? (20)

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

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

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

Как обнаружить клик вне элемента?

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

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

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

Подсказка: это слово «щелчок» !

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

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

Давайте перефразируем вопрос.

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

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

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

событие focusout

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

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

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

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

Кроме того, для многих диалогов вам нужно будет позволить контейнеру получить фокус. Добавьте tabindex="-1" чтобы диалоговое окно 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 события 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>

Esc

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

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

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

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

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

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

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

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

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

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

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

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

.submenu__item {
  width: 150px;
}

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

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

Роли WAI-ARIA и другая поддержка доступности

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


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

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

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

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

В качестве варианта:

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

У него нет проблем с css-tricks.com/dangers-stopping-event-propagation и лучше поддерживает несколько меню на той же странице, где щелчок по второму меню при первом открытии оставит первое открытие в решении stopPropagation.


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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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


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

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

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

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


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

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


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

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

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

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

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

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


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

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

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


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

Если вы используете скрипты для 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;}

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

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

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

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

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

Событие имеет свойство, называемое 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;
}
<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
  }
});

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

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





jquery