การสร้างไดอะล็อกที่สามารถเข้าถึงได้ตั้งแต่เริ่มต้น
เผยแพร่แล้ว: 2022-03-10ก่อนอื่นอย่าทำที่บ้าน อย่าเขียนบทสนทนาหรือห้องสมุดของคุณเอง มีผลิตภัณฑ์มากมายที่ผ่านการทดสอบ ตรวจสอบ ใช้และนำกลับมาใช้ใหม่ และคุณควรเลือกใช้สิ่งเหล่านี้มากกว่าของคุณเอง a11y-dialog เป็นหนึ่งในนั้น แต่มีมากกว่านั้น (ระบุไว้ในตอนท้ายของบทความนี้)
ผมขอถือเอาโพสต์นี้เป็นโอกาสในการเตือนทุกท่านให้ ระมัดระวังในการใช้กล่องโต้ตอบ กำลังทดสอบเพื่อแก้ไขปัญหาการออกแบบทั้งหมดโดยเฉพาะบนอุปกรณ์เคลื่อนที่ แต่มักมีวิธีอื่นในการเอาชนะปัญหาการออกแบบ เรามักจะตกหลุมรักการใช้ไดอะล็อกอย่างรวดเร็ว ไม่ใช่เพราะจำเป็นต้องเป็นทางเลือกที่ถูกต้อง แต่เพราะว่าง่าย พวกเขาแยกปัญหาเรื่องอสังหาริมทรัพย์โดยการแลกเปลี่ยนเพื่อเปลี่ยนบริบท ซึ่งไม่ใช่การแลกเปลี่ยนที่ถูกต้องเสมอไป ประเด็นคือ: พิจารณาว่าไดอะล็อกเป็นรูปแบบการออกแบบที่ถูกต้องหรือไม่ก่อนใช้งาน
ในบทความนี้ เราจะเขียน ไลบรารี JavaScript ขนาดเล็กสำหรับสร้างไดอะล็อกที่สามารถเข้าถึงได้ ตั้งแต่เริ่มต้น (โดยพื้นฐานแล้วคือการสร้าง a11y-dialog) เป้าหมายคือการทำความเข้าใจกับสิ่งที่เข้าไป เราจะไม่จัดการกับสไตล์มากเกินไป แค่ส่วน JavaScript เราจะใช้ JavaScript ที่ทันสมัยเพื่อความเรียบง่าย (เช่น คลาสและฟังก์ชันลูกศร) แต่โปรดทราบว่าโค้ดนี้อาจไม่ทำงานในเบราว์เซอร์รุ่นเก่า
- การกำหนด API
- การสร้างอินสแตนซ์ของกล่องโต้ตอบ
- แสดงและซ่อน
- ปิดด้วยโอเวอร์เลย์
- ปิดด้วยการหลบหนี
- โฟกัสดักจับ
- รักษาโฟกัส
- ฟื้นฟูโฟกัส
- ให้ชื่อที่สามารถเข้าถึงได้
- การจัดการเหตุการณ์ที่กำหนดเอง
- การทำความสะอาด
- เอามารวมกัน
- ห่อ
การกำหนด API
อันดับแรก เราต้องการกำหนดวิธีที่เราจะใช้สคริปต์โต้ตอบของเรา เราจะทำให้มันง่ายที่สุดในการเริ่มต้น เราให้องค์ประกอบ HTML รูทสำหรับกล่องโต้ตอบของเรา และอินสแตนซ์ที่เราได้รับมีเมธอด .show(.. .show(..)
และ .hide(..)
class Dialog { constructor(element) {} show() {} hide() {} }
การสร้างอินสแตนซ์ของไดอะล็อก
สมมติว่าเรามี HTML ต่อไปนี้:
<div>This will be a dialog.</div>
และเรายกตัวอย่างบทสนทนาของเราดังนี้:
const element = document.querySelector('#my-dialog') const dialog = new Dialog(element)
มีบางสิ่งที่เราต้องทำภายใต้ประทุนเมื่อสร้างอินสแตนซ์:
- ซ่อนเพื่อให้ถูกซ่อนโดยค่าเริ่มต้น (
hidden
) - ทำเครื่องหมายว่าเป็นกล่องโต้ตอบสำหรับเทคโนโลยีอำนวยความสะดวก (
role="dialog"
) - ทำให้ส่วนที่เหลือของหน้าเฉื่อยเมื่อเปิด (
aria-modal="true"
)
constructor (element) { // Store a reference to the HTML element on the instance so it can be used // across methods. this.element = element this.element.setAttribute('hidden', true) this.element.setAttribute('role', 'dialog') this.element.setAttribute('aria-modal', true) }
โปรดทราบว่าเราสามารถเพิ่มแอตทริบิวต์ 3 เหล่านี้ใน HTML เริ่มต้นของเราโดยไม่จำเป็นต้องเพิ่มด้วย JavaScript แต่วิธีนี้ทำให้มองไม่เห็น สคริปต์ของเราทำให้แน่ใจว่าสิ่งต่างๆ จะทำงานตามที่ควรจะเป็น ไม่ว่าเราจะคิดจะเพิ่มคุณลักษณะทั้งหมดของเราหรือไม่ก็ตาม
แสดงและซ่อน
เรามีสองวิธี: วิธีหนึ่งเพื่อแสดงกล่องโต้ตอบและอีกวิธีหนึ่งเพื่อซ่อน วิธีการเหล่านี้ไม่ได้ช่วยอะไรมาก (ในตอนนี้) นอกจากการสลับแอตทริบิวต์ที่ hidden
ในองค์ประกอบรูท นอกจากนี้เรายังจะรักษาบูลีนบนอินสแตนซ์เพื่อให้สามารถประเมินได้อย่างรวดเร็วว่ากล่องโต้ตอบแสดงขึ้นหรือไม่ สิ่งนี้จะมีประโยชน์ในภายหลัง
show() { this.isShown = true this.element.removeAttribute('hidden') } hide() { this.isShown = false this.element.setAttribute('hidden', true) }
เพื่อหลีกเลี่ยงไม่ให้มองเห็นไดอะล็อกก่อนที่จาวาสคริปต์จะเริ่มทำงานและซ่อนโดยการเพิ่มแอตทริบิวต์ การเพิ่มการ hidden
ไปยังไดอะล็อกโดยตรงใน HTML ตั้งแต่เริ่มต้นอาจเป็นสิ่งที่น่าสนใจ
<div hidden>This will be a dialog.</div>
ปิดด้วยโอเวอร์เลย์
การคลิกนอกกล่องโต้ตอบควรปิด มีหลายวิธีในการทำเช่นนั้น วิธีหนึ่งอาจเป็นได้คือการฟังเหตุการณ์การคลิกทั้งหมดบนหน้าเว็บและกรองเหตุการณ์ที่เกิดขึ้นภายในกล่องโต้ตอบออก แต่ก็ค่อนข้างซับซ้อนที่ต้องทำ
อีกวิธีหนึ่งคือการฟังเหตุการณ์การคลิกบนโอเวอร์เลย์ (บางครั้งเรียกว่า “ฉากหลัง”) โอเวอร์เลย์เองก็สามารถทำได้ง่ายๆ เหมือนกับ <div>
ที่มีสไตล์บางอย่าง
ดังนั้นเมื่อเปิดกล่องโต้ตอบ เราจำเป็นต้องผูกเหตุการณ์การคลิกบนโอเวอร์เลย์ เราสามารถให้ ID หรือคลาสที่แน่นอนเพื่อให้สามารถสืบค้นได้ หรือเราอาจให้แอตทริบิวต์ข้อมูลแก่มัน ฉันมักจะชอบสิ่งเหล่านี้สำหรับตะขอเกี่ยวกับพฤติกรรม มาแก้ไข HTML ของเราตามลำดับ:
<div hidden> <div data-dialog-hide></div> <div>This will be a dialog.</div> </div>
ตอนนี้ เราสามารถสืบค้นองค์ประกอบด้วยแอตทริบิวต์ data-dialog-hide
ภายในไดอะล็อก และให้ฟังการคลิกที่ซ่อนไดอะล็อก
constructor (element) { // … rest of the code // Bind our methods so they can be used in event listeners without losing the // reference to the dialog instance this._show = this.show.bind(this) this._hide = this.hide.bind(this) const closers = [...this.element.querySelectorAll('[data-dialog-hide]')] closers.forEach(closer => closer.addEventListener('click', this._hide)) }
สิ่งที่ดีเกี่ยวกับการมีบางสิ่งที่ค่อนข้างทั่วไปแบบนี้คือ เราสามารถใช้สิ่งเดียวกันนี้กับปุ่มปิดของกล่องโต้ตอบได้เช่นกัน
<div hidden> <div data-dialog-hide></div> <div> This will be a dialog. <button type="button" data-dialog-hide>Close</button> </div> </div>
ปิดด้วยการหลบหนี
ไม่เพียงแต่กล่องโต้ตอบจะถูกซ่อนเมื่อคลิกภายนอกเท่านั้น แต่ควรซ่อนกล่องโต้ตอบเมื่อกด Esc ด้วย เมื่อเปิดกล่องโต้ตอบ เราสามารถผูกฟังคีย์บอร์ดกับเอกสาร และนำออกเมื่อปิด วิธีนี้จะฟังเฉพาะการกดปุ่มในขณะที่กล่องโต้ตอบเปิดอยู่แทนที่จะเปิดตลอดเวลา
show() { // … rest of the code // Note: `_handleKeyDown` is the bound method, like we did for `_show`/`_hide` document.addEventListener('keydown', this._handleKeyDown) } hide() { // … rest of the code // Note: `_handleKeyDown` is the bound method, like we did for `_show`/`_hide` document.removeEventListener('keydown', this._handleKeyDown) } handleKeyDown(event) { if (event.key === 'Escape') this.hide() }
โฟกัสดักจับ
ตอนนี้เป็นสิ่งที่ดี การจับโฟกัสภายในบทสนทนานั้นเป็นแก่นแท้ของเรื่องทั้งหมด และจะต้องเป็นส่วนที่ซับซ้อนที่สุด (แม้ว่าอาจจะไม่ซับซ้อนอย่างที่คุณคิด)
แนวคิดนี้ค่อนข้างเรียบง่าย: เมื่อกล่องโต้ตอบเปิดขึ้น เราจะฟังการกด Tab หากกด Tab บนองค์ประกอบที่โฟกัสได้สุดท้ายของกล่องโต้ตอบ เราจะย้ายโฟกัสไปที่องค์ประกอบแรกโดยทางโปรแกรม หากกด Shift + Tab บนองค์ประกอบแรกที่โฟกัสได้ของกล่องโต้ตอบ เราจะย้ายไปยังองค์ประกอบสุดท้าย
ฟังก์ชันอาจมีลักษณะดังนี้:
function trapTabKey(node, event) { const focusableChildren = getFocusableChildren(node) const focusedItemIndex = focusableChildren.indexOf(document.activeElement) const lastIndex = focusableChildren.length - 1 const withShift = event.shiftKey if (withShift && focusedItemIndex === 0) { focusableChildren[lastIndex].focus() event.preventDefault() } else if (!withShift && focusedItemIndex === lastIndex) { focusableChildren[0].focus() event.preventDefault() } }
สิ่งต่อไปที่เราต้องหาคือวิธีรับองค์ประกอบที่โฟกัสได้ทั้งหมดของกล่องโต้ตอบ ( getFocusableChildren
) เราจำเป็นต้องค้นหาองค์ประกอบทั้งหมดที่สามารถโฟกัสได้ในทางทฤษฎี จากนั้นเราต้องแน่ใจว่าองค์ประกอบเหล่านั้นมีประสิทธิผล
ส่วนแรกสามารถทำได้ด้วยตัวเลือกที่โฟกัสได้ มันเป็นแพ็คเกจเล็ก ๆ ที่ฉันเขียนซึ่งมีตัวเลือกมากมาย:
module.exports = [ 'a[href]:not([tabindex^="-"])', 'area[href]:not([tabindex^="-"])', 'input:not([type="hidden"]):not([type="radio"]):not([disabled]):not([tabindex^="-"])', 'input[type="radio"]:not([disabled]):not([tabindex^="-"]):checked', 'select:not([disabled]):not([tabindex^="-"])', 'textarea:not([disabled]):not([tabindex^="-"])', 'button:not([disabled]):not([tabindex^="-"])', 'iframe:not([tabindex^="-"])', 'audio[controls]:not([tabindex^="-"])', 'video[controls]:not([tabindex^="-"])', '[contenteditable]:not([tabindex^="-"])', '[tabindex]:not([tabindex^="-"])', ]
และนั่นก็เพียงพอแล้วที่จะพาคุณไปถึงที่นั่น 99% เราสามารถใช้ตัวเลือกเหล่านี้เพื่อค้นหาองค์ประกอบที่โฟกัสได้ทั้งหมด จากนั้นจึงตรวจสอบองค์ประกอบทั้งหมดเพื่อให้แน่ใจว่ามองเห็นได้บนหน้าจอจริง (และไม่ได้ซ่อนหรือบางอย่าง)
import focusableSelectors from 'focusable-selectors' function isVisible(element) { return element => element.offsetWidth || element.offsetHeight || element.getClientRects().length } function getFocusableChildren(root) { const elements = [...root.querySelectorAll(focusableSelectors.join(','))] return elements.filter(isVisible) }
ตอนนี้เราสามารถอัปเดตวิธี handleKeyDown
ของเราได้แล้ว:
handleKeyDown(event) { if (event.key === 'Escape') this.hide() else if (event.key === 'Tab') trapTabKey(this.element, event) }
รักษาโฟกัส
สิ่งหนึ่งที่มักถูกมองข้ามเมื่อสร้างกล่องโต้ตอบที่เข้าถึงได้คือการทำให้แน่ใจว่าโฟกัสยังคงอยู่ภายในกล่องโต้ตอบ แม้ว่า หน้าจะสูญเสียโฟกัสไปแล้ว ลองคิดแบบนี้: จะเกิดอะไรขึ้นหากกล่องโต้ตอบเปิดขึ้น เราเน้นที่แถบ URL ของเบราว์เซอร์ แล้วเริ่มแท็บอีกครั้ง กับดักโฟกัสของเราจะไม่ทำงาน เนื่องจากจะรักษาโฟกัสไว้ภายในกล่องโต้ตอบเมื่ออยู่ภายในกล่องโต้ตอบเพื่อเริ่มต้นเท่านั้น
ในการแก้ไขปัญหานั้น เราสามารถผูกผู้ฟังโฟกัสกับองค์ประกอบ <body>
เมื่อกล่องโต้ตอบปรากฏขึ้น และย้ายโฟกัสไปยังองค์ประกอบแรกที่โฟกัสได้ภายในกล่องโต้ตอบ
show () { // … rest of the code // Note: `_maintainFocus` is the bound method, like we did for `_show`/`_hide` document.body.addEventListener('focus', this._maintainFocus, true) } hide () { // … rest of the code // Note: `_maintainFocus` is the bound method, like we did for `_show`/`_hide` document.body.removeEventListener('focus', this._maintainFocus, true) } maintainFocus(event) { const isInDialog = event.target.closest('[aria-modal="true"]') if (!isInDialog) this.moveFocusIn() } moveFocusIn () { const target = this.element.querySelector('[autofocus]') || getFocusableChildren(this.element)[0] if (target) target.focus() }
องค์ประกอบใดที่ควรโฟกัสเมื่อเปิดกล่องโต้ตอบ และอาจขึ้นอยู่กับประเภทของเนื้อหาที่กล่องโต้ตอบจะแสดง โดยทั่วไป มีสองตัวเลือก:
- เน้นองค์ประกอบแรก
นี่คือสิ่งที่เราทำ เนื่องจากทำให้ง่ายขึ้นโดยที่เรามีฟังก์ชันgetFocusableChildren
อยู่แล้ว - โฟกัสที่ปุ่มปิด
นี่เป็นวิธีแก้ปัญหาที่ดี โดยเฉพาะอย่างยิ่งหากปุ่มอยู่ในตำแหน่งที่สัมพันธ์กับกล่องโต้ตอบ เราสามารถทำให้สิ่งนี้เกิดขึ้นได้โดยสะดวกโดยการวางปุ่มปิดเป็นองค์ประกอบแรกของกล่องโต้ตอบ หากปุ่มปิดอยู่ในโฟลว์ของเนื้อหากล่องโต้ตอบ ในตอนท้าย อาจเป็นปัญหาได้หากกล่องโต้ตอบมีเนื้อหาจำนวนมาก (ดังนั้นจึงสามารถเลื่อนได้) เนื่องจากปุ่มจะเลื่อนเนื้อหาไปจนสุดเมื่อเปิดอยู่ - เน้นกล่องโต้ตอบเอง
สิ่งนี้ไม่ธรรมดามากในไลบรารีไดอะล็อก แต่ก็ควรใช้งานได้ (แม้ว่าจะต้องเพิ่มtabindex="-1"
เข้าไปเพื่อให้เป็นไปได้เนื่องจากองค์ประกอบ<div>
ไม่สามารถโฟกัสได้โดยค่าเริ่มต้น)
โปรดทราบว่าเราตรวจสอบว่ามีองค์ประกอบที่มีแอตทริบิวต์ autofocus
HTML ภายในกล่องโต้ตอบหรือไม่ ซึ่งในกรณีนี้ เราจะย้ายโฟกัสไปที่องค์ประกอบนั้นแทนรายการแรก
ฟื้นฟูโฟกัส
เราสามารถดักจับโฟกัสภายในกล่องโต้ตอบได้สำเร็จ แต่เราลืมย้ายโฟกัสภายในกล่องโต้ตอบเมื่อเปิดขึ้น ในทำนองเดียวกัน เราจำเป็นต้องกู้คืนโฟกัสกลับไปที่องค์ประกอบที่มีอยู่ก่อนที่กล่องโต้ตอบจะเปิดขึ้น
เมื่อแสดงไดอะล็อก เราสามารถเริ่มต้นด้วยการอ้างอิงถึงองค์ประกอบที่มีโฟกัส ( document.activeElement
) โดยส่วนใหญ่ ปุ่มนี้จะเป็นปุ่มที่โต้ตอบด้วยเพื่อเปิดกล่องโต้ตอบ แต่ในกรณีที่เปิดกล่องโต้ตอบโดยทางโปรแกรม ซึ่งพบไม่บ่อยนัก อาจเป็นอย่างอื่น
show() { this.previouslyFocused = document.activeElement // … rest of the code this.moveFocusIn() }
เมื่อซ่อนกล่องโต้ตอบ เราสามารถย้ายโฟกัสกลับไปที่องค์ประกอบนั้นได้ เราป้องกันด้วยเงื่อนไขเพื่อหลีกเลี่ยงข้อผิดพลาด JavaScript หากองค์ประกอบนั้นไม่มีอยู่แล้ว (หรือถ้าเป็น SVG):
hide() { // … rest of the code if (this.previouslyFocused && this.previouslyFocused.focus) { this.previouslyFocused.focus() } }
ให้ชื่อที่สามารถเข้าถึงได้
เป็นสิ่งสำคัญที่กล่องโต้ตอบของเราจะต้องมีชื่อที่สามารถเข้าถึงได้ ซึ่งจะแสดงรายการในแผนผังการช่วยสำหรับการเข้าถึง มีสองวิธีในการแก้ไขปัญหา หนึ่งในนั้นคือการกำหนดชื่อในแอตทริบิวต์ aria- aria-label
แต่ aria-label
มีปัญหา
อีกวิธีหนึ่งคือการมีชื่อในกล่องโต้ตอบของเรา (ไม่ว่าจะซ่อนหรือไม่ก็ตาม) และเชื่อมโยงกล่องโต้ตอบของเรากับแอตทริบิวต์ aria-labelledby
อาจมีลักษณะดังนี้:
<div hidden aria-labelledby="my-dialog-title"> <div data-dialog-hide></div> <div> <h1>My dialog title</h1> This will be a dialog. <button type="button" data-dialog-hide>Close</button> </div> </div>
ฉันเดาว่าเราสามารถทำให้สคริปต์ของเราใช้แอตทริบิวต์นี้แบบไดนามิกโดยอิงจากการมีอยู่ของชื่อและอะไรก็ตาม แต่ฉันจะบอกว่าสิ่งนี้แก้ไขได้ง่ายๆ ด้วยการเขียน HTML ที่เหมาะสมในตอนแรก ไม่จำเป็นต้องเพิ่ม JavaScript สำหรับสิ่งนั้น
การจัดการเหตุการณ์ที่กำหนดเอง
จะเป็นอย่างไรถ้าเราต้องการตอบสนองต่อกล่องโต้ตอบที่เปิดอยู่ หรือปิด? ขณะนี้ยังไม่มีวิธีที่จะทำ แต่การเพิ่มระบบเหตุการณ์ขนาดเล็กไม่ควรยากเกินไป เราต้องการฟังก์ชันในการลงทะเบียนเหตุการณ์ (เรียก .on(..)
) และฟังก์ชันเพื่อยกเลิกการลงทะเบียน ( .off(..)
)
class Dialog { constructor(element) { this.events = { show: [], hide: [] } } on(type, fn) { this.events[type].push(fn) } off(type, fn) { const index = this.events[type].indexOf(fn) if (index > -1) this.events[type].splice(index, 1) } }
จากนั้นเมื่อแสดงและซ่อนเมธอด เราจะเรียกฟังก์ชันทั้งหมดที่ลงทะเบียนไว้สำหรับเหตุการณ์นั้นโดยเฉพาะ
class Dialog { show() { // … rest of the code this.events.show.forEach(event => event()) } hide() { // … rest of the code this.events.hide.forEach(event => event()) } }
การทำความสะอาด
เราอาจต้องการให้วิธีการล้างกล่องโต้ตอบในกรณีที่เราใช้งานเสร็จแล้ว จะต้องรับผิดชอบในการยกเลิกการลงทะเบียนผู้ฟังเหตุการณ์เพื่อไม่ให้นานกว่าที่ควร
class Dialog { destroy() { const closers = [...this.element.querySelectorAll('[data-dialog-hide]')] closers.forEach(closer => closer.removeEventListener('click', this._hide)) this.events.show.forEach(event => this.off('show', event)) this.events.hide.forEach(event => this.off('hide', event)) } }
รวมทุกอย่างไว้ด้วยกัน
import focusableSelectors from 'focusable-selectors' class Dialog { constructor(element) { this.element = element this.events = { show: [], hide: [] } this._show = this.show.bind(this) this._hide = this.hide.bind(this) this._maintainFocus = this.maintainFocus.bind(this) this._handleKeyDown = this.handleKeyDown.bind(this) element.setAttribute('hidden', true) element.setAttribute('role', 'dialog') element.setAttribute('aria-modal', true) const closers = [...element.querySelectorAll('[data-dialog-hide]')] closers.forEach(closer => closer.addEventListener('click', this._hide)) } show() { this.isShown = true this.previouslyFocused = document.activeElement this.element.removeAttribute('hidden') this.moveFocusIn() document.addEventListener('keydown', this._handleKeyDown) document.body.addEventListener('focus', this._maintainFocus, true) this.events.show.forEach(event => event()) } hide() { if (this.previouslyFocused && this.previouslyFocused.focus) { this.previouslyFocused.focus() } this.isShown = false this.element.setAttribute('hidden', true) document.removeEventListener('keydown', this._handleKeyDown) document.body.removeEventListener('focus', this._maintainFocus, true) this.events.hide.forEach(event => event()) } destroy() { const closers = [...this.element.querySelectorAll('[data-dialog-hide]')] closers.forEach(closer => closer.removeEventListener('click', this._hide)) this.events.show.forEach(event => this.off('show', event)) this.events.hide.forEach(event => this.off('hide', event)) } on(type, fn) { this.events[type].push(fn) } off(type, fn) { const index = this.events[type].indexOf(fn) if (index > -1) this.events[type].splice(index, 1) } handleKeyDown(event) { if (event.key === 'Escape') this.hide() else if (event.key === 'Tab') trapTabKey(this.element, event) } moveFocusIn() { const target = this.element.querySelector('[autofocus]') || getFocusableChildren(this.element)[0] if (target) target.focus() } maintainFocus(event) { const isInDialog = event.target.closest('[aria-modal="true"]') if (!isInDialog) this.moveFocusIn() } } function trapTabKey(node, event) { const focusableChildren = getFocusableChildren(node) const focusedItemIndex = focusableChildren.indexOf(document.activeElement) const lastIndex = focusableChildren.length - 1 const withShift = event.shiftKey if (withShift && focusedItemIndex === 0) { focusableChildren[lastIndex].focus() event.preventDefault() } else if (!withShift && focusedItemIndex === lastIndex) { focusableChildren[0].focus() event.preventDefault() } } function isVisible(element) { return element => element.offsetWidth || element.offsetHeight || element.getClientRects().length } function getFocusableChildren(root) { const elements = [...root.querySelectorAll(focusableSelectors.join(','))] return elements.filter(isVisible) }
ห่อ
นั่นเป็นสิ่งที่ค่อนข้าง แต่ในที่สุดเราก็ไปถึงที่นั่น! เป็นอีกครั้งที่ฉันไม่แนะนำให้เปิดไลบรารีไดอะล็อกของคุณเอง เนื่องจากไม่ใช่ไลบรารีที่ตรงไปตรงมาที่สุด และข้อผิดพลาดอาจเป็นปัญหาอย่างมากสำหรับผู้ใช้เทคโนโลยีสิ่งอำนวยความสะดวก แต่อย่างน้อยตอนนี้คุณก็รู้ว่ามันทำงานอย่างไรภายใต้ประทุน!
หากคุณต้องการใช้ไดอะล็อกในโครงการของคุณ ให้ลองใช้หนึ่งในวิธีแก้ไขปัญหาต่อไปนี้ (ช่วยเตือนว่าเรามีรายการส่วนประกอบที่เข้าถึงได้ทั้งหมดของเราด้วย):
- การใช้งาน Vanilla JavaScript: a11y-dialog โดยคุณอย่างแท้จริง หรือ aria-modal-dialog โดย Scott O'Hara
- การใช้งาน React: react-a11y-dialog โดยคุณอีกครั้ง เข้าถึง/โต้ตอบจากกรอบงาน Reach หรือ @react-aria/dialog จาก Adobe คุณอาจสนใจในการเปรียบเทียบ 3 ห้องสมุดนี้
- การใช้งาน Vue: vue-a11y-dialog โดย Moritz Kroger, a11y-vue-dialog โดย Renato de Leao
ต่อไปนี้คือสิ่งที่สามารถเพิ่มได้ แต่ไม่ใช่เพื่อความเรียบง่าย:
- รองรับการโต้ตอบการแจ้งเตือนผ่านบทบาท
alertdialog
อ้างถึงเอกสารประกอบ a11y-dialog ในกล่องโต้ตอบการแจ้งเตือน - ล็อคความสามารถในการเลื่อนในขณะที่เปิดกล่องโต้ตอบ อ้างถึงเอกสารประกอบ a11y-dialog เกี่ยวกับการล็อคการเลื่อน
- รองรับองค์ประกอบ HTML
<dialog>
เนทีฟเนื่องจากเป็นพาร์และไม่สอดคล้องกัน อ้างถึงเอกสารประกอบ a11y-dialog เกี่ยวกับองค์ประกอบการโต้ตอบและงานชิ้นนี้โดย Scott O'hara สำหรับข้อมูลเพิ่มเติมเกี่ยวกับสาเหตุที่ไม่คุ้มกับปัญหา - รองรับการซ้อนไดอะล็อกเพราะเป็นที่น่าสงสัย อ้างถึงเอกสารประกอบ a11y-dialog บนไดอะล็อกที่ซ้อนกัน
- ข้อควรพิจารณาในการปิดกล่องโต้ตอบในการนำทางของเบราว์เซอร์ ในบางกรณี การปิดกล่องโต้ตอบเมื่อกดปุ่มย้อนกลับของเบราว์เซอร์อาจเป็นเรื่องที่สมเหตุสมผล