Pertanyaan Bagaimana cara mendeteksi klik di luar elemen?


Saya memiliki beberapa menu HTML, yang saya tunjukkan sepenuhnya ketika seorang pengguna mengklik pada kepala menu-menu ini. Saya ingin menyembunyikan elemen-elemen ini ketika pengguna mengklik di luar area menu.

Apakah hal seperti ini mungkin terjadi dengan jQuery?

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

2023


asal


Jawaban:


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

Lampirkan peristiwa klik ke badan dokumen yang menutup jendela. Lampirkan peristiwa klik terpisah ke jendela yang berhenti menyebar ke badan dokumen.

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

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

1618



Anda dapat mendengarkan a klik acara berlangsung document dan kemudian pastikan #menucontainer bukan leluhur atau target elemen yang diklik dengan menggunakan .closest().

Jika tidak, maka elemen yang diklik berada di luar #menucontainer dan Anda bisa menyembunyikannya dengan aman.

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

Sunting - 2017-06-23

Anda juga dapat membersihkan setelah pendengar acara jika Anda berencana untuk menutup menu dan ingin berhenti mendengarkan acara. Fungsi ini hanya akan membersihkan pendengar yang baru dibuat, menjaga pendengar klik lainnya document. Dengan sintaks ES2015:

export function hideOnClickOutside(selector) {
  const outsideClickListener = (event) => {
    if (!$(event.target).closest(selector).length) {
      if ($(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. Inilah kode di atas dalam plain vanillaJS (ECMAScript6).

function hideOnClickOutside(element) {
    const outsideClickListener = event => {
        if (!element.contains(event.target)) { // or use: event.target.closest(selector) === null
            if (isVisible(element)) {
                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 berdasarkan komentar Alex untuk digunakan saja !element.contains(event.target) bukannya bagian jQuery.

Tapi element.closest() sekarang juga tersedia di semua browser utama (versi W3C sedikit berbeda dari yang satu jQuery). Polyfills dapat ditemukan di sini: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest


1123



Bagaimana cara mendeteksi klik di luar elemen?

Alasan mengapa pertanyaan ini begitu populer dan memiliki begitu banyak jawaban adalah bahwa hal itu tampak rumit. Setelah hampir delapan tahun dan puluhan jawaban, saya benar-benar terkejut melihat betapa sedikit perawatan yang diberikan untuk aksesibilitas.

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

Ini adalah tujuan mulia dan merupakan sebenarnya isu. Judul pertanyaan — yang paling dicari oleh sebagian besar jawaban untuk ditangani — mengandung ikan hering merah yang tidak menguntungkan.

Petunjuk: itu adalah kata "klik"!

Anda sebenarnya tidak ingin mengikat penangan klik.

Jika Anda mengikat handler klik untuk menutup dialog, Anda sudah gagal. Alasan Anda gagal adalah tidak semua orang yang memicu click acara. 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 a click peristiwa.

Jadi mari kita ulang pertanyaannya.

Bagaimana seseorang menutup dialog ketika pengguna selesai dengan itu?

Ini adalah tujuannya. Sayangnya, sekarang kita perlu mengikat userisfinishedwiththedialog event, dan pengikatan itu tidak begitu 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: berhati-hati dengan blur peristiwa, blur tidak menyebar jika acara terikat ke fase menggelegak!

jQuery's focusout akan baik-baik saja. Jika Anda tidak dapat menggunakan jQuery, maka Anda dapat menggunakannya blur selama fase penangkapan:

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

Juga, untuk banyak dialog Anda harus mengizinkan wadah untuk mendapatkan fokus. Menambahkan tabindex="-1" untuk memungkinkan dialog menerima fokus secara dinamis tanpa mengganggu alur tabbing.

$('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 tautan di dialog tidak dapat diklik. Mencoba untuk mengkliknya atau tab untuk itu akan mengarah ke dialog menutup sebelum interaksi terjadi. Ini karena memfokuskan elemen batin memicu a focusout acara sebelum memicu a focusin acara lagi.

Perbaikannya adalah mengantre perubahan status pada loop acara. Ini bisa dilakukan dengan menggunakan setImmediate(...), atau setTimeout(..., 0) untuk browser yang tidak mendukung setImmediate. Setelah diantrekan dapat dibatalkan oleh berikutnya focusin:

$('.submenu').on({
  focusout: function (e) {
    $(this).data('submenuTimer', setTimeout(function () {
      $(this).removeClass('submenu--active');
    }.bind(this), 0));
  },
  focusin: function (e) {
    clearTimeout($(this).data('submenuTimer'));
  }
});

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

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

Masalah kedua adalah dialog tidak akan menutup ketika tautan ditekan lagi. Ini karena dialog kehilangan fokus, memicu perilaku dekat, setelah tautan klik memicu dialog untuk membuka kembali.

Mirip dengan masalah sebelumnya, negara fokus perlu dikelola. Mengingat bahwa perubahan negara telah diantrekan, itu hanya masalah menangani peristiwa fokus pada pemicu dialog:

Ini seharusnya terlihat akrab
$('a').on({
  focusout: function () {
    $(this.hash).data('timer', setTimeout(function () {
      $(this.hash).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this.hash).data('timer'));  
  }
});

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

$('div').on({
  focusout: function () {
    $(this).data('timer', setTimeout(function () {
      $(this).removeClass('active');
    }.bind(this), 0));
  },
  focusin: function () {
    clearTimeout($(this).data('timer'));
  }
});

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


ESC kunci

Jika Anda berpikir Anda selesai dengan menangani keadaan fokus, ada lebih banyak yang dapat Anda lakukan untuk menyederhanakan pengalaman pengguna.

Ini sering menjadi fitur yang "bagus untuk dimiliki", tetapi itu umum bahwa ketika Anda memiliki modal atau popup apa pun yang ESC kunci akan menutupnya.

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

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

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

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


Jika Anda tahu Anda memiliki elemen yang dapat difokuskan dalam dialog, Anda tidak perlu memfokuskan dialog secara langsung. Jika Anda sedang membuat menu, Anda dapat memfokuskan item menu pertama sebagai gantinya.

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

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

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

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

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

.submenu__item {
  width: 150px;
}

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

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


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-ARIANamun saya sangat merekomendasikan pelaksana mengacu pada spesifikasi untuk rincian tentang peran apa yang harus mereka gunakan dan atribut lain yang sesuai.


184



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

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

127



Saya memiliki aplikasi yang bekerja mirip dengan contoh Eran, kecuali saya melampirkan 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 jQuery's one() fungsi


118



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

Bekerja untuk saya baik-baik saja.


32



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

Berikut ini terjadi ketika a clickoutside handler (WLOG) terikat pada elemen:

  • elemen ditambahkan ke larik yang menahan semua elemen clickoutside penangan
  • Sebuah (diberi nama) klik pawang terikat pada dokumen (jika belum ada)
  • pada apapun klik dalam dokumen, the clickoutside event dipicu untuk elemen-elemen dalam array yang tidak sama dengan atau induk dari klik-tujuan sasaran
  • Selain itu, event.target untuk clickoutside acara diatur ke elemen yang diklik pengguna (sehingga Anda bahkan tahu apa yang diklik pengguna, bukan hanya itu yang diklik di luar)

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


31



Ini bekerja untuk saya dengan sempurna !!

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

26



Setelah penelitian saya menemukan tiga solusi kerja (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>

24