ใช้ Invoker Commands API เพื่อสลับระดับการเข้าถึงขององค์ประกอบ <dialog> และ [popover] จากปุ่ม HTML โดยตรง ซึ่งช่วยลดความจำเป็นในการใช้ Listener เหตุการณ์ JavaScript ที่กำหนดเอง
เมื่อใช้แอตทริบิวต์ commandfor (รหัสเป้าหมาย) และ command (การดำเนินการ) กับ <button> เบราว์เซอร์จะจัดการการเปลี่ยนแปลงสถานะเปิด/ปิด การจัดการโฟกัส และการเชื่อมโยงการช่วยเหลือพิเศษ (เช่น aria-expanded) โดยอัตโนมัติ เราขอแนะนำให้ใช้วิธีการประกาศนี้เนื่องจากจะช่วยนำโค้ด Boilerplate ที่เปราะบางออก ช่วยให้มั่นใจได้ว่าการโต้ตอบจะทำงานได้ทันทีเมื่อแยกวิเคราะห์ HTML และรับประกันประสบการณ์ของผู้ใช้ที่มีประสิทธิภาพและเข้าถึงได้โดยค่าเริ่มต้น
การใช้ป๊อปโอเวอร์แบบประกาศ
คุณสามารถเปิดและปิดป๊อปโอเวอร์ได้โดยใช้ปุ่มเดียว
<!-- MANDATORY: The commandfor attribute links the invoker to the ID of the target element so the browser knows what to control. -->
<!-- MANDATORY: The command attribute specifies the action to perform. Use 'toggle-popover' to handle both open and close states automatically. -->
<button commandfor="my-popover" command="toggle-popover">
Toggle Popover
</button>
<!-- MANDATORY: The target element must have the popover attribute to be controlled as a popover. -->
<div id="my-popover" popover>
<p>Popover content goes here.</p>
</div>
หากต้องการควบคุมการเปิดและปิดด้วยปุ่มแยกกัน คุณสามารถใช้คำสั่ง show-popover และ hide-popover ได้
<!-- MANDATORY: Use 'show-popover' to explicitly open the popover. It will not close the popover if clicked again. -->
<button commandfor="my-explicit-popover" command="show-popover">
Show Popover
</button>
<div id="my-explicit-popover" popover="manual">
<p>This popover is explicitly opened and closed by separate buttons.</p>
<!-- MANDATORY: Use 'hide-popover' to explicitly close the targeted popover. -->
<button commandfor="my-explicit-popover" command="hide-popover">
Hide Popover
</button>
</div>
การใช้กล่องโต้ตอบโมดัลแบบประกาศ
กล่องโต้ตอบโมดัลมักจะใช้ปุ่มแยกต่างหากสำหรับการเปิดและปิด ซึ่งต่างจากป๊อปโอเวอร์ ใช้คำสั่ง show-modal โดยเฉพาะเมื่อคุณต้องการเปิดกล่องโต้ตอบเป็นโมดัล
<!-- MANDATORY: Use command="show-modal" to trigger the dialog as a modal, trapping focus and preventing interaction with the rest of the page. -->
<!-- MANDATORY: The commandfor attribute connects this button to the dialog ID. -->
<button commandfor="confirm-dialog" command="show-modal">
Open Confirmation
</button>
<dialog id="confirm-dialog">
<p>Are you sure you want to proceed?</p>
<!-- MANDATORY: Use command="close" to dismiss the dialog safely. -->
<button commandfor="confirm-dialog" command="close">
Cancel
</button>
</dialog>
กลยุทธ์สำรอง
เนื่องจากคำสั่ง Invoker และป๊อปโอเวอร์ยังไม่ได้รับการรองรับอย่างแพร่หลาย คุณจึงต้องใช้ Polyfill เป็นทางเลือกสำรองสำหรับเบราว์เซอร์รุ่นเก่า
คำสั่ง Invoker ของ Polyfill
บังคับ: ตรวจหาการรองรับฟีเจอร์โดยตรวจสอบพร็อพเพอร์ตี้ commandForElement ในต้นแบบ HTMLButtonElement อย่าตรวจสอบออบเจ็กต์ window หรือ document คุณต้องนำเข้า Polyfill แบบไดนามิกเฉพาะเมื่อไม่มีฟีเจอร์เนทีฟเท่านั้น อย่าโหลด Polyfill โดยไม่มีเงื่อนไข
บังคับ: ฟังเหตุการณ์ "command" โดยตรงในองค์ประกอบเป้าหมาย เนื่องจากเหตุการณ์ "command" แบบเนทีฟไม่แสดงเป็นบับเบิล
ตัวเลือกที่ 1: การใช้ Bundler
ติดตั้ง Polyfill ผ่าน npm (npm install invokers-polyfill) วิธีนี้เหมาะสำหรับโปรเจ็กต์ที่ใช้ Bundler (เช่น Vite หรือ Webpack) หรือแผนที่การนำเข้า สำหรับการตั้งค่าอื่นๆ ทั้งหมด ให้ใช้ตัวเลือก CDN ด้านล่าง
// MANDATORY: Feature detect 'commandForElement' on HTMLButtonElement.prototype.
// Conditionally load the invokers-polyfill only in browsers lacking native support.
if (!('commandForElement' in HTMLButtonElement.prototype)) {
import('invokers-polyfill');
}
ตัวเลือกที่ 2: การใช้ CDN
สำหรับโปรเจ็กต์ที่ไม่มี Bundler ให้นำเข้า Polyfill แบบไดนามิกจาก CDN โดยตรงภายใน <script type="module">
<script type="module">
// MANDATORY: Feature detect 'commandForElement' on HTMLButtonElement.prototype.
// Conditionally load the invokers-polyfill from a CDN only in browsers lacking native support.
if (!('commandForElement' in HTMLButtonElement.prototype)) {
import('https://esm.run/invokers-polyfill');
}
</script>
ข้อจำกัดของ Polyfill สำหรับ Invoker
บังคับ: Polyfill นี้ไม่จัดการสถานะ ARIA (เช่น aria-expanded) ของปุ่มคำสั่งเหมือนที่เบราว์เซอร์แบบเนทีฟทำ เราขอแนะนำอย่างยิ่งให้คุณจัดการสถานะเหล่านี้ด้วยตนเองเพื่อให้มั่นใจว่าเว็บไซต์ของคุณเข้าถึงได้อย่างเต็มรูปแบบ
หากระบบไม่รองรับ Invoker Commands API เหตุการณ์ command จะไม่เริ่มทำงาน เพื่อให้รองรับเบราว์เซอร์ที่ทันสมัยทั้งหมดได้อย่างเต็มรูปแบบ เราขอแนะนำให้ใช้ invokers-polyfill จาก https://github.com/keithamus/invokers-polyfill ผ่าน npm install หรือ CDN
Polyfill นี้รองรับการกระทำที่กำหนดเองอย่างเต็มรูปแบบ (เริ่มต้นด้วย --) และส่งเหตุการณ์ command เหมือนกับ API แบบเนทีฟทุกประการ
การนำเข้าแบบไดนามิก (การเพิ่มประสิทธิภาพ)
เพื่อประสิทธิภาพที่ดีที่สุด คุณควรโหลด Polyfill เฉพาะในกรณีที่เบราว์เซอร์ไม่รองรับ API ในตัว ซึ่งจะช่วยประหยัดแบนด์วิดท์และลดเวลาในการเรียกใช้สคริปต์สำหรับผู้ใช้ในเบราว์เซอร์ที่ทันสมัย
หมายเหตุ: Polyfill นี้ไม่ได้จัดการสถานะ ARIA (เช่น aria-pressed หรือ aria-expanded) สำหรับคำสั่งที่กำหนดเอง คุณต้องซิงค์สถานะเหล่านี้ด้วยตนเองใน Listener เหตุการณ์เพื่อให้มั่นใจว่าเว็บไซต์ของคุณเข้าถึงได้
// 1. Conditionally load the polyfill
const hasNativeSupport = 'commandForElement' in HTMLButtonElement.prototype;
if (!hasNativeSupport) {
// Wrap in an async IIFE to avoid top-level await issues in older browsers
(async () => {
try {
await import('https://esm.run/invokers-polyfill');
} catch (err) {
console.error('Error loading fallback:', err);
}
})();
}
// 2. Manually manage ARIA states in your listener
document.getElementById('action-target').addEventListener('command', (event) => {
const command = event.command;
const target = event.target;
const source = event.source; // The button that triggered the command
if (command === '--spin') {
const isSpun = target.classList.toggle('is-spun');
// Polyfill tip: Manually update ARIA to match the new state
source?.setAttribute('aria-pressed', isSpun);
}
});
การใช้โฆษณาสำรองด้วยตนเอง (รูปแบบดั้งเดิม)
หากไม่ต้องการใช้ Polyfill คุณสามารถใช้การมอบสิทธิ์เหตุการณ์เพื่อส่งเหตุการณ์และรีจิสทรีคำสั่งเพื่อจัดการการดำเนินการร่วมกันได้ นี่เป็นรูปแบบสถาปัตยกรรมที่พบบ่อยในการพัฒนา JavaScript แบบดั้งเดิมซึ่งยังคงมีประสิทธิภาพสูงและปรับขนาดได้
// 1. **Optional:** Define a registry of requested actions for cleaner logic
const commandRegistry = {
'--spin': (target) => target.classList.toggle('is-spun'),
'--grow': (target) => target.classList.toggle('is-grown'),
'--reset': (target) => target.classList.remove('is-spun', 'is-grown'),
};
// 2. If CommandEvent doesn't exist, we assume no native support and provide the fallback
if (!globalThis.CommandEvent) {
globalThis.CommandEvent = class CommandEvent extends Event {
constructor(type, { source, command, ...options } = {}) {
super(type, options);
this.source = source;
this.command = command;
}
}
}
// 3. The fallback: Dispatch events manually if native support is missing
document.addEventListener('click', (event) => {
const button = event.composedPath().find((el) => el.matches?.("button[commandfor]"));
if (!button) return;
const target = document.getElementById(button.getAttribute('commandfor'));
const command = button.getAttribute('command');
if (target && command) {
target.dispatchEvent(new CommandEvent('command', {
command,
source: button,
}));
}
});
// 4. **Mandatory:** Register the unified listener directly on the target element
document.getElementById('action-target').addEventListener('command', (event) => {
const command = event.command;
const target = event.target;
const action = commandRegistry[command];
if (action) {
action(target);
}
});
การรองรับเบราว์เซอร์และทางเลือกสำรองสำหรับ Popover
Popover API ส่วนใหญ่เป็นการเพิ่มประสิทธิภาพแบบต่อเนื่อง แต่ลักษณะการทำงานที่กำหนดไว้ เช่น การโปรโมตเลเยอร์บนสุด การปิดเมื่อคลิกนอกองค์ประกอบ และการเรียกใช้ popovertarget ไม่มี CSS ที่เทียบเท่า เบราว์เซอร์รุ่นเก่าต้องใช้ Polyfill หรือการสำรองข้อมูลด้วยตนเองหากคุณไม่ต้องการจัดส่ง
Polyfill: หากต้องการรองรับแอตทริบิวต์ popover ในเบราว์เซอร์รุ่นเก่า ให้โหลด @oddbird/popover-polyfill แบบมีเงื่อนไข จำเป็น: ตรวจหาฟีเจอร์โดยการตรวจสอบพร็อพเพอร์ตี้ popover ใน HTMLElement.prototype และโหลด Polyfill เฉพาะเมื่อไม่มีการรองรับแบบเนทีฟ อย่าโหลดโดยไม่มีเงื่อนไข
เมื่อใช้ Bundler หรือแผนที่การนำเข้า ให้ทำดังนี้
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
หากไม่มี Bundler ให้นำเข้าจาก CDN ภายใน <script type="module">:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
ข้อควรระวังเกี่ยวกับรูปแบบ: Polyfill ไม่สามารถกำหนดคลาสจำลอง :popover-open จริงได้ จึงใช้คลาส .\:popover-open แทน จำเป็น: รวมทั้ง 2 รายการเข้าด้วยกันโดยใช้ :is() หรือ :where() ไม่เช่นนั้นเบราว์เซอร์ที่ไม่มี :popover-open จะทิ้งกฎทั้งหมด
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
หรือสำหรับทางเลือกสำรองแบบเดิมที่ไม่มี Polyfill ให้ใช้ position: fixed และคำนวณพิกัดด้วยตนเองผ่าน getBoundingClientRect() หรือใช้การกำหนดตำแหน่งเริ่มต้นด้วย inset: auto หากยอมรับได้สำหรับกรณีการใช้งาน