Bir öğenin dışındaki bir tıklamayı nasıl tespit edebilirim?


2486

Bir kullanıcı bu menülerin başına tıkladığında tamamen gösterdiğim bazı HTML menülerim var. Kullanıcı menülerin alanı dışına tıkladığında bu öğeleri gizlemek istiyorum.

JQuery ile böyle bir şey mümkün mü?

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

47
İşte bu stratejinin bir örneği: jsfiddle.net/tedp/aL7Xe/1
Ted

18
Tom'un belirttiği gibi, bu yaklaşımı kullanmadan önce css-tricks.com/dangers-stopping-event-propagation sayfasını okumak istersiniz . Bu jsfiddle aracı oldukça güzel.
Jon Coombs

3
eleman ve sonra event.target bir referans almak ve son olarak! = veya == her ikisi de daha sonra buna göre kod yürütmek ..
Rohit Kumar


2
Vanilla JS çözümü olan event.targetve olmayan çözümevent.stopPropagation .
lowtechsun

Yanıtlar:


1812

NOT: stopEventPropagation()DOM'daki normal olay akışını bozduğu için kullanmaktan kaçınılması gereken bir şeydir. Daha fazla bilgi için bu makaleye bakın . Bunun yerine bu yöntemi kullanmayı düşünün

Belge gövdesine pencereyi kapatan bir tıklama olayı ekleyin. Kapsayıcıya belge gövdesine yayılmasını durduran ayrı bir tıklama olayı ekleyin.

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

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

708
Bu, #menucontainer içinde bulunan düğmeler ve bağlantılar da dahil olmak üzere birçok şeyin standart davranışını bozar. Bu cevabın çok popüler olmasına şaşırdım.
Sanat

75
Bu, #menucontainer içindeki hiçbir şeyin davranışını kırmaz, çünkü içindeki herhangi bir şey için yayılma zincirinin altındadır.
Eran Galperin

94
çok güzel ama $('html').click()beden kullanmamalısın . Vücut her zaman içeriğinin yüksekliğine sahiptir. Çok fazla içerik yok veya ekran çok yüksek, sadece vücut tarafından doldurulan kısımda çalışıyor.
meo

103
Bu çözümün çok fazla oy alması beni de şaşırttı. Dışında durma
Andre

140
Philip Walton, bu cevabın neden en iyi çözüm olmadığını çok iyi açıklıyor: css-tricks.com/dangers-stopping-event-propagation
Tom

1386

Tarihinde bir tıklama olayını dinleyebilir documentve ardından düğmesini #menucontainerkullanarak tıklanan öğenin atası veya hedefi olmadığından emin olabilirsiniz .closest().

Değilse, tıklanan öğe dışındadır #menucontainerve güvenle gizleyebilirsiniz.

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

Düzenle - 2017-06-23

Ayrıca menüyü kapatmayı planlıyorsanız ve olayları dinlemeyi durdurmak istiyorsanız, olay dinleyicisinden sonra da temizleyebilirsiniz. Bu işlev yalnızca yeni oluşturulan dinleyiciyi temizler ve diğer tıklama dinleyicilerini korur document. ES2015 sözdizimi ile:

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

Düzenle - 2018-03-11

JQuery kullanmak istemeyenler için. İşte düz vanillaJS (ECMAScript6) yukarıdaki kod.

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 

NOT: Bu sadece !element.contains(event.target)jQuery parçası yerine kullanmak için Alex yorum dayanmaktadır .

Ancak element.closest()şimdi tüm büyük tarayıcılarda da mevcuttur (W3C sürümü jQuery'den biraz farklıdır). Çoklu dolgular burada bulunabilir: Element.closest ()

Düzenle - 2020-05-21

Kullanıcının öğenin içinde tıklayıp sürüklemesini istediğiniz durumda, öğeyi kapatmadan fareyi öğenin dışında bırakın:

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

Ve içinde 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
Diğer cevapların çoğunu denedim, ama sadece bu çalıştı. Teşekkürler. Kullantığım kod şuydu: $ (document) .click (function (event) {if ($ (event.target) .closest ('. Window'). Length == 0) {$ ('. Window' ) .fadeOut ('fast');}});
Pistos

39
Aslında bu çözüm ile devam ettim, çünkü aynı sayfada birden fazla menüyü daha iyi destekliyor, burada bir ilk açıkken ikinci bir menüye tıklamak stopPropagation çözümünde ilk açık bırakacak.
umassthrower

13
Mükemmel cevap. Kapatmak istediğiniz birden fazla öğeniz olduğunda bu yöntem kullanılabilir.
John

5
Diğer çözümün event.stopPropagation () ile olan kusuru nedeniyle bu kabul edilen cevap olmalıdır.
domates

20
JQuery olmadan - Node.contains ()!element.contains(event.target) kullanarak
Alex Ross

303

Bir öğenin dışındaki bir tıklamayı nasıl tespit edebilirim?

Bu sorunun çok popüler olmasının ve çok fazla cevabın olmasının nedeni, aldatıcı bir şekilde karmaşık olmasıdır. Neredeyse sekiz yıl ve onlarca cevaptan sonra erişilebilirliğe ne kadar az özen gösterildiğini görmek beni gerçekten şaşırttı.

Kullanıcı menülerin alanı dışına tıkladığında bu öğeleri gizlemek istiyorum.

Bu asil bir nedendir ve asıl konudur. Sorunun başlığı - çoğu yanıtın ele alınmaya çalıştığı şey - talihsiz bir kırmızı ringa içerir.

İpucu: "tıklama" kelimesi !

Aslında tıklama işleyicilerini bağlamak istemezsiniz.

İletişim kutusunu kapatmak için tıklama işleyicilerini bağlıyorsanız, zaten başarısız oldunuz. Başarısız olmanızın nedeni herkesin clickolayları tetiklememesidir . Fare kullanmayan kullanıcılar, düğmesine basarak iletişim kutusunuzdan (ve açılır menünüzün tartışmasız bir tür iletişim kutusundan) Tabçıkabileceğini ve daha sonra bir clickolayı tetiklemeden iletişim kutusunun arkasındaki içeriği okuyamayacaklardır .

Şimdi soruyu yeniden yazalım.

Kullanıcı bittiği zaman iletişim kutusu nasıl kapatılır?

Amaç bu. Ne yazık ki, şimdi userisfinishedwiththedialogolayı bağlamamız gerekiyor ve bu bağlanma o kadar basit değil.

Öyleyse bir kullanıcının bir iletişim kutusunu kullanmayı bitirdiğini nasıl tespit edebiliriz?

focusout Etkinlik

İyi bir başlangıç, odaklamanın iletişim kutusundan ayrılıp ayrılmadığını belirlemektir.

İpucu: bluretkinliğe dikkat edin, etkinlik blurköpürme aşamasına bağlıysa yayılmaz!

jQuery focusoutiyi olur. JQuery kullanamıyorsanız blur, yakalama aşamasında kullanabilirsiniz:

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

Ayrıca, birçok iletişim kutusu için kapsayıcının odaklanmasına izin vermeniz gerekir. tabindex="-1"Sekme akışını aksatmadan iletişim kutusunun odağı dinamik olarak almasına izin vermek için ekleyin .

$('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>


Bu demoda bir dakikadan fazla oynarsanız sorunları hızlı bir şekilde görmeye başlamalısınız.

Birincisi, iletişim kutusundaki bağlantının tıklanabilir olmamasıdır. Üzerine veya sekmesine tıklamaya çalışmak, etkileşim gerçekleşmeden önce iletişim kutusunun kapanmasına neden olur. Bunun nedeni, iç öğeye odaklanmanın focusoutbir focusinolayı yeniden tetiklemeden önce bir olayı tetiklemesidir .

Düzeltme, olay döngüsünde durum değişikliğini sıralamaktır. Bu, kullanılarak setImmediate(...)veya setTimeout(..., 0)desteklenmeyen tarayıcılar için yapılabilir setImmediate. Kuyruğa alındıktan sonra, bir sonraki işlem tarafından iptal edilebilir 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'));
  }
});

İkinci sorun, bağlantıya tekrar basıldığında iletişim kutusunun kapanmamasıdır. Bunun nedeni iletişim kutusunun odağı kaybetmesi ve yakın davranışı tetiklemesidir, bundan sonra bağlantı tıklaması iletişim kutusunu yeniden açmak için tetikler.

Önceki konuya benzer şekilde, odak durumunun yönetilmesi gerekiyor. Durum değişikliğinin zaten kuyruğa alınmış olduğu göz önüne alındığında, bu sadece diyalog tetikleyicilerindeki odak olaylarını ele almakla ilgilidir:

Bu tanıdık gelmeli
$('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 anahtar

Odak durumlarını ele alarak bittiğini düşünüyorsanız, kullanıcı deneyimini basitleştirmek için yapabileceğiniz daha çok şey var.

Bu genellikle "olması güzel" bir özelliktir, ancak herhangi bir türden bir modal veya pop-up'ınız olduğunda Escanahtarın onu kapatacağı yaygındır .

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


İletişim kutusunda odaklanabilir öğeleriniz olduğunu biliyorsanız, iletişim kutusunu doğrudan odaklamanız gerekmez. Bir menü oluşturuyorsanız, bunun yerine ilk menü öğesine odaklanabilirsiniz.

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


WAI-ARIA Rolleri ve Diğer Erişilebilirlik Desteği

Bu cevap umarım bu özellik için erişilebilir klavye ve fare desteğinin temellerini kapsar, ancak zaten oldukça büyük olduğundan, WAI-ARIA rolleri ve nitelikleri hakkında herhangi bir tartışmadan kaçınacağım , ancak uygulayıcıların ayrıntılar için spesifikasyonlara başvurmasını şiddetle tavsiye ediyorum hangi rolleri kullanmaları gerektiğine ve diğer uygun niteliklere


29
Bu, açıklamalar ve erişilebilirlik göz önünde bulundurularak en eksiksiz yanıttır. Diğer cevapların çoğu sadece tıklama işlemek ve sadece kod snippet herhangi bir açıklama yapmadan bırakıldığından bu kabul edilen cevap olduğunu düşünüyorum.
Cyrille

4
Şaşırtıcı, iyi açıkladı. Bu yaklaşımı bir React Bileşeninde kullandım ve mükemmel bir şekilde çalıştım
David Lavieri

2
@zzzzBov derin cevap için teşekkürler, vanilya JS elde etmeye çalışıyorum ve tüm jquery şeyler ile biraz kayboldum. vanilya js'de benzer bir şey var mı?
HendrikEng

2
@zzzzBov hayır ben elbette bir jQuery ücretsiz sürümü yazmak için arıyorum değil, ben bunu yapmaya çalışacağım ve en iyisi ben gerçekten sıkışıp kalırsanız burada yeni bir soru sormaktır sanırım. Tekrar çok teşekkürler.
HendrikEng

7
Bu, özel açılanların veya diğer girdilerin tıklanmasını tespit etmek için harika bir yöntem olsa da, iki nedenden dolayı asla modüller veya pop-up'lar için tercih edilen yöntem olmamalıdır. Kullanıcı başka bir sekmeye veya pencereye geçtiğinde veya gerçekten sinir bozucu olan bir bağlam menüsü açtığında mod kapatılır. Ek olarak, 'click' olayı fareyi yukarı kaldırırken, 'focusout' olayı fareyi bastığınız anda tetikler. Normalde, bir düğme yalnızca fare düğmesine basıp serbest bırakmanız durumunda bir eylem gerçekleştirir. Modals için bunu yapmanın uygun, erişilebilir yolu sekmeli kapat düğmesi eklemektir.
Kevin

144

Buradaki diğer çözümler benim için işe yaramadı, bu yüzden kullanmak zorunda kaldım:

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

Kendi pop-over kutunuza yerleştirirken diğer Jquery UI widget'ı dış tıklama html işleyicilerini tetiklemekten kaçınmak için event.target'ı nasıl kullanacağım konusunda başka bir pratik örnek yayınladım: Orijinal Hedefi elde etmenin en iyi yolu
Joey T

43
Bu benim için çalıştı, ancak herhangi bir alt öğenin tıklandığında menüyü kapatmaması için ifadenin && !$(event.target).parents("#foo").is("#foo")içine ekledim IF.
honyovk

1
Şundan daha iyi bulunamıyor: // $ dışındayız (event.target) .parents ('# foo'). Uzunluk == 0
AlexG

3
Derin yuvalamayı işlemek için özlü iyileştirme kullanmaktır .is('#foo, #foo *'), ancak bu sorunu çözmek için tıklama işleyicilerini bağlama önermiyorum .
zzzzBov

1
!$(event.target).closest("#foo").lengthdaha iyi olurdu ve @ honyovk'un eklenmesi ihtiyacını ortadan kaldırır.
tvanc

127

Menüyü açtığımda gövdeye click olayını eklemem dışında Eran'ın örneğine benzer şekilde çalışan bir uygulamam var ...

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

  event.stopPropagation();
});

JQuery işlevi hakkında daha fazla bilgione()


9
ama sonra menünün kendisini tıklarsanız, o zaman dışarıda, işe yaramaz :)
vsync

3
Tıklama dinleyicisini gövdeye bağlamadan önce event.stopProgagantion () yönteminin kullanılmasına yardımcı olur.
Jasper Kennis

4
Bu sorun, "bir" bir diziye birden çok kez olay ekleme jQuery yöntemi için geçerli olmasıdır. Bu nedenle, menüyü bir kereden fazla açmak için tıklarsanız, olay tekrar gövdeye bağlanır ve menüyü birkaç kez gizlemeye çalışır. Bu sorunu gidermek için bir arıza güvenliği uygulanmalıdır.
marksyzm

.one- $('html')İşleyicinin içinde - kullanarak ciltlemeden sonra a yazın $('html').off('click').
Cody

4
@Cody bunun yardımcı olduğunu düşünmüyorum. oneİşleyici otomatik arayacak off(Bu jQuery docs gösterildiği şekliyle).
Mariano Desanze

44

Araştırmadan sonra üç çalışma çözümü buldum (referans için sayfa bağlantılarını unuttum)

İlk çözüm

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

İkinci çözüm

<script>
    $('body').on('click', function(e) {
        if($(e.target).closest('#testDiv').length == 0) {
           /* Hide dropdown here */
        }
    });
</script>

Üçüncü çözüm

<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
Üçüncü çözüm, en zarif kontrol yöntemidir. Ayrıca jQuery ek yükü içermez. Çok hoş. Çok yardımcı oldu. Teşekkürler.
dbarth

document.getElementsByClassNameBirisinin bir ipucu varsa lütfen paylaşın , üçüncü çözümü birden çok öğe ile devam etmeye çalışıyorum .
lowtechsun

@lowtechsun Her birini kontrol etmek için döngü yapmalısınız.
Donnie D'Amato

Gerçekten üçüncü çözümü sevdim, ancak div bölümümün tekrar gizlediğini göstermeye başlamadan önce tıklama tetikleniyor, neden herhangi bir fikir?
indiehjaerta

Üçüncüsü, console.log'a izin verir, elbette, ancak ekranı none olarak ayarlayarak gerçekten kapatmanıza izin vermez - çünkü menü gösterilmeden önce bunu yapar. Bunun için bir çözümünüz var mı?
RolandiXor

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

Benim için iyi çalışıyor.


3
Kullanacağım bu. Mükemmel olmayabilir, ancak bir hobi programcısı olarak, anlaşılması kolay.
kevtrout

Bulanıklık dışında bir hareket #menucontainerhakkında bir tıklama hakkındaydı
borrel

4
@borrel blur kabın dışında bir hareket değildir . Bulanıklık odağın tam tersidir, fareyi düşünmeyi düşünüyorsunuz. Bu çözüm, düz metin ve tıklamalardaki giriş alanları arasında ileri geri takas yaptığım "düzenlemek için tıklayın" metni oluştururken benim için özellikle işe yaradı.
parker.sikand

Ben eklemek zorunda tabindex="-1"için #menuscontainero iş yapmak. Kapsayıcıya bir giriş etiketi koyar ve üzerine tıklarsanız kap gizlenir.
tyrion

mouseleave etkinliği menüler ve kaplar için daha uygundur (ref: w3schools.com/jquery/… )
Evgenia Manolova

38

Şimdi bunun için bir eklenti var: dış etkinlikler ( blog yazısı )

Bir öğeye bir tıklama işleyicisi (WLOG) bağlandığında aşağıdakiler gerçekleşir :

  • elemanın tüm elemanlarını tutan bir dizi ilave edilir clickoutside işleyicileri
  • a ( ad boşluklu ) tıklama işleyici belgeye bağlıdır (henüz yoksa)
  • belgedeki herhangi bir tıklamayla , bu dizideki tıklama veya hedeflerin üst öğesi olmayan öğeler için clickoutside olayı tetiklenir
  • Buna ek olarak, clickoutside etkinliği için event.target , kullanıcının tıkladığı öğeye ayarlanır (böylece kullanıcının yalnızca dışını tıkladığını değil, neyi tıkladığını bile bilirsiniz)

Dolayısıyla, yayılmadan hiçbir olay durdurulmaz ve ek tıklama işleyicileri, dış işleyiciyle öğenin "üstünde" kullanılabilir.


Güzel bir eklenti. Blog yayın bağlantısı canlıyken "dış etkinlikler" bağlantısının bağlantısı kesildi ve "clickoutside" tür etkinlikler için çok yararlı bir eklenti sunuyor. Ayrıca MIT lisanslıdır.
TechNyquist

Harika bir eklenti. Mükemmel çalıştı. Kullanımı öyle:$( '#element' ).on( 'clickoutside', function( e ) { .. } );
Gavin

33

Bu benim için mükemmel çalıştı!

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

Bu çözüm, yaptığım şey için iyi çalıştı, hala kabarmak için tıklama etkinliğine ihtiyacım vardı, teşekkürler.
Mike

26

Gerçekten ihtiyacınız olan kullanıcı dış tıkladığında menüyü kapatmak olduğunu sanmıyorum; kullanıcı sayfanın herhangi bir yerini tıkladığında menünün kapanması için ihtiyacınız olan şey. Menüye tıklarsanız veya menüden çıkarsanız, menü doğru şekilde kapanmalıdır?

Yukarıda tatmin edici bir cevap bulamadığım geçen gün bu blog gönderisini yazmamı istedi . Daha bilgiçlik için, dikkat edilmesi gereken birkaç gotcha var:

  1. Gövde öğesine tıklama zamanında bir click olay işleyicisi eklerseniz, menüyü kapatmadan ve etkinliği kaldırmadan önce 2. tıklamayı beklediğinizden emin olun. Aksi takdirde, menüyü açan tıklama olayı, menüyü kapatmak zorunda olan dinleyiciye yayılır.
  2. Bir click etkinliğinde event.stopPropogation () yöntemini kullanırsanız, sayfanızdaki diğer öğelerin hiçbirinde tıklatma-kapanma özelliği bulunamaz.
  3. Gövde elemanına süresiz olarak bir tıklama olayı işleyicisi eklemek, performansa uygun bir çözüm değildir
  4. Etkinliğin hedefini ve ebeveynlerini işleyicinin yaratıcısıyla karşılaştırmak, istediğiniz şeyin menüyü tıkladığınızda kapatmak istediğinizi, gerçekten istediğiniz şeyin sayfanın herhangi bir yerini tıkladığınızda kapatmak olduğunu varsayar.
  5. Gövde öğesindeki olayları dinlemek, kodunuzu daha kırılgan hale getirecektir. Bunu kırdığı gibi masum bir şekilde şekillendirin:body { margin-left:auto; margin-right: auto; width:960px;}

2
"Menüye tıklarsanız ya da menüden çıkarsanız doğru kapanır mı?" her zaman değil. Bir öğeyi sürükleyerek bir tıklamayı iptal etmek yine de belge düzeyinde bir tıklamayı tetikler, ancak amaç menüyü kapatmaya devam etmek değildir. Ayrıca dahili olarak tıklamaya olanak tanıyan "tıklama" davranışını kullanabilen başka iletişim kutuları da vardır.
zzzzBov

25

Başka bir posterin de söylediği gibi, özellikle görüntülediğiniz öğenin (bu durumda bir menü) etkileşimli öğeleri varsa, çok sayıda gotchas var. Oldukça sağlam olmak için aşağıdaki yöntemi buldum:

$('#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

Durum için basit bir çözüm:

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

Yukarıdaki komut dosyası div, divclick olayının dışında tetiklenirse gizlenir .

Daha fazla bilgi için aşağıdaki blogu görebilirsiniz: http://www.codecanal.com/detect-click-outside-div-using-javascript/


22

Solution1

Bazı yan etkileri olabilecek event.stopPropagation () yerine basit bir bayrak değişkeni tanımlayın ve bir ifkoşul ekleyin . Bunu test ettim ve stopPropagation'ın herhangi bir yan etkisi olmadan düzgün çalıştım:

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

Solution2

Sadece basit bir ifkoşulla:

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

Bu çözümü bir boole bayrağıyla kullandım ve eklemli bir DOm ile de iyi ve ayrıca #menucontainer içinde başka birçok öğe varsa
Migio B

Çözüm 1 daha iyi çalışır, çünkü olayı belgeye yayılana kadar tıklama hedefi DOM'dan kaldırıldığında durumları işler.
Alice

21

Pencere tıklama etkinlik hedefini kontrol edin (başka hiçbir yerde yakalanmadığı sürece pencereye yayılmalıdır) ve menü öğelerinden herhangi biri olmadığından emin olun. Değilse, menünüzün dışındasınız demektir.

Veya tıklamanın konumunu kontrol edin ve menü alanında olup olmadığını kontrol edin.


18

Böyle bir şeyle başarılı oldum:

var $menuscontainer = ...;

$('#trigger').click(function() {
  $menuscontainer.show();

  $('body').click(function(event) {
    var $target = $(event.target);

    if ($target.parents('#menuscontainer').length == 0) {
      $menuscontainer.hide();
    }
  });
});

Mantık şöyledir: #menuscontainergösterildiğinde, #menuscontaineryalnızca hedef (tıklamanın) alt öğesi değilse gizlenen gövdeye bir tıklama işleyicisi bağlayın .


17

Varyant olarak:

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

Olay yayılımını durdurmakla ilgili bir sorun yoktur ve aynı sayfada birden fazla menüyü daha iyi destekler; burada bir birinci menü açıkken ikinci menüye tıklamak stopPropagation çözümünde ilk açık bırakılır.


16

Olay, öğenin event.path adında bir özelliğe sahiptir ve bu öğe "ağaç sırasına göre tüm atalarının statik sıralı listesi" dir . Bir olayın belirli bir DOM öğesinden mi, yoksa alt öğelerinden birinden mi kaynaklandığını kontrol etmek için, söz konusu DOM öğesinin yolunu kontrol etmeniz yeterlidir. Fonksiyondaki OReleman kontrolünü mantıksal olarak yaparak birden fazla elemanı kontrol etmek için de kullanılabilir 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>

Yani davanız için olmalı

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});

1
Edge'de çalışmaz.
Efsaneler

event.pathbir şey değil.
Andrew

14

Bu yöntemi bazı jQuery takvim eklentisinde buldum.

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

Gelecekteki izleyiciler için vanilya JavaScript çözümü.

Belgedeki herhangi bir öğeyi tıklattığınızda, tıklatılan öğenin kimliği değiştirilirse veya gizli öğe gizli değilse ve gizli öğe tıklanan öğeyi içermiyorsa öğeyi değiştirin.

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

Aynı sayfada birden fazla geçişiniz olacaksa, bunun gibi bir şey kullanabilirsiniz:

  1. hiddenDaraltılabilir öğeye sınıf adını ekleyin .
  2. Belge tıklatıldığında, tıklanan öğeyi içermeyen ve gizlenmeyen tüm gizli öğeleri kapatın
  3. Tıklanan öğe iki durumluysa, belirtilen öğeyi değiştirin.

(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

Kimsenin focusoutolayı gerçekten kabul etmediğine şaşırdım :

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>


Cevabımı kaçırdın sanırım. stackoverflow.com/a/47755925/6478359
Muhammet Can TONBUL

Bu, doğrudan doğruya kabul edilen cevap olmalıdır. Teşekkürler!!!
Şanslı Lam

8

IE ve FF 3 için komut dosyası oluşturuyorsanız ve yalnızca belirli bir kutu alanında tıklamanın olup olmadığını bilmek istiyorsanız, aşağıdaki gibi de kullanabilirsiniz:

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

Akış kesintisi, bulanıklaştırma / odaklanma olayı veya başka zor teknikler kullanmak yerine, olay akışını öğenin akrabasıyla eşleştirmeniz yeterlidir:

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

Tıklama dışı etkinlik dinleyicisini kaldırmak için basitçe:

$(document).off("click.menu-outside");

Bana göre bu en iyi çözümdü. Animasyon sonrası bir geri aramada kullandım, bu yüzden olayı somehwere'ye ihtiyacım vardı. +1
qwertzman

Burada gereksiz bir kontrol var. eğer eleman gerçekten #menuscontainerde hala ebeveynlerinden geçiyorsan . önce bunu kontrol etmelisiniz ve eğer o öğe değilse, DOM ağacına gidin.
vsync

Sağ ! Koşulu olarak değiştirebilirsiniz if(!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){. Bu küçük bir optimizasyon, ancak programınızın ömründe sadece birkaç kez gerçekleşir: her tıklama için, click.menu-outsideetkinlik kaydedilmişse. Daha uzun (+32 karakter) ve yöntem zinciri kullanmayın
14'te

8

kullanın:

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

Burada merak eden biri javascript çözümü (es6) ise:

window.addEventListener('mouseup', e => {
        if (e.target != yourDiv && e.target.parentNode != yourDiv) {
            yourDiv.classList.remove('show-menu');
            //or yourDiv.style.display = 'none';
        }
    })

ve es5, her ihtimale karşı:

window.addEventListener('mouseup', function (e) {
if (e.target != yourDiv && e.target.parentNode != yourDiv) {
    yourDiv.classList.remove('show-menu'); 
    //or yourDiv.style.display = 'none';
}

});


7

İşte saf javascript ile basit bir çözüm. Öyle kadar güncel ES6 ile :

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 ile güncel" () => {}yerine ES6 ile güncel olan tek şey oldukça cesur bir iddiadır function() {}. Orada var ES6 bir bükülme ile düz JavaScript olarak sınıflandırılır.
MortenMoulder

@MortenMoulder: Ya. Aslında ES6 olmasına rağmen sadece dikkat içindir. Ancak çözüme bakın. Bence bu iyi.
Duannx

Vanilya JS'sidir ve DOM'dan kaldırılan etkinlik hedefi için çalışır (örneğin, iç pop-up'dan değer seçildiğinde, hemen pop-up'ı kapatır). Benden +1!
Alice

6

Belgeye bir tıklama olayı dinleyicisi bağlayın. Olay dinleyicisinin içinde, olay nesnesine , özellikle event.target'a tıklanarak hangi öğenin tıklandığını görebilirsiniz:

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

Aşağıdaki komut dosyasını kullandım ve jQuery ile yaptım.

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 kodunu aşağıda bulabilirsiniz

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

Sen öğretici okuyabilir burada


5
$(document).click(function() {
    $(".overlay-window").hide();
});
$(".overlay-window").click(function() {
    return false;
});

Belgeyi tıklarsanız, aynı öğeyi tıklatmadığınız sürece belirli bir öğeyi gizleyin.


5

En popüler cevap için oy verin, ancak ekleyin

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

dolayısıyla, kaydırma çubuğundaki bir tıklama hedef öğenizi [gizlemez veya herhangi bir şekilde] gizlemez.


5

Daha kolay kullanım ve daha etkileyici kod için, bunun için bir jQuery eklentisi oluşturdum:

$('div.my-element').clickOut(function(target) { 
    //do something here... 
});

Not: hedef , kullanıcının gerçekten tıkladığı öğedir. Eğer kullanmak böylece Ama geri arama hala orijinal elemanın bağlamında yürütülür bu bir jQuery geri dönüşünde yer beklediğiniz gibi.

Eklenti:

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

Varsayılan olarak, tıklama olayı dinleyicisi belgeye yerleştirilir. Ancak, olay dinleyicisi kapsamını sınırlamak istiyorsanız, tıklamaların dinleneceği en üst üst öğe olacak bir üst düzey öğeyi temsil eden bir jQuery nesnesini iletebilirsiniz. Bu, belge düzeyinde gereksiz olay dinleyicilerini önler. Açıkçası, sağlanan üst öğe ilk öğenizin bir üst öğesi değilse, çalışmaz.

Şöyle kullanın:

$('div.my-element').clickOut($('div.my-parent'), function(target) { 
    //do something here...
});
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.