סקירה כללית בסיסית של בניית רכיב נגיש עם לחצנים מפוצלים.
בפוסט הזה אני רוצה לשתף אתכם במחשבה על דרך ליצירת לחצן מפוצל . רוצים לנסות את ההדגמה?
אם אתם מעדיפים סרטון, הנה גרסה של YouTube לפוסט:
סקירה כללית
לחצנים מפוצלים הם לחצנים שמסתירים לחצן ראשי ורשימה של לחצנים נוספים. הן שימושיות לחשיפת פעולה נפוצה תוך הצבת פעולה משנית, שנמצאת בשימוש נמוך יותר פעולות עד שיהיה צורך בכך. לחצן מפוצל יכול לעזור בעיצוב עמוס להרגיש מינימליות. לחצן פיצול מתקדם עשוי אפילו לזכור את הפעולה האחרונה של המשתמש ולקדם אותו למיקום הראשי.
ניתן למצוא לחצן פיצול משותף באפליקציית האימייל שלכם. הפעולה הראשית נשלח, אבל אולי כדאי לך לשלוח אותו מאוחר יותר או לשמור טיוטה:
אזור הפעולות המשותף נחמד, מאחר שהמשתמש לא צריך להסתכל מסביב. הם פעולות חיוניות באימייל נכללות בלחצן הפיצול.
חלקים
בואו נפרט את החלקים העיקריים של לחצן פיצול לפני שנדבר על התזמור הכללי וחוויית המשתמש הסופית. נגישות של VisBug כאן משתמשים בכלי הבדיקה כדי להציג תצוגת מאקרו של הרכיב, של ה-HTML, הסגנון והנגישות של כל חלק עיקרי.
מאגר לחצנים לפיצול ברמה העליונה
הרכיב ברמה הגבוהה ביותר הוא Flexbox מוטבע, עם סיווג של
gui-split-button
, שמכיל את הפעולה הראשית
וגם .gui-popup-button
.
לחצן הפעולה הראשית
ה-<button>
שגלוי בהתחלה וניתן למיקוד נכנס בתוך הקונטיינר עם
שתי צורות תואמות של פינות עבור
Focus,
מעבירים את העכבר ואז
אינטראקציות פעילות עם
בתוך .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-haspopup
ו-aria-expanded
ב-ARIA. הסימנים האלה
חיוני שקוראי מסך יהיו מודעים ליכולת ולמצב הפיצול
חוויית הלחצן. המאפיין 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>
יכריז על עצמו כ'לחצן'
list" לקוראי מסך, שזה בדיוק הממשק שמוצג.
<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);
}
}
לבסוף, כדי להוסיף קצת אווירה, הלחצן והסמל של העיצוב הבהיר מקבלים shadow:
.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)
})
סיכום
עכשיו, אחרי שהסברתי איך עשיתי את זה, איך היית? 🙂
בואו לגוון את הגישות שלנו ונלמד את כל הדרכים לבניית אתרים באינטרנט. אפשר ליצור הדגמה, לשלוח לי קישורים של שלחו לי ציוץ ואני אוסיף אותם לקטע 'רמיקסים' של הקהילה שבהמשך.