2,184


619

私はいくつかのHTMLメニューを持っていますが、ユーザーがこれらのメニューの頭をクリックすると完全に表示されます。 ユーザーがメニュー領域の外側をクリックしたときにこれらの要素を非表示にします。

このようなことはjQueryでも可能ですか?

$( "#menuscontainer")。clickOutsideThisElement(function(){//メニューを隠す});

76 Answer


1,678


_ NOTE Using stopEventPropagation() is something that should be avoided DOMの通常のイベントフローを壊すためです。 詳細についてはhttps://css-tricks.com/dangers-stopping-event-propagation/ [この記事]を参照してください。 代わりにhttps://stackoverflow.com/a/3028037/561309 [この方法]の使用を検討してください _

ウィンドウを閉じるドキュメントの本体にクリックイベントを添付します。 ドキュメント本体への伝播を停止するコンテナに個別のクリックイベントを添付します。

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

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


1,228


document`の* click *イベントをリッスンしてから、http://api.jquery.com/closest/[.closestを使用して、`#menucontainer`が先祖でもクリックされた要素のターゲットでもないことを確認できます。 () `]。

そうでなければ、クリックされた要素は `#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年3月11日

jQueryを使いたくない人のために。 これがプレーンvanillaJS(ECMAScript6)の上記のコードです。

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target) && isVisible(element)) { // or use: event.target.closest(selector) === null
          element.style.display = 'none'
          removeClickListener()
        }
    }

    const removeClickListener = () => {
        document.removeEventListener('click', outsideClickListener)
    }

    document.addEventListener('click', outsideClickListener)
}

const isVisible = elem => !!elem && !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ) // source (2018-03-11): https://github.com/jquery/jquery/blob/master/src/css/hiddenVisibleSelectors.js

*注意:*これは、jQuery部分の代わりに `!element.contains(event.target)`を使用するためのAlexのコメントに基づいています。

しかし現在 `element.closest()`はすべての主要ブラウザで利用可能です(W3CバージョンはjQueryのものとは少し異なります)。 ポリフィルはここで見つけることができます:https://developer.mozilla.org/en-US/docs/Web/API/Element/closest


227


_ 要素の外側のクリックを検出する方法は? _

この質問が非常に人気があり、非常に多くの回答を持っているのは、それが一見複雑なためです。 約8年と何十もの答えが出た後、私はアクセシビリティにあまり注意が払われていないことに本当に驚いています。

_ ユーザーがメニュー領域の外側をクリックしたときにこれらの要素を非表示にします。 _

これは気高い原因であり、実際の問題です。 質問のタイトル - これはほとんどの回答が対処しようとしているように思われる - に残念な赤いニシンが含まれています。

*ヒント:それは単語 "クリック"です!

実際にはクリックハンドラをバインドしたくはありません。

ダイアログを閉じるためにクリックハンドラをバインドしているならば、あなたはすでに失敗しました。 あなたが失敗した理由は、誰もが `click`イベントを引き起こすわけではないということです。 マウスを使用していないユーザーは、[。kbd]#Tab#を押すことにより、ダイアログ(およびポップアップメニューはほぼ間違いなくダイアログの一種)をエスケープできます。続いて「クリック」イベントをトリガーせずにダイアログ。

それでは質問を言い換えましょう。

_ ユーザーがダイアログを終了したら、どのようにダイアログを閉じますか? _

これが目標です。 残念ながら、今度は `userisfinishedwiththedialog`イベントをバインドする必要があり、そのバインドはそれほど単純ではありません。

それでは、ユーザーがダイアログの使用を終了したことをどのようにして検出できますか?

`focusout`イベント

最初は、フォーカスがダイアログから離れたかどうかを判断することから始めます。

ヒント: blur`イベントには注意してください。イベントがバブリングフェーズにバインドされていると blur`は伝播しません!*

jQueryのhttp://api.jquery.com/focusout/ [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;
}
Example

  Lorem ipsum dolor sit amet.

'' '' '

そのデモを1分以上プレイした場合は、すぐに問題が発生するはずです。

1つは、ダイアログ内のリンクがクリックできないことです。 それをクリックしようとするか、タブをクリックしようとすると、対話が行われる前にダイアログが閉じます。 これは、内側の要素にフォーカスを合わせると、再度focusinイベントが発生する前に、focusoutイベントが発生するためです。

この問題を解決するには、イベントループで状態の変更をキューに入れます。 これは setImmediate(…​)`を使うか、 `setImmediate`をサポートしていないブラウザでは setTimeout(…​、0) `を使って行うことができます。 一旦キューに入れられると、それは後続の `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;
}
Example

  Lorem ipsum dolor sit amet.

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

  Lorem ipsum dolor sit amet.

'' '' '

[.kbd]#Esc#キー

あなたがフォーカス状態を処理することによって行われたと思ったならば、ユーザーエクスペリエンスを単純化するためにあなたができることがもっとあります。

多くの場合、これは「必要な」機能ですが、モーダルまたはポップアップの種類がある場合、[。kbd]#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;
}
Example

  Lorem ipsum dolor sit amet.

'' '' '

ダイアログ内にフォーカス可能な要素があることがわかっていれば、ダイアログに直接フォーカスを合わせる必要はありません。 メニューを作成している場合は、代わりに最初のメニュー項目に焦点を合わせることができます。

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

      Example 1
      Example 2
      Example 3
      Example 4



    Menu 2

      Example 1
      Example 2
      Example 3
      Example 4



lorem ipsum dolor sit amet.

'' '' '

WAI-ARIAの役割とその他のアクセシビリティサポート

この回答はこの機能に対するアクセシブルなキーボードとマウスのサポートの基本をカバーしていることを願っていますが、それはかなり大きいのでhttps://www.w3.org/TR/wai-aria/[WAI- ARIAの役割と属性]、しかし私は、どの役割を使用すべきか、その他の適切な属性についての詳細は、実装者が仕様を参照することを強く推奨します。


133


ここで他の解決策は私のために働かなかった従って私は使用しなければならなかった:

if(!$(event.target).is( '#foo')){//メニューを隠す}


122


メニューを開いたときにclickイベントを本文に添付する以外は、Eranの例と同じように機能するアプリケーションがあります。 このようなKinda:

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

  event.stopPropagation();
});

jQueryの `one()`関数に関するさらなる情報]


36


$( "#menucontainer")。(function(){$(this).focus();});をクリックします。 $( "#menucontainer")。blur(function(){$(this).hide();});

私のためにうまく働きます。


35


今そのためのプラグインがあります:https://github.com/cowboy/jquery-outside-events[outside events]( blog post

_clickoutside_ハンドラ(WLOG)が要素にバインドされていると、次のようになります。

  • 要素は_clickoutside_ハンドラーですべての要素を保持する配列に追加されます

  • a( namespaced)_click_ハンドラはドキュメントにバインドされています(まだ存在しない場合)。

  • ドキュメント内の任意の_click_で、clickoutside_イベントは、_click-eventsターゲットと等しくないか、その親ではない、その配列内の要素に対してトリガーされます。

  • さらに、_clickoutside_イベントのevent.targetは、ユーザーがクリックした要素に設定されています(したがって、ユーザーがクリックした内容は、ユーザーが外部でクリックしたことだけではありません)。

そのため、イベントが伝播から停止されることはなく、追加の_click_handlerをoutside-handlerを使用して要素の「上」で使用することができます。


32


調査の結果、私は3つの解決策を見つけました(参考のためにページリンクを忘れました)。

最初の解決策

//このソリューションのいいところは、イベントの伝播を止めないことです。

var clickFlag = 0; $( 'body')。on( 'クリック'、function(){if(clickFlag == 0){console.log( 'ここで要素を隠す'); / *ここで要素を隠す* /} else {clickFlag = 0;} ;}}); $( 'body')。on( 'クリック'、 '#testDiv'、関数(イベント){clickFlag = 1; console.log( '要素を表示'); / *要素を表示* /});

第二の解決策

$( 'body')。on( 'クリック'、function(e){if($(e.target).closest( '#testDiv')。length == 0){/ *ここでドロップダウンを隠す* /}} ;

第三の解決策

var specifiedElement = document.getElementById( 'testDiv'); document.addEventListener( 'click'、function(event){var isClickInside = specifiedElement.contains(event.target); if(isClickInside){console.log( 'あなたはクリックしました')} else {console.log( 'クリックしました外側')         }     });


29


これは私にとっては完璧に機能しました。

$( 'html')。(function(e){if(e.target.id == 'あなたのDIV-ID'){//何かをすること} else {//何かをすること}});をクリックしてください。


24


ユーザーが外側をクリックしたときにメニューを閉じることが本当に必要だとは思いません。ユーザーがページ上の任意の場所をクリックしたときにメニューが閉じるようにする必要があります。 あなたがメニューをクリックするか、メニューの外に出ると、それは正しく閉じるべきですか?

上記の満足できる答えが見つからないと、私は先日http://programming34m0.blogspot.com/2011/05/simplifying-javascript-jump-menu.html [このブログ記事]を書くように促しました。 もっと教育的なことには、注意すべき点がいくつかあります。

  1. クリック時にbody要素にクリックイベントハンドラーをアタッチした場合 メニューを閉じてイベントをバインド解除する前に、必ず2回目のクリックを待ってください。 そうでなければ、メニューを開いたクリックイベントは、メニューを閉じなければならないリスナにバブルアップします。

  2. クリックイベントでevent.stopPropogation()を使用する場合、他の要素はありません ページのどこでもクリックして閉じる機能を使用できます。

  3. クリックイベントハンドラをbody要素に無期限にアタッチすることは 高性能ソリューションではありません

  4. イベントのターゲットと、その親とハンドラーの比較 作成者は、クリックしたときにメニューを閉じること、ページ上の任意の場所をクリックしたときに本当に閉じることを望むことを前提としています。

  5. body要素のイベントをリッスンすることで、コードがより魅力的になります 脆い。 これが壊れるのと同じくらい無邪気にスタイリングすることはそれを壊すでしょう: `body {margin-left:auto;右マージン:自動。幅:960ピクセル;}