HTMLメニューがいくつかあり、ユーザーがこれらのメニューの先頭をクリックすると完全に表示されます。ユーザーがメニュー領域の外側をクリックしたときに、これらの要素を非表示にしたいと思います。
このようなことはjQueryで可能ですか?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
HTMLメニューがいくつかあり、ユーザーがこれらのメニューの先頭をクリックすると完全に表示されます。ユーザーがメニュー領域の外側をクリックしたときに、これらの要素を非表示にしたいと思います。
このようなことはjQueryで可能ですか?
$("#menuscontainer").clickOutsideThisElement(function() {
// Hide the menus
});
回答:
注:を使用する
stopEventPropagation()
と、DOMの通常のイベントフローが中断されるため、使用を避けてください。詳細については、この記事を参照してください。代わりにこの方法の使用を検討してください
ウィンドウを閉じるドキュメント本文にクリックイベントをアタッチします。ドキュメント本文への伝播を停止する別のクリックイベントをコンテナにアタッチします。
$(window).click(function() {
//Hide the menus if visible
});
$('#menucontainer').click(function(event){
event.stopPropagation();
});
$('html').click()
体ではなく使用する必要があります。ボディは常にコンテンツの高さを持っています。コンテンツが多くないか、画面が非常に高いため、体で満たされた部分でのみ機能します。
でクリックイベントをリッスンし、を使用して祖先またはクリックされた要素のターゲットでないdocument
ことを確認#menucontainer
できます .closest()
。
そうでない場合、クリックされた要素はの外にあり、#menucontainer
安全に非表示にできます。
$(document).click(function(event) {
$target = $(event.target);
if(!$target.closest('#menucontainer').length &&
$('#menucontainer').is(":visible")) {
$('#menucontainer').hide();
}
});
メニューを閉じる予定があり、イベントのリスニングを停止する場合は、イベントリスナーの後にクリーンアップすることもできます。この関数は、新しく作成されたリスナーのみをクリーンアップし、の他のクリックリスナーを保持し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)
}
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部分の代わりに使用するAlexコメントに基づいています。
しかしelement.closest()
、現在はすべての主要なブラウザーでも使用できます(W3CバージョンはjQueryバージョンとは少し異なります)。ポリフィルはここにあります:Element.closest()
ユーザーが要素の内側をクリックアンドドラッグできるようにする場合は、要素を閉じずに、要素の外側でマウスを離します。
...
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()
}
}
!element.contains(event.target)
使用してNode.contains()
要素の外側のクリックを検出するにはどうすればよいですか?
この質問が非常に人気があり、非常に多くの回答がある理由は、一見複雑なためです。ほぼ8年と数十の回答を経て、アクセシビリティにほとんど注意が払われていないことに本当に驚いています。
ユーザーがメニュー領域の外側をクリックしたときに、これらの要素を非表示にしたいと思います。
これは高貴な原因であり、実際問題です。質問のタイトル(ほとんどの回答が対処しようとしているように見えるもの)には、不幸な赤いニシンが含まれています。
ヒント:「クリック」という言葉ですです。
クリックハンドラーをバインドしてダイアログを閉じる場合は、既に失敗しています。失敗した理由は、誰もがclick
イベントをトリガーするとは限らないためです。マウスを使用していないユーザーは、を押してダイアログをエスケープすることができ(ポップアップメニューは間違いなく一種のダイアログです)、Tabその後、をトリガーしない限り、ダイアログの背後にあるコンテンツを読み取ることができません。click
イベントを。
では、質問を言い換えましょう。
ユーザーがダイアログを使い終わったら、ダイアログをどのように閉じますか?
これが目標です。残念ながら、今はuserisfinishedwiththedialog
イベントをバインドする必要があり、そのバインドはそれほど簡単ではありません。
では、ユーザーがダイアログの使用を終了したことをどのように検出できますか?
focusout
出来事最初に、フォーカスがダイアログから離れたかどうかを確認します。
ヒント:blur
イベントに注意してください。イベントblur
がバブリングフェーズにバインドされている場合は伝播しません。
jQuery focusout
は問題なく動作します。jQueryを使用できない場合はblur
、キャプチャ段階で使用できます。
element.addEventListener('blur', ..., true);
// use capture: ^^^^
また、多くのダイアログでは、コンテナーがフォーカスを取得できるようにする必要があります。追加tabindex="-1"
すると、ダイアログが他の方法でタブフローを中断することなく動的にフォーカスを受け取ることができます。
$('a').on('click', function () {
$(this.hash).toggleClass('active').focus();
});
$('div').on('focusout', function () {
$(this).removeClass('active');
});
div {
display: none;
}
.active {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#example">Example</a>
<div id="example" tabindex="-1">
Lorem ipsum <a href="http://example.com">dolor</a> sit amet.
</div>
そのデモを1分以上プレイすると、すぐに問題が発生し始めます。
1つ目は、ダイアログ内のリンクがクリックできないことです。それをクリックするか、タブをクリックすると、対話が行われる前にダイアログが閉じます。これは、内側の要素にフォーカス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'));
}
});
2番目の問題は、リンクが再度押されたときにダイアログが閉じないことです。これは、ダイアログがフォーカスを失い、閉じる動作をトリガーした後、リンクのクリックがダイアログをトリガーして再度開くためです。
前の問題と同様に、フォーカス状態を管理する必要があります。状態変更はすでにキューに入れられているので、ダイアログトリガーでフォーカスイベントを処理するだけです。
これはおなじみのはずです$('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キーがそれを閉じるのが一般的です。
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の役割と属性についての説明は避けますが、詳細については、仕様を参照することを強くお勧めします彼らが使用すべき役割とその他の適切な属性について。
ここでの他の解決策は私にとってはうまくいかなかったので、私は使用する必要がありました:
if(!$(event.target).is('#foo'))
{
// hide menu
}
&& !$(event.target).parents("#foo").is("#foo")
にIF
ステートメント内に追加した以外は、これは私にとってはうまくいきました。
.is('#foo, #foo *')
ですが、この問題を解決するためにクリックハンドラーをバインドすることはお勧めしません。
!$(event.target).closest("#foo").length
@honyovkを追加する必要がなくなり、不要になります。
Eranの例と同様に機能するアプリケーションがありますが、メニューを開くときにクリックイベントを本文に添付します...こんな感じです。
$('#menucontainer').click(function(event) {
$('html').one('click',function() {
// Hide the menus
});
event.stopPropagation();
});
.one
- $('html')
ハンドラー内で-を記述し$('html').off('click')
ます。
one
ハンドラが自動的に呼び出されますoff
(それがjQueryのドキュメントに示されていますように)。
調査後、3つの実用的な解決策を見つけました(参照用のページリンクを忘れてしまいました)
<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>
document.getElementsByClassName
ます。
$("#menuscontainer").click(function() {
$(this).focus();
});
$("#menuscontainer").blur(function(){
$(this).hide();
});
私にとってはうまくいきます。
#menucontainer
クリックに関する質問以外の動きです
tabindex="-1"
せるには、を追加する#menuscontainer
必要がありました。コンテナー内にinputタグを配置してクリックすると、コンテナーが非表示になるようです。
今、そのためのプラグインがあります:外部イベント(ブログ投稿)
clickoutsideハンドラー(WLOG)が要素にバインドされると、次のようになります。
そのため、イベントの伝播が停止されることはなく、追加のクリックハンドラーを要素の「上」で外部ハンドラーとともに使用できます。
$( '#element' ).on( 'clickoutside', function( e ) { .. } );
ユーザーが外側をクリックしたときにメニューを閉じることは本当に必要だとは思いません。必要なのは、ユーザーがページのどこかをクリックしたときにメニューを閉じることです。メニューをクリックしたり、メニューから外したりすると、正しく閉じますか?
上記で満足のいく答えが見つからなかったため、先日このブログ投稿を書くようになりました。より知識を深めるために、注意すべきいくつかの落とし穴があります。
body { margin-left:auto; margin-right: auto; width:960px;}
別のポスターが言ったように、特にあなたが表示している要素(この場合はメニュー)がインタラクティブな要素を持っている場合、多くの落とし穴があります。次の方法はかなり堅牢であることがわかりました。
$('#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);
}
})
});
この状況の簡単な解決策は次のとおりです。
$(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
Clickイベントの外部がトリガーされた場合に非表示にします。
詳細については、次のブログを参照してください。 ください。http //www.codecanal.com/detect-click-outside-div-using-javascript/
副作用のあるevent.stopPropagation()を使用する代わりに、単純なフラグ変数を定義して1つの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";
}
});
単純な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
}
});
ウィンドウクリックイベントターゲットをチェックし(他の場所でキャプチャされない限り、ウィンドウに伝播する必要があります)、メニュー要素ではないことを確認します。そうでない場合は、メニューの外にいます。
または、クリックの位置を確認し、それがメニュー領域内に含まれているかどうかを確認します。
私はこのようなもので成功しました:
var $menuscontainer = ...;
$('#trigger').click(function() {
$menuscontainer.show();
$('body').click(function(event) {
var $target = $(event.target);
if ($target.parents('#menuscontainer').length == 0) {
$menuscontainer.hide();
}
});
});
ロジックは次のとおり#menuscontainer
です。表示されたときに、クリックハンドラーを本文にバインドし、#menuscontainer
(クリックの)ターゲットがその子ではない場合にのみ非表示にします。
バリエーションとして:
var $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();
}
});
イベントの伝播の停止には問題がなく、同じページで複数のメニューをより適切にサポートします。最初のメニューが開いているときに2番目のメニューをクリックすると、最初のメニューがstopPropagationソリューションで開いたままになります。
イベントには要素のevent.pathと呼ばれるプロパティがあり、これは「すべての祖先がツリー順に静的に順序付けられたリスト」です。イベントが特定のDOM要素またはその子の1つから発生したかどうかを確認するには、その特定の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
}
});
event.path
事ではありません。
このメソッドはいくつかの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);
これは、将来の視聴者向けのバニラJavaScriptソリューションです。
ドキュメント内のいずれかの要素をクリックしたときに、クリックされた要素のIDが切り替えられた場合、または非表示要素が非表示にならず、非表示要素にクリックされた要素が含まれていない場合は、要素を切り替えます。
(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);
})();
同じページに複数のトグルを配置する場合は、次のようなものを使用できます。
hidden
を折りたたみ可能なアイテムに追加します。(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>
誰も実際に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>
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).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");
#menuscontainer
場合は、まだその親を通過しています。最初にそれを確認し、それがその要素でない場合は、DOMツリーを上に移動します。
if(!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){
。これは小さな最適化ですが、プログラムの存続期間中に数回だけ発生しますclick.menu-outside
。イベントが登録されている場合は、クリックごとに発生します。より長く(+32文字)、メソッドチェーンを使用しません
使用する:
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();
});
ここで知りたい人が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';
}
});
これは、純粋な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;
})
() => {}
代わりに実行されfunction() {}
ます。あなたが持っているものは、ES6をひねったプレーンJavaScriptとして分類されます。
ドキュメントのクリックイベントリスナーをフックします。イベントリスナー内では、イベントオブジェクト、特に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");
}
});
以下のスクリプトを使用して、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>
使いやすく、より表現力豊かなコードを作成するために、このためのjQueryプラグインを作成しました。
$('div.my-element').clickOut(function(target) {
//do something here...
});
注: targetは、ユーザーが実際にクリックした要素です。しかし、コールバックは、まだ元の要素のコンテキストで実行されるので、あなたが利用することができ、これを使用すると、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...
});