วิธีการตรวจสอบการคลิกนอกองค์ประกอบ?
เหตุผลที่คำถามนี้เป็นที่นิยมและมีคำตอบมากมายนั่นคือมันซับซ้อนเกินไป หลังจากเกือบแปดปีและคำตอบหลายสิบฉันประหลาดใจอย่างแท้จริงเพื่อดูว่าได้รับการดูแลเล็กน้อยเพื่อการเข้าถึง
ฉันต้องการซ่อนองค์ประกอบเหล่านี้เมื่อผู้ใช้คลิกนอกพื้นที่ของเมนู
นี่คือสาเหตุอันสูงส่งและเป็นปัญหาที่แท้จริง ชื่อคำถาม - ซึ่งเป็นคำตอบที่ดูเหมือนจะพยายามตอบ - ประกอบด้วยปลาเฮอริ่งแดงที่โชคร้าย
คำแนะนำ: เป็นคำว่า"คลิก" !
คุณไม่ต้องการผูกตัวจัดการการคลิก
หากคุณผูกพันคลิกตัวจัดการเพื่อปิดกล่องโต้ตอบแสดงว่าคุณล้มเหลวแล้ว สาเหตุที่คุณล้มเหลวคือไม่ใช่ทุกคนที่ก่อให้click
เกิดเหตุการณ์ ผู้ใช้ที่ไม่ได้ใช้เมาส์จะสามารถหลบหนีกล่องโต้ตอบของคุณ (และเมนูป๊อปอัปของคุณนั้นเป็นประเภทกล่องโต้ตอบ) โดยการกดTabจากนั้นผู้ใช้จะไม่สามารถอ่านเนื้อหาที่อยู่ด้านหลังกล่องโต้ตอบได้click
เหตุการณ์ในภายหลัง
งั้นลองใช้คำถามใหม่อีกครั้ง
หนึ่งจะปิดกล่องโต้ตอบเมื่อผู้ใช้เสร็จสิ้นด้วยมันได้อย่างไร
นี่คือเป้าหมาย น่าเสียดายที่ตอนนี้เราต้องผูกuserisfinishedwiththedialog
เหตุการณ์และการผูกนั้นไม่ตรงไปตรงมา
ดังนั้นเราจะตรวจสอบได้อย่างไรว่าผู้ใช้เสร็จสิ้นการใช้กล่องโต้ตอบ?
focusout
เหตุการณ์
การเริ่มต้นที่ดีคือการพิจารณาว่าโฟกัสได้ออกจากกล่องโต้ตอบหรือไม่
คำแนะนำ: ระวังด้วยblur
เหตุการณ์blur
อย่าเผยแพร่ถ้าเหตุการณ์นั้นผูกพันกับเฟสเดือดดาล!
jQuery's focusout
จะทำได้ดี หากคุณไม่สามารถใช้ jQuery คุณสามารถใช้blur
ระหว่างขั้นตอนการดักจับ:
element.addEventListener('blur', ..., true);
// use capture: ^^^^
นอกจากนี้สำหรับการสนทนาหลาย ๆ ครั้งคุณจะต้องอนุญาตให้คอนเทนเนอร์ได้รับโฟกัส เพิ่มtabindex="-1"
เพื่อให้ไดอะล็อกได้รับการโฟกัสแบบไดนามิกโดยไม่รบกวนการไหลของการแท็บ
$('a').on('click', function () {
$(this.hash).toggleClass('active').focus();
});
$('div').on('focusout', function () {
$(this).removeClass('active');
});
div {
display: none;
}
.active {
display: block;
}
<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>
หากคุณเล่นกับตัวอย่างนั้นเป็นเวลานานกว่าหนึ่งนาทีคุณควรเริ่มเห็นปัญหาอย่างรวดเร็ว
อย่างแรกคือลิงค์ในกล่องโต้ตอบนั้นไม่สามารถคลิกได้ ความพยายามที่จะคลิกที่มันหรือแท็บนั้นจะนำไปสู่การปิดกล่องโต้ตอบก่อนที่จะเกิดการโต้ตอบ เนื่องจากการโฟกัสองค์ประกอบภายในจะทำให้เกิดfocusout
เหตุการณ์ก่อนเรียก afocusin
เหตุการณ์อีกครั้ง
การแก้ไขคือการจัดคิวการเปลี่ยนแปลงสถานะบนลูปเหตุการณ์ ซึ่งสามารถทำได้โดยการใช้setImmediate(...)
หรือเบราว์เซอร์ที่ไม่สนับสนุนsetTimeout(..., 0)
setImmediate
เมื่อเข้าคิวแล้วสามารถยกเลิกได้ในภายหลัง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>
ปัญหาที่สองคือกล่องโต้ตอบจะไม่ปิดลงเมื่อมีการกดลิงค์อีกครั้ง นี่เป็นเพราะไดอะล็อกสูญเสียการโฟกัสทริกเกอร์ลักษณะการปิดหลังจากที่การคลิกลิงค์ทริกเกอร์ไดอะล็อกเพื่อเปิดอีกครั้ง
คล้ายกับปัญหาก่อนหน้านี้รัฐโฟกัสจำเป็นต้องได้รับการจัดการ เนื่องจากการเปลี่ยนแปลงสถานะได้รับการจัดคิวแล้วมันเป็นเพียงเรื่องของการจัดการเหตุการณ์โฟกัสบนทริกเกอร์การโต้ตอบ:
สิ่งนี้ควรดูคุ้นเคย
$('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 สำคัญ
หากคุณคิดว่าคุณทำสำเร็จแล้วด้วยการจัดการสถานะโฟกัสคุณสามารถทำสิ่งต่างๆได้มากกว่าเพื่อให้ประสบการณ์ผู้ใช้ง่ายขึ้น
นี่มักจะเป็นคุณสมบัติ "ดีที่มี" แต่เป็นเรื่องปกติที่เมื่อคุณมีคำกริยาหรือป๊อปอัพของการเรียงลำดับใด ๆ ที่Escสำคัญจะปิดมัน
keydown: function (e) {
if (e.which === 27) {
$(this).removeClass('active');
e.preventDefault();
}
}
$('a').on('click', function () {
$(this.hash).toggleClass('active').focus();
});
$('div').on({
focusout: function () {
$(this).data('timer', setTimeout(function () {
$(this).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this).data('timer'));
},
keydown: function (e) {
if (e.which === 27) {
$(this).removeClass('active');
e.preventDefault();
}
}
});
$('a').on({
focusout: function () {
$(this.hash).data('timer', setTimeout(function () {
$(this.hash).removeClass('active');
}.bind(this), 0));
},
focusin: function () {
clearTimeout($(this.hash).data('timer'));
}
});
div {
display: none;
}
.active {
display: block;
}
<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>
หากคุณรู้ว่าคุณมีองค์ประกอบที่สามารถโฟกัสได้ภายในกล่องโต้ตอบคุณไม่จำเป็นต้องโฟกัสกล่องโต้ตอบโดยตรง หากคุณกำลังสร้างเมนูคุณสามารถเน้นรายการเมนูแรกแทน
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.
บทบาทของ WAI-ARIA และการสนับสนุนการเข้าถึงอื่น ๆ
คำตอบนี้หวังว่าจะครอบคลุมพื้นฐานของการรองรับแป้นพิมพ์และเมาส์ที่ใช้งานได้สำหรับคุณลักษณะนี้ แต่เนื่องจากมันมีขนาดค่อนข้างใหญ่อยู่แล้วฉันจะหลีกเลี่ยงการอภิปรายเกี่ยวกับบทบาทและคุณลักษณะของWAI-ARIAแต่ฉันขอแนะนำให้ผู้ใช้ เกี่ยวกับบทบาทที่ควรใช้และคุณลักษณะอื่น ๆ ที่เหมาะสม