בניית רכיב של לחצן מפוצל

סקירה כללית בסיסית של בניית רכיב נגיש עם לחצנים מפוצלים.

בפוסט הזה אני רוצה לשתף אתכם במחשבה על דרך ליצירת לחצן מפוצל . רוצים לנסות את ההדגמה?

הדגמה

אם אתם מעדיפים סרטון, הנה גרסה של YouTube לפוסט:

סקירה כללית

לחצנים מפוצלים הם לחצנים שמסתירים לחצן ראשי ורשימה של לחצנים נוספים. הן שימושיות לחשיפת פעולה נפוצה תוך הצבת פעולה משנית, שנמצאת בשימוש נמוך יותר פעולות עד שיהיה צורך בכך. לחצן מפוצל יכול לעזור בעיצוב עמוס להרגיש מינימליות. לחצן פיצול מתקדם עשוי אפילו לזכור את הפעולה האחרונה של המשתמש ולקדם אותו למיקום הראשי.

ניתן למצוא לחצן פיצול משותף באפליקציית האימייל שלכם. הפעולה הראשית נשלח, אבל אולי כדאי לך לשלוח אותו מאוחר יותר או לשמור טיוטה:

לחצן פיצול לדוגמה כפי שמופיע באפליקציית אימייל.

אזור הפעולות המשותף נחמד, מאחר שהמשתמש לא צריך להסתכל מסביב. הם פעולות חיוניות באימייל נכללות בלחצן הפיצול.

חלקים

בואו נפרט את החלקים העיקריים של לחצן פיצול לפני שנדבר על התזמור הכללי וחוויית המשתמש הסופית. נגישות של VisBug כאן משתמשים בכלי הבדיקה כדי להציג תצוגת מאקרו של הרכיב, של ה-HTML, הסגנון והנגישות של כל חלק עיקרי.

רכיבי ה-HTML שמרכיבים את הלחצן המפוצל.

מאגר לחצנים לפיצול ברמה העליונה

הרכיב ברמה הגבוהה ביותר הוא Flexbox מוטבע, עם סיווג של gui-split-button, שמכיל את הפעולה הראשית וגם .gui-popup-button.

המחלקה של gui-split-button נבדקה ומוצגת בה מאפייני ה-CSS שנעשה בהם שימוש בכיתה הזו.

לחצן הפעולה הראשית

ה-<button> שגלוי בהתחלה וניתן למיקוד נכנס בתוך הקונטיינר עם שתי צורות תואמות של פינות עבור Focus, מעבירים את העכבר ואז אינטראקציות פעילות עם בתוך .gui-split-button.

הכלי לבדיקת כללי ה-CSS של רכיב הלחצן.

לחצן להחלפת המצב של החלון הקופץ

הלחצן 'חלון קופץ' רכיב התמיכה נועד להפעלה ולאזכור של רשימת הלחצנים המשניים. שימו לב שזה לא <button> ואי אפשר להתמקד בו. אבל, לפעמים הוא עוגן המיקום של .gui-popup והמארח הוא :focus-within בשימוש כדי להציג את החלון הקופץ.

הבודק מציג את כללי ה-CSS של הלחצן הקופץ של המחלקה בכיתה.

הכרטיס הקופץ

זהו צאצא של כרטיס צף לעוגן שלו .gui-popup-button, במיקום מוחלט ו- מתבצעת גלישת סמנטית של רשימת הלחצנים.

הבודק שמציג את כללי ה-CSS של החלון הקופץ של הכיתה

הפעולות המשניות

<button> שניתן להתמקד בו עם גופן קטן מעט יותר מהראשי לחצן פעולה כולל סמל סגנון ללחצן הראשי.

הכלי לבדיקת כללי ה-CSS של רכיב הלחצן.

מאפיינים מותאמים אישית

המשתנים הבאים עוזרים ליצור הרמוניית צבעים וכמקום מרכזי לשנות את הערכים שבשימוש בכל הרכיב.

@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)
})

סיכום

עכשיו, אחרי שהסברתי איך עשיתי את זה, איך היית? 🙂

בואו לגוון את הגישות שלנו ונלמד את כל הדרכים לבניית אתרים באינטרנט. אפשר ליצור הדגמה, לשלוח לי קישורים של שלחו לי ציוץ ואני אוסיף אותם לקטע 'רמיקסים' של הקהילה שבהמשך.

רמיקסים קהילתיים