نمای کلی
از عنصر بومی <dialog> برای کادرهای گفتگوی مدال و غیرمدال استفاده کنید. این عنصر معناشناسی دسترسپذیری، مدیریت تمرکز، رندرینگ لایه بالایی، و مدیریت Esc را ارائه میدهد که همپوشانیهای سفارشی باید بهصورت دستی بازسازی کنند.
نوع همپوشانی را انتخاب کنید
اولین قانون منطبق را انتخاب کنید. اگر مطمئن نیستید، از «قانون ۱» استفاده کنید.
قانون ۱: همپوشانی باعث وقفه در کار کاربر میشود و صفحه را مسدود میکند (پیشفرض)
از این دکمه برای تأیید، فرم، ورود به سیستم، کنشهای مخرب، و هر درخواست «مدال»، «کادر گفتگو»، «لایتباکس»، یا «بالاپر» که خلاف آن را بیان نمیکند استفاده کنید.
از <dialog> که بهعنوان مدال باز شده است، بهصورت اعلانی با command="show-modal" (به کنترل پاپاور و کادر گفتگوی اعلانی مراجعه کنید) یا از اسکریپت با showModal() استفاده کنید.
<button type="button" command="show-modal" commandfor="confirm">Delete</button>
<!-- MANDATORY: no role="dialog" or aria-modal; the native modal already provides them -->
<dialog id="confirm" aria-labelledby="confirm-title">
<h2 id="confirm-title">Delete this item?</h2>
<form method="dialog">
<button value="cancel" autofocus>Cancel</button>
<button value="delete">Delete</button>
</form>
</dialog>
مرورگر این موارد را ارائه میدهد: صفحه بیاثر، مهار تمرکز، لایه بالایی، ::backdrop، Esc و رد کردن اشاره برگشت، و بازگشت تمرکز به فراخواننده.
role="dialog"،aria-modal، تله تمرکز،inert، یا گردانندهEscرا اضافه نکنید.- نباید آن را با
show()یا مشخصهopenباز کنید. - باید با CSS زیر، پیمایش صفحه را در پشت آن متوقف کنید. برای جزئیات، جلوگیری از پیمایش صفحه زیر کادر گفتگوی مقید را ببینید.
- پیمایش را با
overflow: hiddenیاposition: fixedدرhtmlیاbodyقفل نکنید.
/* MANDATORY (both rules): without them, scrolling over the backdrop or inside the dialog scrolls the page behind it */
dialog:modal {
overscroll-behavior: contain; /* stops scroll gestures in the dialog chaining to the page */
}
dialog::backdrop {
overflow: hidden; /* ::backdrop is not a scroll container by default, so it would ignore overscroll-behavior */
overscroll-behavior: contain;
}
برای منوها یا نکتهابزارها نیست: قانون ۳ را ببینید.
قانون ۲: کاربر درحالیکه پانل باز است به کار در صفحه ادامه میدهد
از این نقش برای نوارهای جستجو، بازرسها، پالتها، و قابهای نوشتن که ازنظر معنایی هنوز کادر گفتگو هستند استفاده کنید.
از <dialog popover> برای پانل لایه بالایی با رد کردن بالاپر استفاده کنید (auto رد کردن آسان دارد، manual ندارد). فقط زمانی از dialog.show() استفاده کنید که پانل نباید در لایه بالایی باشد.
<button type="button" popovertarget="find">Find</button>
<dialog id="find" popover="manual" aria-labelledby="find-title">
<h2 id="find-title">Find</h2>
<input type="search" aria-label="Search">
<button type="button" popovertarget="find" popovertargetaction="hide">Close</button>
</dialog>
صفحه تعاملی باقی میماند و غیرفعال نمیشود.
- باید دکمه بستن قابلمشاهده داشته باشد.
- از
showModal()که صفحه را مسدود میکند استفاده نکنید.
نه برای مسدود کردن تأییدیهها: قانون ۱ را ببینید.
قانون ۳: رونهاد کادر گفتگو نیست
از آن برای منوها، منوهای کرکرهای، نکتهابزارها، کارتهای شناور، و پیامهای موقت استفاده کنید.
از عنصر معنایی با مشخصه popover استفاده کنید، نه <dialog>.
<button type="button" popovertarget="menu">Actions</button>
<ul id="menu" popover>
<li><a href="/rename">Rename</a></li>
</ul>
انتخاب نحوه بستن آن
پیشفرض به نحوه باز شدن کادر گفتگو بستگی دارد. اگر closedby وجود نداشته باشد یا نامعتبر باشد، showModal() مانند closerequest رفتار میکند و هر روش دیگری برای باز کردن مانند none رفتار میکند.
- فقط کلید Esc و اشاره برگشت (پیشفرض مدال): هیچچیز تنظیم نکنید، یا
closedby="closerequest". - همچنین برای بستن، بیرون کلیک کنید: از
closedby="any"استفاده کنید. - فقط توسعهدهنده آن را میبندد (برای مثال، فرمی با تغییرات ذخیرهنشده): از
closedby="none"استفاده کنید و مسیر تأیید اضافه کنید. بخش «بستن» را در زیر ببینید.
رهنمودها
نام دسترسپذیر و تمرکز اول
- باید هر کادر گفتگویی را با
aria-labelledbyکه به عنوان آن اشاره میکند یاaria-labelوقتی عنوان نمایانی وجود ندارد نامگذاری کنید. - باید
autofocusرا روی کنترل اصلی یا فیلد اول قرار دهید. - باید
autofocusرا روی کنش ایمن (مثل «لغو» یا «نگه داشتن») در تأیید مخرب قرار دهید. - نباید کدی بنویسید که تمرکز را هنگام بسته شدن برگرداند. مرورگر آن را به فراخواننده برمیگرداند.
در حال باز کردن
- باید بهصورت اعلانی با
command="show-modal"وcommandforدر<button>باز شود. Declarative Dialog and Popover Control را ببینید. - باید از
dialog.showModal()هنگام باز کردن از دستورگان استفاده کنید.
در حال بستن
- باید با دکمههای
<form method="dialog">یاcommand="close"بسته شود. - باید
closedbyرا آگاهانه انتخاب کنید:any(بستن با ضربه زدن در خارج از کادر وEsc)،closerequest(فقطEsc، پیشفرض مدال) یاnone(فقط بستن تحت کنترل توسعهدهنده). - برای دکمههای «لغو» که باید از منطق تغییرات ذخیرهنشده پیروی کنند، باید از
command="request-close"یاrequestClose()استفاده کنید. آنها ابتداcancelرا اجرا میکنند وclosedbyرا نادیده میگیرند. - باید با
closedbyبهعنوان ارتقای پیشرو رفتار کنید.Escو اشاره برگشت در تلفن همراه ازقبل بدون آن مدالها را میبندند.
درحال اتصال به نزدیک
- باید
dialog.returnValueرا در شنوندهcloseبخوانید تا بدانید از کدام دکمه استفاده شده است، سپس آن را باdialog.returnValue = ''بازنشانی کنید.Escو دیگر درخواستهای بستن هیچ مقداری را تنظیم نمیکنند و مرورگرها در اینکه آیا مقدار قبلی را پاک میکنند یا نه متفاوت هستند، بنابراین بدون بازنشانی، مقدار قدیمی میتواند نشت کند. با''بهعنوان «بستهشده بدون انتخاب» رفتار شود. - باید از
cancelبرای رهگیریEscوrequest-closeاستفاده کنید، اما برای فشردن مکررEscبدون فعالسازی کاربر بهpreventDefault()اتکا نکنید. - باید از
closedby="none"با مرحله تأیید صریح برای فرمهای کثیف استفاده کنید. - باید به کاربر بگویید چرا وقتی از
cancelجلوگیری میشود کادر گفتوگو باز میماند: پیام قابلمشاهدهای نشان دهید و آن را باaria-describedbyاز کنترلی که فوکوس را دریافت میکند (مثل «رد کردن») ارجاع دهید تا وقتی فوکوس جابهجا میشود صفحهخوان آن را بخواند. بدون آن، وقتی تمرکز به دکمه جدیدی منتقل میشود، فقط نام آن دکمه اعلام میشود. - اجازه ندهید تأیید، کنترلها را به زیر اشارهگر منتقل کند. فضای پیام را رزرو کنید (آن را با
visibilityپنهان کنید، نه باdisplayیاhidden) و دکمه مخرب را در جایی اضافه کنید که دکمههای موجود جابهجا نشوند (برای مثال، اولین دکمه در ردیف راستچینشده)، تا کلیک دوم در همان نقطه نتواند تغییرات کاربر را لغو کند.
کادرهای گفتگوی غیرمُدال
- باید از
<dialog popover>(autoیاmanual) برای پانل لایه بالایی استفاده کنید و ازdialog.show()زمانی که لایه بالایی را نمیخواهید. - باید در هر کادر گفتگوی غیرمدال، دکمه بستن نمایان اضافه کنید.
پساویز و سبکبندی
- DO style
::backdropand usedialog:modalto target modal-only styles. - باید پویانمایی را به Animate Elements To and From Top Layer، کشیدن را به Build a Draggable Element، و بازوالدینسازی را به Persistent Top Layer UI بسپارید.
displayرا بدون قیدوشرط رویdialogتنظیم نکنید. این گزینه وضعیت بسته را لغو میکند. باdialog[open]آن را بررسی کنید.
دسترسپذیری
role="dialog"،aria-modal، تله فوکوس دستی، یاinertتغییر وضعیت به مدال بومی را اضافه نکنید.- باید از
<button>بومی با نام دسترسپذیر برای کنترلهای بستن استفاده کنید. - باید از یک سطح عنوان یکسان برای عنوانهای کادر گفتگو استفاده کنید.
راهبردهای جایگزین
closedby (dialog-closedby)
بدون closedby، پنجره مانا همچنان با Esc و اشاره بازگشت بسته میشود؛ فقط کلیک کردن در بیرون از پنجره ازبین میرود، بنابراین این تنزل متین است. closedby="none" هم به همین روش نادیده گرفته میشود: Esc cancel را فعال میکند و سپس کادر گفتگو را میبندد. محافظ تغییرات ذخیرهنشده را در گرداننده cancel نگه دارید، نه فقط در closedby="none"، تا از جاهایی که closedby وجود ندارد نیز محافظت کند. اگر هدف «خط مبنا» شما به رد کردن ساده نیاز دارد، این را در جایی که closedby وجود ندارد اضافه کنید:
if (!('closedBy' in HTMLDialogElement.prototype)) {
// Test the pointer position, not event.target: the <dialog> is also the target for its own
// padding and the gaps between its children, so a target check would close on those clicks
const outside = (e, dialog) => {
const r = dialog.getBoundingClientRect();
return e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom;
};
for (const dialog of document.querySelectorAll('dialog[closedby="any"]')) {
let pressedOutside = false;
dialog.addEventListener('pointerdown', (e) => { pressedOutside = outside(e, dialog); });
// Require the press and the release outside, so a text-selection drag that ends on the backdrop does not dismiss
dialog.addEventListener('pointerup', (e) => { if (pressedOutside && outside(e, dialog)) dialog.close(); });
}
}
overscroll-behavior
Treat scroll containment as progressive enhancement. آن را با ویژگی تشخیص ندهید: @supports این ویژگی را بهعنوان پشتیبانیشده گزارش میکند، حتی در جایی که محفظههای غیرقابلپیمایش آن را نادیده میگیرند. در مواردی که این ویژگی اعمال نمیشود، کادر گفتگو همچنان حاوی پیمایش است، هر زمان که محتوای خودش سرریز شود، و showModal() همچنان صفحه را غیرفعال میکند. Do not fall back to overflow: hidden or position: fixed on html or body, which removes the root scrollbar and shifts the layout.
فرمانهای فراخواننده (command / commandfor)
اگر هدف «خط پایه» شما شامل «فرمانهای فراخواننده» نمیشود، همان نشانهگذاری را حفظ کنید و شبیهساز مرورگر را بهصورت شرطی بار کنید (همان تجربه، بهعنوان ارتقای پیشرو). در واسط شناسایی کنید و هرگز آن را بدون قیدوشرط بار نکنید:
// MANDATORY: feature detect on HTMLButtonElement.prototype and load only when native support is missing
if (!('commandForElement' in HTMLButtonElement.prototype)) {
import('invokers-polyfill'); // npm install invokers-polyfill
}
فقط قانون ۲ و ۳ از Popover استفاده میکنند.
پشتیبانی مرورگر و بازگشت به حالت قبلی برای Popover
«میانای برنامهسازی کاربردی Popover» عمدتاً ارتقای پیشرو است، اما رفتارهای تعریفکننده آن (ارتقای لایه بالایی، رد کردن با کلیک در خارج از عنصر، و فراخوانی popovertarget) معادل CSS-only ندارند. مرورگرهای قدیمیتر به شبیهساز مرورگر نیاز دارند، یا اگر ترجیح میدهید شبیهساز مرورگر ارسال نکنید، به بازگشت دستی نیاز دارند.
شبیهساز مرورگر: برای پشتیبانی از مشخصه popover در مرورگرهای قدیمیتر، @oddbird/popover-polyfill را بهصورت شرطی بار کنید. اجباری: ویژگی را با بررسی کردن ویژگی popover در HTMLElement.prototype شناسایی کنید و شبیهساز مرورگر را فقط زمانی بار کنید که پشتیبانی بومی وجود ندارد — آن را بدون قیدوشرط بار نکنید.
با بستهبندی یا نقشه واردات:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
بدون بستهبندی، از 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>
هشدار سبکبندی: شبیهساز مرورگر نمیتواند کلاس کاذب واقعی :popover-open را تعریف کند، بنابراین بهجای آن کلاس .\:popover-open را اعمال میکند. اجباری: این دو را با :is() یا :where() ترکیب کنید، درغیراینصورت مرورگرهایی که فاقد :popover-open هستند کل قانون را نادیده میگیرند:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
یا برای بازگشت به نسخه قدیمی بدون شبیهساز مرورگر، از position: fixed استفاده کنید و مختصات را بهصورت دستی ازطریق getBoundingClientRect() محاسبه کنید یا اگر برای مورد استفاده قابلقبول است، به موقعیتیابی پیشفرض با inset: auto تکیه کنید.