Bagaimana cara mendeteksi klik di luar elemen?

2486

Saya memiliki beberapa menu HTML, yang saya tampilkan sepenuhnya ketika seorang pengguna mengklik bagian atas menu ini. Saya ingin menyembunyikan elemen-elemen ini ketika pengguna mengklik di luar area menu.

Apakah hal seperti ini dimungkinkan dengan jQuery?

$("#menuscontainer").clickOutsideThisElement(function() {
    // Hide the menus
});
Sergio del Amo
sumber
47
Inilah contoh strategi ini: jsfiddle.net/tedp/aL7Xe/1
Ted
18
Seperti yang disebutkan Tom, Anda harus membaca css-tricks.com/dangers-stopping-event-propagation sebelum menggunakan pendekatan ini. Alat jsfiddle itu cukup keren.
Jon Coombs
3
dapatkan referensi ke elemen dan kemudian event.target, dan akhirnya! = atau == keduanya kemudian jalankan kode yang sesuai ..
Rohit Kumar
Saya merekomendasikan penggunaan github.com/gsantiago/jquery-clickout :)
Rômulo M. Farias
2
Solusi Vanilla JS dengan event.targetdan tanpa event.stopPropagation .
lowtechsun

Jawaban:

1812

CATATAN: Menggunakan stopEventPropagation()adalah sesuatu yang harus dihindari karena merusak aliran peristiwa normal di DOM. Lihat artikel ini untuk informasi lebih lanjut. Pertimbangkan untuk menggunakan metode ini sebagai gantinya

Lampirkan acara klik ke badan dokumen yang menutup jendela. Lampirkan acara klik terpisah ke wadah yang menghentikan propagasi ke badan dokumen.

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

$('#menucontainer').click(function(event){
    event.stopPropagation();
});
Eran Galperin
sumber
708
Ini mematahkan perilaku standar banyak hal, termasuk tombol dan tautan, yang terkandung dalam #menucontainer. Saya terkejut jawaban ini sangat populer.
Seni
75
Ini tidak merusak perilaku apa pun di dalam #menucontainer, karena ia berada di bagian bawah rantai propagasi untuk apa pun di dalamnya.
Eran Galperin
94
itu sangat indah tetapi Anda harus menggunakan $('html').click()bukan tubuh. Tubuh selalu memiliki tinggi isinya. Itu tidak ada banyak konten atau layar sangat tinggi, itu hanya berfungsi pada bagian yang diisi oleh tubuh.
meo
103
Saya juga terkejut bahwa solusi ini mendapat begitu banyak suara. Ini akan gagal untuk elemen di luar yang memiliki stopPropagation jsfiddle.net/Flandre/vaNFw/3
Andre
140
Philip Walton menjelaskan dengan sangat baik mengapa jawaban ini bukan solusi terbaik: css-tricks.com/dangers-stopping-event-propagation
Tom
1387

Anda dapat mendengarkan acara klikdocument dan kemudian memastikan #menucontainerbukan leluhur atau target dari elemen yang diklik dengan menggunakan .closest().

Jika tidak, maka elemen yang diklik berada di luar #menucontainerdan Anda dapat menyembunyikannya dengan aman.

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

Edit - 2017-06-23

Anda juga dapat membersihkan setelah pendengar acara jika Anda berencana untuk mengabaikan menu dan ingin berhenti mendengarkan acara. Fungsi ini hanya akan membersihkan pendengar yang baru saja dibuat, menjaga pendengar klik lainnya aktif document. Dengan sintaks 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)
}

Edit - 2018-03-11

Bagi mereka yang tidak ingin menggunakan jQuery. Berikut kode di atas dalam plain 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 

CATATAN: Ini didasarkan pada komentar Alex untuk hanya menggunakan !element.contains(event.target)bukan bagian jQuery.

Tetapi element.closest()sekarang juga tersedia di semua browser utama (versi W3C sedikit berbeda dari yang jQuery). Polyfills dapat ditemukan di sini: Element.closest ()

Edit - 2020-05-21

Jika Anda ingin pengguna dapat mengklik dan menarik di dalam elemen, lalu lepaskan mouse di luar elemen, tanpa menutup elemen:

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

Dan di 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()
        }
    }
Seni
sumber
28
Saya mencoba banyak jawaban lain, tetapi hanya yang ini berhasil. Terima kasih. Kode yang akhirnya saya gunakan adalah ini: $ (document) .click (function (event) {if ($ (event.target) .closest ('. Window'). Length == 0) {$ ('. Window' ) .fadeOut ('fast');}});
Pistos
39
Saya benar-benar berakhir dengan solusi ini karena lebih baik mendukung beberapa menu pada halaman yang sama di mana mengklik pada menu kedua saat yang pertama terbuka akan meninggalkan yang pertama terbuka dalam solusi stopPropagation.
umassthrower
13
Jawaban yang sangat bagus. Ini adalah cara untuk pergi ketika Anda memiliki beberapa item yang ingin Anda tutup.
Yohanes
5
Ini harus menjadi jawaban yang diterima karena kekurangan solusi lain dengan event.stopPropagation ().
tomat
20
Tanpa jQuery - !element.contains(event.target)menggunakan Node.contains ()
Alex Ross
304

Bagaimana cara mendeteksi klik di luar elemen?

Alasan mengapa pertanyaan ini sangat populer dan memiliki begitu banyak jawaban adalah karena itu rumit. Setelah hampir delapan tahun dan lusinan jawaban, saya benar-benar terkejut melihat betapa sedikit perhatian diberikan pada aksesibilitas.

Saya ingin menyembunyikan elemen-elemen ini ketika pengguna mengklik di luar area menu.

Ini adalah tujuan mulia dan merupakan masalah aktual . Judul pertanyaan — yang merupakan jawaban sebagian besar jawaban yang tampaknya ingin diatasi — berisi ikan haring merah yang malang.

Petunjuk: itu kata "klik" !

Anda sebenarnya tidak ingin mengikat penangan klik.

Jika Anda mengikat penangan klik untuk menutup dialog, Anda sudah gagal. Alasan Anda gagal adalah karena tidak semua orang memicu clickacara. Pengguna yang tidak menggunakan mouse akan dapat keluar dari dialog Anda (dan menu pop-up Anda bisa dibilang jenis dialog) dengan menekan Tab, dan mereka kemudian tidak akan dapat membaca konten di balik dialog tanpa kemudian memicu suatu clickacara.

Jadi mari kita ulangi pertanyaannya.

Bagaimana cara menutup dialog ketika pengguna selesai dengan itu?

Inilah tujuannya. Sayangnya, sekarang kita perlu mengikat userisfinishedwiththedialogacara, dan pengikatan itu tidak mudah.

Jadi bagaimana kita bisa mendeteksi bahwa pengguna telah selesai menggunakan dialog?

focusout peristiwa

Awal yang baik adalah menentukan apakah fokus telah meninggalkan dialog.

Petunjuk: hati-hati dengan bluracara tersebut, blurjangan menyebar jika acara itu terikat ke fase gelembung!

jQuery focusoutakan baik-baik saja. Jika Anda tidak dapat menggunakan jQuery, maka Anda dapat menggunakan blurselama fase penangkapan:

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

Juga, untuk banyak dialog Anda harus mengizinkan wadah untuk mendapatkan fokus. Tambah tabindex="-1"untuk memungkinkan dialog menerima fokus secara dinamis tanpa mengganggu aliran tab.

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


Jika Anda bermain dengan demo itu selama lebih dari satu menit, Anda harus segera mulai melihat masalah.

Yang pertama adalah bahwa tautan dalam dialog tidak dapat diklik. Mencoba mengkliknya atau menabraknya akan menyebabkan dialog ditutup sebelum interaksi terjadi. Ini karena memfokuskan elemen dalam memicu suatu focusoutperistiwa sebelum memicu suatu focusinperistiwa lagi.

Cara mengatasinya adalah mengantri perubahan status pada loop acara. Ini dapat dilakukan dengan menggunakan setImmediate(...), atau setTimeout(..., 0)untuk browser yang tidak mendukung setImmediate. Setelah antri itu dapat dibatalkan oleh yang berikut 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'));
  }
});

Masalah kedua adalah bahwa dialog tidak akan ditutup ketika tautan ditekan lagi. Ini karena dialog kehilangan fokus, memicu perilaku tutup, setelah itu klik tautan memicu dialog untuk dibuka kembali.

Mirip dengan masalah sebelumnya, kondisi fokus perlu dikelola. Mengingat bahwa perubahan status telah diantrekan, itu hanya masalah penanganan acara fokus pada pemicu dialog:

Ini seharusnya terlihat familier
$('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 kunci

Jika Anda merasa sudah selesai dengan menangani status fokus, masih ada lagi yang bisa Anda lakukan untuk menyederhanakan pengalaman pengguna.

Ini sering merupakan fitur "baik untuk memiliki", tetapi itu umum bahwa ketika Anda memiliki modal atau popup dalam bentuk apa pun bahwa Esckunci akan menutupnya.

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


Jika Anda tahu Anda memiliki elemen yang bisa difokus di dalam dialog, Anda tidak perlu memfokuskan dialog secara langsung. Jika Anda membuat menu, Anda bisa memfokuskan item menu pertama.

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


Peran WAI-ARIA dan Dukungan Aksesibilitas Lainnya

Jawaban ini semoga mencakup dasar-dasar dukungan keyboard dan mouse yang dapat diakses untuk fitur ini, tetapi karena sudah cukup besar saya akan menghindari diskusi tentang peran dan atribut WAI-ARIA , namun saya sangat merekomendasikan agar pelaksana merujuk pada spesifikasi untuk perinciannya. tentang peran apa yang harus mereka gunakan dan atribut lain yang sesuai.

zzzzBov
sumber
29
Ini adalah jawaban yang paling lengkap, dengan penjelasan dan aksesibilitas dalam pikiran. Saya pikir ini harus menjadi jawaban yang diterima karena sebagian besar jawaban lain hanya menangani klik dan hanya potongan kode yang dijatuhkan tanpa penjelasan.
Cyrille
4
Luar biasa, dijelaskan dengan baik. Saya hanya menggunakan pendekatan ini pada Komponen Bereaksi dan bekerja dengan sempurna terima kasih
David Lavieri
2
@zzzzBov terima kasih atas jawaban yang dalam, saya mencoba mencapainya di vanilla JS, dan saya agak bingung dengan semua hal jquery. apakah ada yang serupa di vanilla js?
HendrikEng
2
@zzzzBov tidak, saya tidak mencari Anda untuk menulis versi bebas jQuery tentu saja, saya akan mencoba melakukannya dan saya kira yang terbaik adalah mengajukan pertanyaan baru di sini jika saya benar-benar terjebak. Terima kasih banyak lagi.
HendrikEng
7
Meskipun ini adalah metode yang bagus untuk mendeteksi mengklik dari dropdown kustom atau input lain, itu tidak boleh menjadi metode yang disukai untuk modals atau popup karena dua alasan. Modal akan ditutup ketika pengguna beralih ke tab atau jendela lain, atau membuka menu konteks, yang benar-benar menjengkelkan. Selain itu, acara 'klik' diaktifkan pada mouse ke atas, sedangkan acara 'fokus' memicu instan Anda menekan mouse ke bawah. Biasanya, sebuah tombol hanya melakukan tindakan jika Anda menekan tombol mouse ke bawah dan kemudian melepaskannya. Cara yang tepat dan dapat diakses untuk melakukan ini untuk modals adalah dengan menambahkan tombol tutup tabbable.
Kevin
144

Solusi lain di sini tidak bekerja untuk saya, jadi saya harus menggunakan:

if(!$(event.target).is('#foo'))
{
    // hide menu
}
Dennis
sumber
Saya telah memposting contoh praktis lain tentang cara menggunakan event.target untuk menghindari pemicu widget Jquery UI lain di luar klik penangan html saat menyematkannya di kotak sembulan Anda sendiri: Cara terbaik untuk mendapatkan Target Asli
Joey T
43
Ini bekerja untuk saya, kecuali saya menambahkan && !$(event.target).parents("#foo").is("#foo")di dalam IFpernyataan sehingga elemen anak tidak akan menutup menu ketika diklik.
honyovk
1
Tidak dapat menemukan yang lebih baik daripada: // Kami di luar $ (event.target) .parents ('# foo'). Length == 0
AlexG
3
Peningkatan singkat untuk menangani deep nesting adalah dengan menggunakan .is('#foo, #foo *'), tetapi saya tidak merekomendasikan penjilidan klik yang mengikat untuk mengatasi masalah ini .
zzzzBov
1
!$(event.target).closest("#foo").lengthakan lebih baik dan meniadakan kebutuhan untuk penambahan @ honyovk.
tvanc
127

Saya memiliki aplikasi yang bekerja mirip dengan contoh Eran, kecuali saya lampirkan acara klik ke tubuh ketika saya membuka menu ... Agak seperti ini:

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

  event.stopPropagation();
});

Informasi lebih lanjut tentang fungsi jQueryone()

Joe Lencioni
sumber
9
tapi kemudian jika Anda klik pada menu itu sendiri, kemudian di luar, itu tidak akan bekerja :)
vsync
3
Ini membantu untuk menempatkan event.stopProgagantion () sebelum mengikat pendengar klik ke badan.
Jasper Kennis
4
Masalah dengan ini adalah bahwa "satu" berlaku untuk metode jQuery untuk menambahkan peristiwa ke array beberapa kali. Jadi, jika Anda mengklik menu untuk membukanya lebih dari satu kali, acara tersebut terikat ke tubuh lagi dan mencoba untuk menyembunyikan menu beberapa kali. Failafe harus diterapkan untuk memperbaiki masalah ini.
marksyzm
Setelah mengikat menggunakan .one - di dalam $('html')pawang - tulis a $('html').off('click').
Cody
4
@Cody Saya tidak berpikir itu membantu. The onehandler secara otomatis akan memanggil off(seperti yang ditampilkan dalam dokumentasi jQuery).
Mariano Desanze
44

Setelah penelitian, saya menemukan tiga solusi yang berfungsi (saya lupa tautan halaman untuk referensi)

Solusi pertama

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

Solusi kedua

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

Solusi ketiga

<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>
Rameez Rami
sumber
8
Solusi ketiga sejauh ini merupakan cara pemeriksaan paling elegan. Ini juga tidak melibatkan overhead jQuery. Sangat bagus. Itu banyak membantu. Terima kasih.
dbarth
Saya mencoba untuk mendapatkan solusi ketiga dengan beberapa elemen document.getElementsByClassName, jika seseorang memiliki petunjuk, silakan bagikan.
lowtechsun
@lowtechsun Anda harus mengulang untuk memeriksa masing-masing.
Donnie D'Amato
Benar-benar menyukai solusi ketiga, namun klik memicu sebelum div saya mulai menunjukkan yang menyembunyikannya lagi, tahu mengapa?
indiehjaerta
Yang ketiga memungkinkan untuk console.log, tentu saja, tetapi itu tidak membuat Anda benar-benar menutup dengan mengatur tampilan ke none - karena akan melakukan ini sebelum menu ditampilkan. Apakah Anda punya solusi untuk ini?
RolandiXor
40
$("#menuscontainer").click(function() {
    $(this).focus();
});
$("#menuscontainer").blur(function(){
    $(this).hide();
});

Bekerja untuk saya baik-baik saja.

pengguna212621
sumber
3
Ini yang saya gunakan. Ini mungkin tidak sempurna, tetapi sebagai programmer hobi, cukup sederhana untuk dipahami dengan jelas.
kevtrout
blur adalah langkah di luar #menucontainerpertanyaannya adalah tentang klik
borrel
4
@borrel blur bukan gerakan di luar wadah. Blur adalah kebalikan dari fokus, Anda berpikir tentang mouseout. Solusi ini bekerja sangat baik bagi saya ketika saya membuat teks "klik untuk mengedit" di mana saya bertukar bolak-balik antara teks biasa dan bidang input pada klik.
parker.sikand
Saya harus menambahkan tabindex="-1"ke #menuscontaineruntuk membuatnya bekerja. Tampaknya jika Anda memasukkan tag input ke dalam wadah dan mengkliknya, wadah tersebut akan disembunyikan.
tyrion
acara mouseleave lebih cocok untuk menu dan wadah (ref: w3schools.com/jquery/… )
Evgenia Manolova
38

Sekarang ada plugin untuk itu: acara luar ( posting blog )

Berikut ini terjadi ketika penangan klik - keluar (WLOG) terikat ke suatu elemen:

  • elemen ditambahkan ke array yang menampung semua elemen dengan penangan clickoutside
  • penangan klik ( namespaced ) terikat ke dokumen (jika belum ada)
  • pada klik apa pun dalam dokumen, acara sisi klik dipicu untuk elemen-elemen dalam array yang tidak sama dengan atau orang tua dari klik- target acara
  • selain itu, event.target untuk acara clickoutside diatur ke elemen yang diklik pengguna (sehingga Anda bahkan tahu apa yang diklik pengguna, bukan hanya dia mengklik di luar)

Jadi tidak ada acara yang dihentikan dari propagasi dan penangan klik tambahan dapat digunakan "di atas" elemen dengan penangan luar.

Wolfram
sumber
Plugin yang bagus. Tautan di "acara luar" sudah mati, sedangkan tautan posting blog tetap hidup dan menyediakan plugin yang sangat berguna untuk acara semacam "clickoutside". Ini juga berlisensi MIT.
TechNyquist
Plugin yang bagus. Bekerja dengan sempurna. Penggunaannya seperti itu:$( '#element' ).on( 'clickoutside', function( e ) { .. } );
Gavin
33

Ini bekerja dengan baik untuk saya !!

$('html').click(function (e) {
    if (e.target.id == 'YOUR-DIV-ID') {
        //do something
    } else {
        //do something
    }
});
srinath
sumber
Solusi ini bekerja dengan baik untuk apa yang saya lakukan, di mana saya masih membutuhkan acara klik untuk muncul, terima kasih.
Mike
26

Saya tidak berpikir apa yang sebenarnya Anda butuhkan adalah menutup menu ketika pengguna mengklik di luar; apa yang Anda butuhkan adalah untuk menutup menu ketika pengguna mengklik di mana saja pada halaman. Jika Anda mengklik pada menu, atau mematikan menu itu harus menutup kan?

Tidak menemukan jawaban yang memuaskan di atas mendorong saya untuk menulis posting blog ini beberapa hari yang lalu. Untuk yang lebih bertele-tele, ada sejumlah gotcha untuk diperhatikan:

  1. Jika Anda melampirkan pengendali acara klik ke elemen tubuh pada waktu klik pastikan untuk menunggu klik kedua sebelum menutup menu, dan melepaskan ikatan acara. Kalau tidak, acara klik yang membuka menu akan muncul ke pendengar yang harus menutup menu.
  2. Jika Anda menggunakan event.stopPropogation () pada acara klik, tidak ada elemen lain di halaman Anda yang dapat memiliki fitur klik di mana saja untuk menutup.
  3. Melampirkan pengendali acara klik ke elemen tubuh tanpa batas bukanlah solusi yang tepat
  4. Membandingkan target acara, dan orang tuanya dengan pembuat handler mengasumsikan bahwa yang Anda inginkan adalah menutup menu ketika Anda mengkliknya, ketika Anda benar-benar ingin menutupnya ketika Anda mengklik di mana saja pada halaman.
  5. Mendengarkan acara pada elemen tubuh akan membuat kode Anda lebih rapuh. Styling tidak bersalah seperti ini akan merusaknya:body { margin-left:auto; margin-right: auto; width:960px;}
34m0
sumber
2
"Jika kamu mengklik pada menu, atau mematikan menu itu harusnya menutup kan?" tidak selalu. Membatalkan klik dengan menarik elemen akan tetap memicu klik tingkat dokumen, tetapi maksudnya bukan untuk melanjutkan menutup menu. Ada juga banyak jenis dialog lain yang bisa menggunakan perilaku "klik-keluar" yang memungkinkan untuk mengklik secara internal.
zzzzBov
25

Seperti poster lain katakan ada banyak gotcha, terutama jika elemen yang Anda tampilkan (dalam hal ini menu) memiliki elemen interaktif. Saya menemukan metode berikut cukup kuat:

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

    })
});
benb
sumber
25

Solusi sederhana untuk situasi ini adalah:

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

Script di atas akan menyembunyikan divjika di luar divacara klik dipicu.

Anda dapat melihat blog berikut untuk informasi lebih lanjut: http://www.codecanal.com/detect-click-outside-div-using-javascript/

Jitendra Damor
sumber
22

Solusi1

Alih-alih menggunakan event.stopPropagation () yang dapat mempengaruhi beberapa sisi, cukup tentukan variabel flag sederhana dan tambahkan satu ifsyarat. Saya menguji ini dan bekerja dengan baik tanpa ada efek samping dari 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";
    }
});

Solusi2

Hanya dengan ifkondisi sederhana :

$(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
    }
});
Iman Sedighi
sumber
Saya menggunakan solusi ini dengan bendera boolean dan ada baiknya juga dengan DOm yang diartikulasikan dan juga jika di dalam #menucontainer ada banyak elemen lain
Migio B
Solusi 1 berfungsi lebih baik, karena menangani kasus ketika target klik dihapus dari DOM pada saat acara menyebar ke dokumen.
Alice
21

Periksa target acara klik jendela (harus merambat ke jendela, asalkan tidak ditangkap di tempat lain), dan pastikan itu bukan elemen menu. Jika tidak, maka Anda berada di luar menu Anda.

Atau periksa posisi klik, dan lihat apakah itu ada di dalam area menu.

Chris MacDonald
sumber
18

Saya sudah sukses dengan sesuatu seperti ini:

var $menuscontainer = ...;

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

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

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

Logikanya adalah: ketika #menuscontainerditampilkan, ikat penangan klik ke badan yang menyembunyikan #menuscontainerhanya jika target (dari klik) bukan anak dari itu.

Chu Yeow
sumber
17

Sebagai varian:

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

Ini tidak memiliki masalah dengan menghentikan propagasi acara dan lebih baik mendukung beberapa menu pada halaman yang sama di mana mengklik pada menu kedua saat yang pertama terbuka akan meninggalkan yang pertama terbuka dalam solusi stopPropagation.

Bohdan Lyzanets
sumber
16

Acara ini memiliki properti yang disebut event.path dari elemen yang merupakan "daftar urutan statis semua leluhurnya dalam urutan pohon" . Untuk memeriksa apakah suatu peristiwa berasal dari elemen DOM tertentu atau salah satu dari anak-anaknya, cukup periksa lintasan untuk elemen DOM tertentu. Itu juga dapat digunakan untuk memeriksa beberapa elemen dengan secara logis ORmemeriksa elemen pada somefungsi.

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

Jadi untuk kasus Anda Seharusnya begitu

$("body").click(function() {
  target = $("#menuscontainer")[0];
  flag = event.path.some(function(el, i, arr) {
    return (el == target)
  });
  if (!flag) {
    // Hide the menus
  }
});
Dan Philip
sumber
1
Tidak berfungsi di Edge.
Legenda
event.pathbukanlah suatu hal.
Andrew
14

Saya menemukan metode ini di beberapa plugin kalender 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);
nazar kuliyev
sumber
13

Ini adalah solusi JavaScript vanili untuk pemirsa di masa depan.

Setelah mengklik elemen apa pun di dalam dokumen, jika id elemen yang diklik diaktifkan, atau elemen tersembunyi tidak disembunyikan dan elemen tersembunyi tidak mengandung elemen yang diklik, alihkan elemen tersebut.

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

Jika Anda akan memiliki beberapa matikan di halaman yang sama, Anda dapat menggunakan sesuatu seperti ini:

  1. Tambahkan nama kelas hidden ke item yang dapat dilipat.
  2. Saat dokumen di-klik, tutup semua elemen tersembunyi yang tidak mengandung elemen yang diklik dan tidak disembunyikan
  3. Jika elemen yang diklik adalah toggle, alihkan elemen yang ditentukan.

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

user4639281
sumber
13

Saya terkejut tidak ada yang benar-benar mengakui focusoutacara:

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>

Jovanni G
sumber
Anda melewatkan jawaban saya, saya pikir. stackoverflow.com/a/47755925/6478359
Muhammet Can TONBUL
Ini harus menjadi jawaban yang diterima, langsung ke intinya. Terima kasih!!!
Lucky Lam
8

Jika Anda membuat skrip untuk IE dan FF 3. * dan Anda hanya ingin tahu apakah klik itu terjadi dalam area kotak tertentu, Anda juga bisa menggunakan sesuatu seperti:

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;}
Erik
sumber
8

Alih-alih menggunakan gangguan aliran, kejadian blur / fokus, atau teknik rumit lainnya, cukup sesuaikan aliran acara dengan kekerabatan elemen:

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

Untuk menghapus klik di luar pendengar acara, cukup:

$(document).off("click.menu-outside");
mems
sumber
Bagi saya ini adalah solusi terbaik. Digunakan dalam panggilan balik setelah animasi jadi saya perlu ke acara lepaskan entah di mana. +1
qwertzman
Ada cek berlebihan di sini. jika elemen tersebut memang #menuscontainerAnda masih melalui orang tua itu. Anda harus terlebih dahulu memeriksa itu, dan jika itu bukan elemen itu, maka naik pohon DOM.
vsync
Baik ! Anda dapat mengubah kondisinya menjadi if(!($(event.target).is("#menuscontainer") || $(event.target).parents().is("#menuscontainer"))){. Ini adalah pengoptimalan kecil, tetapi hanya terjadi beberapa kali dalam masa program Anda: untuk setiap klik, jika click.menu-outsideacara tersebut terdaftar. Lebih panjang (+32 karakter) dan tidak menggunakan metode chaining
mems
8

Menggunakan:

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

Jika seseorang yang penasaran di sini adalah solusi javascript (es6):

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

dan es5, untuk berjaga-jaga:

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

});

Walt
sumber
7

Berikut ini adalah solusi sederhana dengan javascript murni. Ini terkini dengan 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;
})
Duannx
sumber
"Up-to-date dengan ES6" adalah klaim yang cukup berani, ketika satu-satunya hal yang terkini dengan ES6 adalah melakukan () => {}alih - alih function() {}. Apa yang Anda miliki di sana diklasifikasikan sebagai JavaScript biasa dengan sentuhan ES6.
MortenMoulder
@MortenMoulder: Ya. Ini hanya untuk perhatian meskipun sebenarnya ES6. Tapi lihat saja solusinya. Saya pikir itu bagus.
Duannx
Ini vanilla JS dan berfungsi untuk target acara yang dihapus dari DOM (mis. Ketika nilai dari popup dipilih, segera tutup popup). +1 dari saya!
Alice
6

Kaitkan pendengar acara klik pada dokumen. Di dalam pendengar acara, Anda dapat melihat objek acara , khususnya, event.target untuk melihat elemen apa yang diklik:

$(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");
    }
});
Salman A
sumber
6

Saya telah menggunakan skrip di bawah ini dan selesai dengan 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
    }
})

Di bawah ini temukan kode 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>

Anda dapat membaca tutorialnya di sini

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

Jika Anda mengklik dokumen tersebut, sembunyikan elemen yang diberikan, kecuali jika Anda mengklik elemen yang sama.

Rowan
sumber
5

Suara positif untuk jawaban paling populer, tetapi tambahkan

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

jadi, klik pada bilah gulir tidak [menyembunyikan atau apa pun] elemen target Anda.

bbe
sumber
5

Untuk penggunaan yang lebih mudah, dan kode yang lebih ekspresif, saya membuat plugin jQuery untuk ini:

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

Catatan: target adalah elemen yang sebenarnya diklik pengguna. Tetapi panggilan balik masih dieksekusi dalam konteks elemen asli, sehingga Anda dapat memanfaatkan ini seperti yang Anda harapkan dalam panggilan balik jQuery.

Plugin:

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

Secara default, pendengar peristiwa klik ditempatkan pada dokumen. Namun, jika Anda ingin membatasi ruang lingkup pendengar acara, Anda bisa meneruskan dalam objek jQuery yang mewakili elemen tingkat induk yang akan menjadi induk teratas di mana klik akan didengarkan. Ini mencegah pendengar acara tingkat dokumen yang tidak perlu. Jelas, itu tidak akan berfungsi kecuali elemen induk yang disediakan adalah induk dari elemen awal Anda.

Gunakan seperti ini:

$('div.my-element').clickOut($('div.my-parent'), function(target) { 
    //do something here...
});
Matt Goodwin
sumber