تاريخ النشر: 7 آذار (مارس) 2025
الأزرار ضرورية لإنشاء تطبيقات ويب ديناميكية. تفتح الأزرار القوائم، ويُستخدَم الزرّ لتفعيل الإجراءات وإيقافها وإرسال النماذج. وهي تشكل أساس التفاعل على الويب. يمكن أن يؤدي جعل الأزرار بسيطة وسهلة الاستخدام إلى بعض الصعوبات العميقة. يمكن أن يواجه المطوّرون الذين يبنون واجهات أمامية صغيرة أو أنظمة مكوّنات حلولًا تصبح أكثر تعقيدًا من اللازم. على الرغم من أنّ الأطر المرجعية مفيدة، يمكن للويب تقديم المزيد من المساعدة.
يقدّم الإصدار 135 من Chrome إمكانات جديدة لتوفير سلوك تعريفي باستخدامسمتَي command
وcommandfor
الجديدتَين، ما يؤدي إلى تحسين سمتَي popovertargetaction
وpopovertarget
واستبدالهما. يمكن
إضافة هذه السمات الجديدة إلى الأزرار، ما يتيح للمتصفّح معالجة بعض المشاكل الأساسية المتعلّقة
بالسلاسة وسهولة الاستخدام، وتوفير وظائف شائعة مدمجة.
الأنماط التقليدية
يمكن أن يشكّل إنشاء سلوكيات الأزرار بدون إطار عمل بعض الصعوبات المثيرة للاهتمام
مع تطور رمز الإنتاج. على الرغم من أنّ HTML يوفّر onclick
معالِجات ل buttons، غالبًا ما يتم حظر هذه المعالِجات خارج العروض التوضيحية أو الأدلة التعليمية بسبب قواعد Content security policy (CSP). في حين يتم إرسال هذه الأحداث على عناصر
الزر، يتم عادةً وضع الأزرار على صفحة للتحكّم في عناصر أخرى
تتطلّب رمزًا برمجيًا للتحكّم في عنصرَين في آنٍ واحد. عليك أيضًا التأكّد من أنّه يمكن لمستخدمي التكنولوجيا المساعِدة التفاعل مع المحتوى. يؤدي ذلك غالبًا إلى استخدام رمز برمجي يشبه هذا الرمز:
<div class="menu-wrapper">
<button class="menu-opener" aria-expanded="false">
Open Menu
</button>
<div popover class="menu-content">
<!-- ... -->
</div>
</div>
<script type="module">
document.addEventListener('click', e => {
const button = e.target;
if (button.matches('.menu-opener')) {
const menu = button
.closest('.menu-wrapper')
.querySelector('.menu-content');
if (menu) {
button.setAttribute('aria-expanded', 'true');
menu.showPopover();
menu.addEventListener('toggle', e => {
// reset back to aria-expanded=false on close
if (e.newState == 'closed') {
button.setAttribute('aria-expanded', 'false');
}
}, {once: true})
}
}
});
</script>
يمكن أن يكون هذا النهج غير ملائم في بعض الحالات، وتهدف الأطر إلى تحسين سهولة الاستخدام. قد يتضمن النمط الشائع مع إطار عمل مثل React ربط النقر بأحد حالات التحويل التالية:
function MyMenu({ children }) {
const [isOpen, setIsOpen] = useState(false);
const open = useCallback(() => setIsOpen(true), []);
const handleToggle = useCallback((e) => {
// popovers have light dismiss which influences our state
setIsOpen(e.newState === 'open')
}, []);
const popoverRef = useRef(null);
useEffect(() => {
if (popoverRef.current) {
if (isOpen) {
popoverRef.current.showPopover();
} else {
popoverRef.current.hidePopover();
}
}
}, [popoverRef, isOpen]);
return (
<>
<button onClick={open} aria-expanded={isOpen}>
Open Menu
</button>
<div popover onToggle={handleToggle} ref={popoverRef}>
{children}
</div>
</>
);
}
تهدف العديد من الإطارات الأخرى أيضًا إلى توفير ميزات سهولة الاستخدام نفسها، على سبيل المثال، يمكن كتابة هذا الرمز البرمجي باستخدام AlpineJS على النحو التالي:
<div x-data="{open: false}">
<button @click="open = !open; $refs.popover.showPopover()" :aria-expanded="open">
Open Menu
</button>
<div popover x-ref="popover" @toggle="open = $event.newState === 'open'">
<!-- ... -->
</div>
</div>
قد يبدو هذا الرمز في Svelte على النحو التالي:
<script>
let popover;
let open = false;
function togglePopover() {
open ? popover.hidePopover() : popover.showPopover();
open = !open;
}
</script>
<button on:click={togglePopover} aria-expanded={open}>
Open Menu
</button>
<div bind:this={popover} popover>
<!-- ... -->
</div>
قد تأخذ بعض أنظمة التصميم أو المكتبات خطوة إضافية من خلال توفير عناصر ملفوفة حول عناصر الأزرار التي تُحاط بتغييرات الحالة. يُخفِّض هذا الإجراء من التغييرات في الحالة باستخدام مكوّن عامل تشغيل، ما يؤدي إلى تقليل المرونة قليلاً لتحسين سهولة الاستخدام:
import {MenuTrigger, MenuContent} from 'my-design-system';
function MyMenu({children}) {
return (
<MenuTrigger>
<button>Open Menu</button>
</MenuTrigger>
<MenuContent>{children}</MenuContent>
);
}
نمط الأمر وcommandfor
باستخدام السمتَين command
وcommandfor
، يمكن الآن للأزرار تنفيذ إجراءات
على عناصر أخرى بشكل تعريفي، ما يمنح سهولة الاستخدام في إطار العمل بدون
تضحية بالمرونة. يقبل الزر commandfor
معرّفًا، مثل سمة
for
، في حين يقبل command
قيمًا مضمّنة، ما يتيح استخدام أسلوب
أكثر سهولة وقابلية للنقل.
مثال: زر قائمة مفتوحة يتضمّن الأمر وcommandfor
يُنشئ رمز HTML التالي علاقات وصفية بين الزر والملف الشخصي
، ما يتيح للمتصفّح التعامل مع المنطق وإمكانية الوصول نيابةً عنك. ليس هناك
حاجة إلى إدارة aria-expanded
أو إضافة أيّ JavaScript إضافي.
<button commandfor="my-menu" command="show-popover">
Open Menu
</button>
<div popover id="my-menu">
<!-- ... -->
</div>
مقارنة command
وcommandfor
بـ popovertargetaction
وpopovertarget
إذا سبق لك استخدام popover
، قد تكون على دراية بسمات popovertarget
وpopovertargetaction
. تعمل هذه الرموز بشكلٍ مشابه لرمزَي commandfor
و
command
على التوالي، باستثناء أنّها خاصة بالنوافذ المنبثقة. تحلّ سمتا command
و
commandfor
محلّ هاتين السمتَين القديمتَين تمامًا. تتيح السمات الجديدة
كل الوظائف التي كانت تتيحها السمات القديمة، بالإضافة إلى وظائف
جديدة.
الطلبات المضمّنة
تحتوي سمة command
على مجموعة من السلوكيات المضمّنة التي يتم ربطها بواجهات برمجة تطبيقات مختلفة للعناصر التفاعلية:
show-popover
: تؤدي إلىel.showPopover()
.hide-popover
: تؤدي إلىel.hidePopover()
.toggle-popover
: تؤدي إلىel.togglePopover()
.show-modal
: تؤدي إلىdialogEl.showModal()
.close
: تؤدي إلىdialogEl.close()
.
وتتم ربط هذه الأوامر بنظيراتها في JavaScript، مع تسهيل استخدامها (مثل توفير aria-details
وaria-expanded
العلاقات المماثلة) وإدارة التركيز وغير ذلك.
مثال: مربّع حوار تأكيد يتضمّن command
وcommandfor
<button commandfor="confirm-dialog" command="show-modal">
Delete Record
</button>
<dialog id="confirm-dialog">
<header>
<h1>Delete Record?</h1>
<button commandfor="confirm-dialog" command="close" aria-label="Close" value="close">
<img role="none" src="/close-icon.svg">
</button>
</header>
<p>Are you sure? This action cannot be undone</p>
<footer>
<button commandfor="confirm-dialog" command="close" value="cancel">
Cancel
</button>
<button commandfor="confirm-dialog" command="close" value="delete">
Delete
</button>
</footer>
</dialog>
سيؤدي النقر على الزر حذف السجلّ إلى فتح مربّع الحوار كعنصر نوافذ مشروطة، في حين يؤدي
النقر على الزر إغلاق أو إلغاء أو حذف إلى إغلاق مربّع الحوار مع
إرسال حدث "close"
أيضًا في مربّع الحوار الذي يحتوي على سمة returnValue
تتطابق مع قيمة الزر.
يقلل ذلك من الحاجة إلى JavaScript خارج مستمع حدث واحد في مربّع الحوار لتحديد الإجراء التالي:
dialog.addEventListener("close", (event) => {
if (event.target.returnValue == "cancel") {
console.log("cancel was clicked");
} else if (event.target.returnValue == "close") {
console.log("close was clicked");
} else if (event.target.returnValue == "delete") {
console.log("delete was clicked");
}
});
الطلبات المخصّصة
بالإضافة إلى الأوامر المدمجة، يمكنك تحديد أوامر مخصّصة باستخدام البادئة
--
. ستُرسِل الأوامر المخصّصة حدث "command"
على العنصر المستهدف (تمامًا مثل الأوامر المضمّنة)، ولكن لن تُنفِّذ أبدًا أي منطق إضافي مثل القيم المضمّنة. يمنح ذلك مرونة في
إنشاء مكوّنات يمكنها الاستجابة للأزرار بطرق مختلفة بدون الحاجة إلى
توفير مكوّنات ملفوفة أو التنقّل في نموذج DOM للعنصر المستهدَف أو ربط
نقرات الأزرار بتغييرات الحالة. يتيح لك ذلك توفير واجهة برمجة تطبيقات ضمن HTML ل
مكوناتك:
<button commandfor="the-image" command="--rotate-landscape">
Landscape
</button>
<button commandfor="the-image" command="--rotate-portrait">
Portrait
</button>
<img id="the-image" src="photo.jpg">
<script type="module">
const image = document.getElementById("the-image");
image.addEventListener("command", (event) => {
if ( event.command == "--rotate-landscape" ) {
image.style.rotate = "-90deg"
} else if ( event.command == "--rotate-portrait" ) {
image.style.rotate = "0deg"
}
});
</script>
الأوامر في ShadowDOM
بما أنّ سمة commandfor
تأخذ رقم تعريف، هناك قيود حول
عبور نموذج DOM الظل. في هذه الحالات، يمكنك استخدام JavaScript API لضبط
السمة .commandForElement
التي يمكنها ضبط أي عنصر على مستوى
جذور الظل:
<my-element>
<template shadowrootmode=open>
<button command="show-popover">Show popover</button>
<slot></slot>
</template>
<div popover><!-- ... --></div>
</my-element>
<script>
customElements.define("my-element", class extends HTMLElement {
connectedCallback() {
const popover = this.querySelector('[popover]');
// The commandForElement can set cross-shadow root elements.
this.shadowRoot.querySelector('button').commandForElement = popover;
}
});
</script>
قد تقدّم الاقتراحات المستقبلية طريقة توضيحية لمشاركة المراجع على مستوى حدود الظل، مثل اقتراح استهداف المراجع.
ما هي الخطوات التالية؟
سنواصل استكشاف إمكانيات توفير أوامر مضمّنة جديدة لتلبية الوظائف الشائعة التي تستخدمها المواقع الإلكترونية. يتم تناول الأفكار المقترَحة في اقتراح واجهة المستخدم المفتوحة. في ما يلي بعض الأفكار التي تم استكشافها من قبل:
- فتح عناصر
<details>
وإغلاقها - أمر
"show-picker"
لعنصرَي<input>
و<select>
، مع الربط بقيمةshowPicker()
- أوامر التشغيل لعنصرَي
<video>
و<audio>
- نسخ محتوى النص من العناصر
نرحب بملاحظات المنتدى. إذا كانت لديك اقتراحات، يُرجى عدم التردد في إرسال ملف بشأن مشكلة في نظام تتبُّع مشاكل واجهة المستخدم المفتوحة.
مزيد من المعلومات
يمكنك العثور على مزيد من المعلومات عن command
وcommandfor
في
المواصفات
وعلى
MDN.