نظرة عامة أساسية حول كيفية بناء مكون مجزأ يمكن الوصول إليه.
في هذه المشاركة، أريد مشاركة الأفكار حول طريقة لإنشاء زر تقسيم . جرِّب الإصدار التجريبي.
في ما يلي إصدار YouTube من هذه المشاركة إذا كنت تفضّل الفيديوهات:
نظرة عامة
أزرار التقسيم هي أزرار تُخفي زرًا أساسيًا وقائمة أزرارًا إضافية. وهي مفيدة لعرض إجراء شائع مع دمج الإجراءات الثانوية الأقل استخدامًا حتى الحاجة. يمكن أن يكون زر التقسيم أمرًا حاسمًا لمساعدة التصميم المشغول على الشعور بالحد الأدنى. قد يتذكر زر التقسيم المتقدم آخر إجراء المستخدم ويروّج له في الموضع الأساسي.
يمكن العثور على زر تقسيم شائع في تطبيق البريد الإلكتروني. الإجراء الأساسي هو الإرسال، ولكن ربما يمكنك الإرسال لاحقًا أو حفظ مسودة بدلاً من ذلك:
منطقة العمل المشتركة رائعة، لأن المستخدم لا يحتاج إلى النظر حوله. إنه يعلم أن إجراءات البريد الإلكتروني الأساسية مضمنة في زر التقسيم.
قطع الغيار
دعنا نحلل الأجزاء الأساسية لزر التقسيم قبل مناقشة التنسيق العام وتجربة المستخدم النهائية. تُستخدم أداة فحص إمكانية الوصول في VisBug هنا للمساعدة في إظهار عرض ماكرو للمكوِّن، وإظهار جوانب HTML والنمط وإمكانية الوصول لكل جزء رئيسي.
حاوية زر تقسيم المستوى الأعلى
المكوِّن الأعلى مستوى هو مربّع flexbox مضمّن، مع فئة gui-split-button
ويحتوي على الإجراء الأساسي و.gui-popup-button
.
زر الإجراء الأساسي
إنّ علامة <button>
الظاهرة في البداية والقابلة للتركيز تكون مناسبة للعرض ضمن الحاوية ذات شكلَين مطابِقَين من أجل التركيز وhover والتفاعلات النشطة لكي تظهر مضمَّنة في .gui-split-button
.
زر تبديل النافذة المنبثقة
عنصر الدعم "الزر المنبثق" مخصص للتنشيط والإشارة إلى قائمة الأزرار الثانوية. يُرجى العِلم أنّ هذه السمة ليست <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')
مع استيراد المكتبات المذكورة أعلاه وتحديد العناصر وحفظها في متغيرات، ستكون ترقية التجربة على بعد بضع دوال من أن تكتمل.
مؤشّرات التنقّل
عندما تركّز لوحة المفاتيح أو قارئ الشاشة على .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)
})
الخلاصة
الآن بعد أن عرفت كيف فعلت ذلك، كيف يمكنك‽ 🙂
دعونا ننويع أساليبنا ونتعلم جميع طرق الإنشاء على الويب. يمكنك إنشاء عرض توضيحي وروابط تغريدات لي وسنضيفها إلى قسم الريمكسات في المنتدى أدناه.
ريمكسات من المنتدى
- Codepen من تأليف Joost van der Cheee