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 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 suatu click
acara.
Jadi mari kita ulangi pertanyaannya.
Bagaimana cara menutup dialog ketika pengguna selesai dengan itu?
Inilah tujuannya. Sayangnya, sekarang kita perlu mengikat userisfinishedwiththedialog
acara, 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 blur
acara tersebut, blur
jangan menyebar jika acara itu terikat ke fase gelembung!
jQuery focusout
akan baik-baik saja. Jika Anda tidak dapat menggunakan jQuery, maka Anda dapat menggunakan blur
selama 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 focusout
peristiwa sebelum memicu suatu focusin
peristiwa 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'));
}
});
$('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 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'));
}
});
$('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 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();
}
}
$('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 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();
}
$('.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-ARIA , namun saya sangat merekomendasikan agar pelaksana merujuk pada spesifikasi untuk perinciannya. tentang peran apa yang harus mereka gunakan dan atribut lain yang sesuai.