نظرة عامة أساسية حول كيفية إنشاء مكوّن زر مجزأ سهل الاستخدام
في هذه المشاركة، أود أن أشارككم أفكارًا حول طريقة لإنشاء زر تقسيم . جرِّب العرض التجريبي.
إذا كنت تفضّل الفيديو، يمكنك الاطّلاع على نسخة من هذه المشاركة على YouTube:
نظرة عامة
الأزرار المجزّأة هي أزرار تخفي زرًا أساسيًا وقائمة بأزرار إضافية. إنها مفيدة لكشف إجراء شائع أثناء دمج الإجراءات الثانوية والأقل استخدامًا حتى الحاجة. يمكن أن يكون الزر المُقسَّم عنصرًا مهمًا لجعل التصميم المُشغّل يبدو بسيطًا. قد يتذكر زر التقسيم المتقدّم أيضًا آخر إجراء للمستخدِم ويصعّده إلى الموضع الأساسي.
يمكنك العثور على زر تقسيم شائع في تطبيق البريد الإلكتروني. الإجراء الأساسي هو الإرسال، ولكن قد يكون بإمكانك الإرسال لاحقًا أو حفظ مسودة بدلاً من ذلك:
إنّ منطقة الإجراءات المشترَكة رائعة، لأنّ المستخدم لا يحتاج إلى البحث في جميع الاتجاهات. ويعلمون أنّ الإجراءات الأساسية للبريد الإلكتروني مضمّنة في زرّ التقسيم.
الأجزاء
لنقسّم الأجزاء الأساسية للزر المُقسّم قبل مناقشة التنسيق العام وتجربة المستخدم النهائي. يتم استخدام أداة فحص إمكانية الوصول في VisBug هنا للمساعدة في إظهار عرض مكرو للمكوِّن، مع إبراز جوانب HTML والنمط وسهولة الوصول لكل جزء رئيسي.
حاوية الزر المُقسَّم على مستوى أعلى
العنصر الأعلى مستوى هو مربّع مرن مضمّن، وله فئة
gui-split-button
، ويحتوي على الإجراء الأساسي
و.gui-popup-button
.
زر الإجراء الأساسي
يتلاءم العنصر <button>
المرئي في البداية والذي يمكن التركيز عليه مع الحاوية مع
شكلَين متطابقَين للزوايا من أجل
التركيز والمرّر فوقه والتفاعلات النشطة ليظهر
ضمن .gui-split-button
.
زر إيقاف/تفعيل النافذة المنبثقة
يُستخدَم عنصر الدعم "زر النافذة المنبثقة" لتفعيل قائمة buttons
الثانوية والإشارة إليها. يُرجى ملاحظة أنّه ليس <button>
ولا يمكن التركيز عليه. ومع ذلك،
فهو عنصر الربط لتحديد موضع .gui-popup
ومضيف :focus-within
المستخدَم
لعرض النافذة المنبثقة.
البطاقة المنبثقة
هذه بطاقة ثانوية عائمة في علامة الارتساء
.gui-popup-button
، ويتم وضعها في موضع مطلق، ويتم التفاف قائمة الأزرار دلاليًا.
الإجراءات الثانوية
<button>
قابل للتركيز بحجم خط أصغر قليلاً من زر الإجراء
الأساسي، ويحتوي على رمز وأسلوب
مكمّل للزر الأساسي
الخصائص المخصّصة
تساعد المتغيرات التالية في خلق تناغم الألوان ومكان مركزي لتعديل القيم المستخدمة في جميع أنحاء المكون.
@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --light (prefers-color-scheme: light);
.gui-split-button {
--theme: hsl(220 75% 50%);
--theme-hover: hsl(220 75% 45%);
--theme-active: hsl(220 75% 40%);
--theme-text: hsl(220 75% 25%);
--theme-border: hsl(220 50% 75%);
--ontheme: hsl(220 90% 98%);
--popupbg: hsl(220 0% 100%);
--border: 1px solid var(--theme-border);
--radius: 6px;
--in-speed: 50ms;
--out-speed: 300ms;
@media (--dark) {
--theme: hsl(220 50% 60%);
--theme-hover: hsl(220 50% 65%);
--theme-active: hsl(220 75% 70%);
--theme-text: hsl(220 10% 85%);
--theme-border: hsl(220 20% 70%);
--ontheme: hsl(220 90% 5%);
--popupbg: hsl(220 10% 30%);
}
}
التنسيقات والألوان
Markup
يبدأ العنصر كـ <div>
باسم فئة مخصّص.
<div class="gui-split-button"></div>
أضِف الزر الأساسي وعناصر .gui-popup-button
.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span>
</div>
لاحِظ سمتَي aria aria-haspopup
وaria-expanded
. هذه الإشارات مهمة لبرامج قراءة الشاشة كي تكون على دراية بإمكانية استخدام زر الالتفاف المُقسّم وحالة استخدامه. تكون السمة title
مفيدة للجميع.
أضِف رمز <svg>
وعنصر الحاوية .gui-popup
.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup"></ul>
</span>
</div>
بالنسبة إلى موضع النافذة المنبثقة المباشر، يكون .gui-popup
عنصرًا فرعيًا للزر الذي
يوسيّعه. العائق الوحيد في هذه الاستراتيجية هو أنّ حاوية .gui-split-button
لا يمكنها استخدام overflow: hidden
، لأنّ ذلك سيؤدي إلى اقتصاص النافذة المنبثقة وعدم
ظهورها بشكل مرئي.
إنّ <ul>
المليء بمحتوى <li><button>
سيُعلن عن نفسه على أنّه "قائمة أزرار" لبرامج قراءة الشاشة، أي الواجهة التي يتم عرضها تحديدًا.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li>
<button>Schedule for later</button>
</li>
<li>
<button>Delete</button>
</li>
<li>
<button>Save draft</button>
</li>
</ul>
</span>
</div>
أضفت رموزًا إلى الأزرار الثانوية من https://heroicons.com لإضفاء لمسة من الأناقة والتشويق. والرموز اختيارية لكلٍّ من الأزرار الأساسية والثانوية.
<div class="gui-split-button">
<button>Send</button>
<span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
<svg aria-hidden="true" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<ul class="gui-popup">
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
Schedule for later
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
Delete
</button></li>
<li><button>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
</svg>
Save draft
</button></li>
</ul>
</span>
</div>
الأنماط
مع استخدام HTML والمحتوى، تكون الأنماط جاهزة لتوفير الألوان والتنسيق.
تصميم حاوية الزر المُقسَّم
يعمل نوع عرض inline-flex
بشكل جيد لمكوِّن الالتفاف هذا، حيث يجب أن يتناسب مع أزرار أو إجراءات أو عناصر تقسيم أخرى.
.gui-split-button {
display: inline-flex;
border-radius: var(--radius);
background: var(--theme);
color: var(--ontheme);
fill: var(--ontheme);
touch-action: manipulation;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
التصميم <button>
إنّ الأزرار رائعة في إخفاء مقدار الرمز البرمجي المطلوب. قد تحتاج إلى إلغاء الأنماط التلقائية للمتصفح أو استبدالها، ولكن عليك أيضًا فرض بعض الاستبدال وإضافة حالات التفاعل والتكيّف مع الإعدادات المفضّلة المختلفة للمستخدمين و أنواع الإدخال. تتراكم أنماط الأزرار بسرعة.
تختلف هذه الأزرار عن الأزرار العادية لأنّها تشترك في خلفية مع عنصر رئيسي. عادةً ما يمتلك الزرّ خلفيته ولون نصه. ومع ذلك، تشارك هذه التطبيقات الخلفية، ولا تطبّق خلفيتها إلا عند التفاعل.
.gui-split-button button {
cursor: pointer;
appearance: none;
background: none;
border: none;
display: inline-flex;
align-items: center;
gap: 1ch;
white-space: nowrap;
font-family: inherit;
font-size: inherit;
font-weight: 500;
padding-block: 1.25ch;
padding-inline: 2.5ch;
color: var(--ontheme);
outline-color: var(--theme);
outline-offset: -5px;
}
أضِف حالات التفاعل مع عدد قليل من فئات CSS الصورية واستخدِم السمات المخصّصة المطابقة للحالة:
.gui-split-button button {
…
&:is(:hover, :focus-visible) {
background: var(--theme-hover);
color: var(--ontheme);
& > svg {
stroke: currentColor;
fill: none;
}
}
&:active {
background: var(--theme-active);
}
}
يحتاج الزر الأساسي إلى بعض الأنماط الخاصة لإكمال تأثير التصميم:
.gui-split-button > button {
border-end-start-radius: var(--radius);
border-start-start-radius: var(--radius);
& > svg {
fill: none;
stroke: var(--ontheme);
}
}
أخيرًا، للحصول على بعض الرموز، يحصل الزر "مظهر فاتح" والرمز على ظل:
.gui-split-button {
@media (--light) {
& > button,
& button:is(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--theme-active);
}
& > .gui-popup-button > svg,
& button:is(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--theme-active));
}
}
}
يُظهر الزرّ الرائع اهتمامًا بالتفاعلات الدقيقة والتفاصيل الصغيرة.
ملاحظة حول :focus-visible
لاحظ أنّ أنماط الأزرار تستخدم :focus-visible
بدلاً من :focus
. :focus
هي لمسة ضرورية لإنشاء واجهة مستخدم سهلة الاستخدام، ولكنّها تتضمن عيبًا واحدًا: فهي ليست ذكية في ما يتعلّق بما إذا كان المستخدم بحاجة إلى رؤيتها أو
عدم رؤيتها، وسيتم تطبيقها على أي عنصر يتم التركيز عليه.
يحاول الفيديو أدناه تقسيم هذا التفاعل الصغير لعرض مدى روعة
:focus-visible
كبديل ذكي.
تصميم زر النافذة المنبثقة
4ch
صندوق مرن لتوسط رمز وتثبيت قائمة أزرار منبثقة مثل الزر الأساسي، يكون هذا الزر شفافًا إلى أن يتم تمرير مؤشر الماوس فوقه أو التفاعل معه، ويتم تمديده لملء المساحة.
.gui-popup-button {
inline-size: 4ch;
cursor: pointer;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
border-inline-start: var(--border);
border-start-end-radius: var(--radius);
border-end-end-radius: var(--radius);
}
يمكنك إضافة حالات التمرير فوق العنصر والتركيز عليه والنشاط باستخدام تضمين CSS
وأداة الاختيار الوظيفية
:is()
:
.gui-popup-button {
…
&:is(:hover,:focus-within) {
background: var(--theme-hover);
}
/* fixes iOS trying to be helpful */
&:focus {
outline: none;
}
&:active {
background: var(--theme-active);
}
}
تشكّل هذه الأنماط عنصر الجذب الأساسي لعرض النافذة المنبثقة وإخفائها. عندما يحتوي العنصر
.gui-popup-button
على focus
في أيّ من عناصره الفرعية، اضبط opacity
، والموقع
وpointer-events
، في الرمز والنافذة المنبثقة.
.gui-popup-button {
…
&:focus-within {
& > svg {
transition-duration: var(--in-speed);
transform: rotateZ(.5turn);
}
& > .gui-popup {
transition-duration: var(--in-speed);
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
}
}
بعد اكتمال أنماط الانتقال والخروج، تكون الخطوة الأخيرة هي تطبيق التحولات مشروطًا في الانتقال استنادًا إلى الإعدادات المفضّلة للمستخدم في ما يتعلّق بالحركة:
.gui-popup-button {
…
@media (--motionOK) {
& > svg {
transition: transform var(--out-speed) ease;
}
& > .gui-popup {
transform: translateY(5px);
transition:
opacity var(--out-speed) ease,
transform var(--out-speed) ease;
}
}
}
ستلاحظ مراقبة شدة باستخدام الرمز أن معدل الشفافية لا يزال متغيرًا للمستخدمين الذين يفضلون الحركة المنخفضة.
تصميم النافذة المنبثقة
العنصر .gui-popup
هو قائمة بأزرار بطاقة عائمة تستخدم سمات مخصّصة ووحدات نسبية لتكون أصغر حجمًا بشكل طفيف ومطابقتها بشكل تفاعلي مع الزر الأساسي ومتوافقة مع العلامة التجارية بفضل استخدام الألوان فيها. لاحِظ أنّ الرموز ذات تباين أقل،
وأنّها أرق، وأنّ الظلّ يتضمّن لمحة من اللون الأزرق المميّز للعلامة التجارية. تمامًا مثل الأزرار،
تنتج واجهة المستخدم وتجربة المستخدم القوية عن تجميع هذه التفاصيل الصغيرة.
.gui-popup {
--shadow: 220 70% 15%;
--shadow-strength: 1%;
opacity: 0;
pointer-events: none;
position: absolute;
bottom: 80%;
left: -1.5ch;
list-style-type: none;
background: var(--popupbg);
color: var(--theme-text);
padding-inline: 0;
padding-block: .5ch;
border-radius: var(--radius);
overflow: hidden;
display: flex;
flex-direction: column;
font-size: .9em;
transition: opacity var(--out-speed) ease;
box-shadow:
0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
;
}
تم منح الأيقونات والأزرار ألوان العلامة التجارية لتنسيقها بشكل جميل في كل بطاقة ذات مظهر داكن وفاتح:
.gui-popup {
…
& svg {
fill: var(--popupbg);
stroke: var(--theme);
@media (prefers-color-scheme: dark) {
stroke: var(--theme-border);
}
}
& button {
color: var(--theme-text);
width: 100%;
}
}
تحتوي النافذة المنبثقة للمظهر الداكن على إضافات ظلال نصوص ورموز، بالإضافة إلى ظلال مربع أكثر شدة:
.gui-popup {
…
@media (--dark) {
--shadow-strength: 5%;
--shadow: 220 3% 2%;
& button:not(:focus-visible, :hover) {
text-shadow: 0 1px 0 var(--ontheme);
}
& button:not(:focus-visible, :hover) > svg {
filter: drop-shadow(0 1px 0 var(--ontheme));
}
}
}
أنماط الرموز العامة <svg>
يصل حجم جميع الرموز نسبيًا إلى الزر font-size
الذي يتم استخدامها فيه من خلال استخدام الوحدة ch
على أنّها inline-size
. يتم أيضًا توفير بعض الأنماط للمساعدة في تحديد الأيقونات
الناعمة والسلاسة.
.gui-split-button svg {
inline-size: 2ch;
box-sizing: content-box;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2px;
}
التخطيط من اليمين إلى اليسار
تُنفِّذ الخصائص المنطقية كل الأعمال المعقّدة.
في ما يلي قائمة بالسمات المنطقية المستخدَمة:
- display: inline-flex
لإنشاء عنصر مرن مضمّن
- يمكن استخدام padding-block
وpadding-inline
كزوج بدلاً من اختصار padding
للحصول على مزايا المساحة المتروكة حول الجوانب المنطقية.
- border-end-start-radius
و
الأصدقاء:
ستتم إزالة الزوايا الحادة استنادًا إلى اتجاه المستند.
- يضمن استخدام inline-size
بدلاً من width
عدم ربط الحجم بالأبعاد المادية.
- تضيف border-inline-start
حدودًا إلى البداية، وقد تكون على اليمين أو اليسار حسب اتجاه النص.
JavaScript
إنّ معظم رمز JavaScript التالي مخصّص لتحسين إمكانية الاستخدام. يتم استخدام مكتبتَين من مكتبات المساعدة لجعل المهام أسهل قليلاً. يتم استخدام BlingBlingJS لإجراء طلبات بحث موجزة في DOM وإعداد مستمع الأحداث بسهولة، في حين تساعد مكتبة roving-ux في تسهيل التفاعلات المتاحة باستخدام لوحة المفاتيح ووحدة التحكّم في الألعاب في النافذة المنبثقة.
import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'
const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')
بعد استيراد المكتبات أعلاه واختيار العناصر وحفظها فيvariabels، لا تبقى سوى بضع دوالّ لإجراء ترقية التجربة.
مؤشر التنقّل
عندما تركّز لوحة مفاتيح أو قارئ شاشة على .gui-popup-button
، نريد
توجيه التركيز إلى الزر الأول (أو الزر الذي تم التركيز عليه مؤخرًا) في
.gui-popup
. تساعدنا المكتبة في إجراء ذلك باستخدام المَعلمتَين element
وtarget
.
popupButtons.forEach(element =>
rovingIndex({
element,
target: 'button',
}))
يوجِّه العنصر الآن التركيز إلى العناصر الثانوية المستهدفة في <button>
ويتيح
التنقّل العادي باستخدام مفتاح السهم لتصفّح الخيارات.
تفعيل aria-expanded
على الرغم من أنّه من الواضح بصريًا أنّ النافذة المنبثقة تظهر وتُخفى، يحتاج قارئ الشاشة إلى أكثر من الإشارات المرئية. يتم استخدام JavaScript هنا ليتكامل مع تفاعل :focus-within
المستند إلى CSS من خلال تبديل سمة مناسبة لقارئ الشاشة.
popupButtons.on('focusin', e => {
e.currentTarget.setAttribute('aria-expanded', true)
})
popupButtons.on('focusout', e => {
e.currentTarget.setAttribute('aria-expanded', false)
})
جارٍ تفعيل المفتاح Escape
تم توجيه تركيز المستخدم عمدًا إلى فخ، ما يعني أنّنا نحتاج إلى
توفير طريقة للخروج. الطريقة الأكثر شيوعًا هي السماح باستخدام مفتاح Escape
.
وللقيام بذلك، راقب ضغطات المفاتيح على الزر المنبثق، حيث ستظهر أي أحداث على لوحة المفاتيح للأطفال على الشاشة لهذا الوالد.
popupButtons.on('keyup', e => {
if (e.code === 'Escape')
e.target.blur()
})
إذا لاحظ الزر المنبثق أي ضغط على مفتاح Escape
، يزيل ذلك التركيز من نفسه باستخدام blur()
.
عدد النقرات على الزر المُقسَّم
أخيرًا، إذا نقر المستخدم على الأزرار أو ضغط عليها أو تفاعلت لوحة المفاتيح معها، يجب أن ينفِّذ
التطبيق الإجراء المناسب. يتم استخدام تصعيد الأحداث
مرة أخرى هنا، ولكن هذه المرة في حاوية .gui-split-button
، لرصد نقرات
الزر من نافذة منبثقة فرعية أو الإجراء الأساسي.
splitButtons.on('click', event => {
if (event.target.nodeName !== 'BUTTON') return
console.info(event.target.innerText)
})
الخاتمة
الآن بعد أن عرفت كيف فعلت ذلك، كيف يمكنك‽ 🙂
يمكننا تنويع أساليبنا وتعلُّم جميع طرق إنشاء المحتوى على الويب. أنشئ عرضًا توضيحيًا وأرسِل إلينا رابطًا على Twitter، وسنضيفه إلى قسم الريمكسات التي أنشأها المستخدمون أدناه.
الريمكسات التي أنشأها المستخدمون
- Codepen من تأليف Joost van der Schee