בניית רכיב מתג

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

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

הדגמה

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

סקירה כללית

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

ההדגמה הזו משתמשת ב-<input type="checkbox" role="switch"> ברוב לפונקציונליות הזו, שלא צריך להשתמש ב-CSS או ב-JavaScript פונקציונליות מלאה ונגישות. טעינת CSS מאפשרת תמיכה מימין לשמאל שפות, אנכיות, אנימציה ועוד. טעינת JavaScript מבצעת את המעבר מוחשי לגרירה.

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

המשתנים הבאים מייצגים את החלקים השונים של המתג ואת אפשרויות. כמחלקה ברמה העליונה, .gui-switch מכיל מאפיינים מותאמים אישית שנמצאים בשימוש לרכיבי הצאצאים של הרכיבים, ובנקודות הכניסה בהתאמה אישית.

מעקב

האורך (--track-size), המרווח הפנימי ושני צבעים:

.gui-switch {
  --track-size: calc(var(--thumb-size) * 2);
  --track-padding: 2px;

  --track-inactive: hsl(80 0% 80%);
  --track-active: hsl(80 60% 45%);

  --track-color-inactive: var(--track-inactive);
  --track-color-active: var(--track-active);

  @media (prefers-color-scheme: dark) {
    --track-inactive: hsl(80 0% 35%);
    --track-active: hsl(80 60% 60%);
  }
}

תמונות ממוזערות

הגודל, צבע הרקע וצבעי האינטראקציה מדגישים:

.gui-switch {
  --thumb-size: 2rem;
  --thumb: hsl(0 0% 100%);
  --thumb-highlight: hsl(0 0% 0% / 25%);

  --thumb-color: var(--thumb);
  --thumb-color-highlight: var(--thumb-highlight);

  @media (prefers-color-scheme: dark) {
    --thumb: hsl(0 0% 5%);
    --thumb-highlight: hsl(0 0% 100% / 25%);
  }
}

תנועה מופחתת

כדי להוסיף כינוי ברור ולהפחית את חזרות, משתמש עם העדפת תנועה מופחתת אפשר להזין שאילתת מדיה בנכס מותאם אישית באמצעות PostCSS יישומי פלאגין שמבוססים על הטיוטה הזו מפרט בשאילתות מדיה 5:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

Markup

בחרתי לעטוף את הרכיב <input type="checkbox" role="switch"> שלי עם <label>, קיבוץ הקשרים כדי למנוע שיוך של תיבות סימון ותוויות אי בהירות, ובמקביל לתת למשתמש את היכולת לקיים אינטראקציה עם התווית להחליף את מצב הקלט.

א&#39;
תווית ותיבת סימון טבעיות ולא מעוצבות.

<label for="switch" class="gui-switch">
  Label text
  <input type="checkbox" role="switch" id="switch">
</label>

<input type="checkbox"> מגיע מראש עם API ומצב. הדפדפן מנהל את checked מאפיין וקלט אירועים כגון oninputו-onchanged.

פריסות

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

.gui-switch

הפריסה ברמה העליונה של המתג היא Flexbox. הכיתה .gui-switch מכילה את הנכסים המותאמים אישית הפרטיים והציבוריים שבהם הילדים משתמשים כדי לחשב שונות.

כלי פיתוח של Flexbox מציגים את הפריסה שלהם בשכבת-על של תווית אופקית ומתג
של השטח.

.gui-switch {
  display: flex;
  align-items: center;
  gap: 2ch;
  justify-content: space-between;
}

הרחבה ושינוי של פריסת Flexbox הם כמו שינוי של כל פריסה של Flexbox. לדוגמה, כדי להציב תוויות מעל או מתחת למתג, או כדי לשנות את flex-direction:

כלי פיתוח Flexbox יוצרים שכבת-על של תווית אנכית ומתג.

<label for="light-switch" class="gui-switch" style="flex-direction: column">
  Default
  <input type="checkbox" role="switch" id="light-switch">
</label>

מעקב

הקלט של תיבת הסימון מעוצב כרצועה של מתג על ידי הסרת הערך הרגיל appearance: checkbox ולציין גודל משלו במקום:

כלי הפיתוח לרשת מוצגים כשכבת-על של מסלול החלפת המצב, ומוצג בו מסלול הרשת בעל השם
אזורים עם השם &#39;track&#39;.

.gui-switch > input {
  appearance: none;

  inline-size: var(--track-size);
  block-size: var(--thumb-size);
  padding: var(--track-padding);

  flex-shrink: 0;
  display: grid;
  align-items: center;
  grid: [track] 1fr / [track] 1fr;
}

המסלול גם יוצר אזור של מסלולי רשת, אחד אחרי השני, לאגודל .

תמונות ממוזערות

הסגנון appearance: none מסיר גם את סימן הווי החזותי שסופק על ידי בדפדפן. רכיב זה משתמש רכיב מדומה וה:checked pseudo-class בקלט, החלפת האינדיקטור החזותי הזה.

האגודל הוא צאצא של פסאודו-אלמנט שמחובר ל-input[type="checkbox"] ול- מופיע על גבי הטראק במקום מתחתיו, על ידי הצהרה על אזור הרשת track:

כלי פיתוח שמציגים את המדומה של הרכיב כפי שממוקם בתוך רשת של CSS.

.gui-switch > input::before {
  content: "";
  grid-area: track;
  inline-size: var(--thumb-size);
  block-size: var(--thumb-size);
}

סגנונות

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

השוואה בין העיצוב הבהיר והעיצוב הכהה של המתג
.

סגנונות אינטראקציה במגע

בנייד, דפדפנים מוסיפים לתוויות הדגשות מסוג הקשה ותכונות לבחירת טקסט של קלטים. אלו השפיעו לרעה על הסגנון ועל המשוב החזותי של האינטראקציות המתג הזה נדרש. עם כמה שורות של CSS אפשר להסיר את האפקטים האלה ולהוסיף אותם סגנון cursor: pointer משלי:

.gui-switch {
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

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

מעקב

הסגנונות של הרכיב הזה מתמקדים בעיקר בצורה ובצבע שלו, שאליהם הוא ניגש מההורה .gui-switch דרך מדורג.

כדאי להחליף בין וריאציות עם גדלים וצבעים של טראקים בהתאמה אישית.

.gui-switch > input {
  appearance: none;
  border: none;
  outline-offset: 5px;
  box-sizing: content-box;

  padding: var(--track-padding);
  background: var(--track-color-inactive);
  inline-size: var(--track-size);
  block-size: var(--thumb-size);
  border-radius: var(--track-size);
}

מגוון רחב של אפשרויות להתאמה אישית במתג הנגישות מאפיינים מותאמים אישית. border: none התווסף כי appearance: none לא להסיר את הגבולות מתיבות הסימון בכל הדפדפנים.

תמונות ממוזערות

אלמנט האגודל כבר מופיע ב-track השמאלי, אך יש צורך בסגנונות של עיגול:

.gui-switch > input::before {
  background: var(--thumb-color);
  border-radius: 50%;
}

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

אינטראקציה

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

.gui-switch > input::before {
  box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);

  @media (--motionOK) { & {
    transition:
      transform var(--thumb-transition-duration) ease,
      box-shadow .25s ease;
  }}
}

המיקום של האגודל

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

הרכיב input הוא הבעלים של משתנה המיקום --thumb-position והאגודל רכיב פסאודו משתמש בו כמיקום translateX:

.gui-switch > input {
  --thumb-position: 0%;
}

.gui-switch > input::before {
  transform: translateX(var(--thumb-position));
}

עכשיו אנחנו יכולים לשנות את --thumb-position בהגדרות של שירות ה-CSS ושל המחלקות המדומה סופקו באלמנטים של תיבות סימון. מכיוון שהגדרנו את transition: transform var(--thumb-transition-duration) ease בשלב מוקדם יותר של הרכיב הזה, השינויים האלה עשוי להוסיף אנימציה כאשר משנים אותו:

/* positioned at the end of the track: track length - 100% (thumb width) */
.gui-switch > input:checked {
  --thumb-position: calc(var(--track-size) - 100%);
}

/* positioned in the center of the track: half the track - half the thumb */
.gui-switch > input:indeterminate {
  --thumb-position: calc(
    (var(--track-size) / 2) - (var(--thumb-size) / 2)
  );
}

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

לאורך

התמיכה בוצעה באמצעות תכונת שינוי של מחלקה -vertical, שמוסיפה רוטציה עם שירות CSS מבצע טרנספורמציה לרכיב input.

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

.gui-switch.-vertical {
  min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));

  & > input {
    transform: rotate(-90deg);
  }
}

(RTL) מימין לשמאל

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

.gui-switch {
  --isLTR: 1;

  &:dir(rtl) {
    --isLTR: -1;
  }
}

מאפיין מותאם אישית שנקרא --isLTR מכיל בהתחלה את הערך 1, כלומר true מכיוון שהפריסה שלנו היא משמאל לימין כברירת מחדל. לאחר מכן, באמצעות שירות ה-CSS פסאודו class :dir(), הערך מוגדר ל--1 כשהרכיב נמצא בפריסה מימין לשמאל.

כדי להחיל את --isLTR בתוך טרנספורמציה, צריך להשתמש בו בתוך calc():

.gui-switch.-vertical > input {
  transform: rotate(-90deg);
  transform: rotate(calc(90deg * var(--isLTR) * -1));
}

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

צריך לעדכן גם את הטרנספורמציות translateX ברכיב לדוגמה חשבון לדרישה ההפוך:

.gui-switch > input:checked {
  --thumb-position: calc(var(--track-size) - 100%);
  --thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}

.gui-switch > input:indeterminate {
  --thumb-position: calc(
    (var(--track-size) / 2) - (var(--thumb-size) / 2)
  );
  --thumb-position: calc(
   ((var(--track-size) / 2) - (var(--thumb-size) / 2))
    * var(--isLTR)
  );
}

הגישה הזו לא תצליח לתת מענה לכל הצרכים של קונספטים כמו שירות CSS לוגי משתנה, הוא מציע עקרונות DRY במקרים שונים.

מדינות

השימוש בפלטפורמה input[type="checkbox"] לא יושלם בלי טיפול במצבים הבאים: :checked, :disabled, :indeterminate וגם :hover. :focus נשארה לבדה בכוונה, עם התאמה שבוצעה לקיזוז שלה בלבד; טבעת המיקוד נראתה מעולה ב-Firefox Safari:

צילום מסך של טבעת המיקוד שמתמקד במתג ב-Firefox וב-Safari.

מסומן

<label for="switch-checked" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>

המצב הזה מייצג את המצב on. במצב הזה, הקלט "track" הרקע מוגדר לצבע הפעיל ומיקום האגודל מוגדר סוף".

.gui-switch > input:checked {
  background: var(--track-color-active);
  --thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}

מושבת

<label for="switch-disabled" class="gui-switch">
  Default
  <input type="checkbox" role="switch" id="switch-disabled" disabled="true">
</label>

לחצן :disabled לא רק נראה שונה מבחינה חזותית, אלא גם צריך לגרום אלמנט לא ניתן לשינוי.לא ניתנים לשינוי של אינטראקציה מהדפדפן, אבל מצבים ויזואליים צריכים סגנונות בגלל השימוש ב-appearance: none.

.gui-switch > input:disabled {
  cursor: not-allowed;
  --thumb-color: transparent;

  &::before {
    cursor: not-allowed;
    box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 50%);

    @media (prefers-color-scheme: dark) { & {
      box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 50%);
    }}
  }
}

המתג עם הסגנון הכהה מושבת, מסומן ולא מסומן
.

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

לא קבוע

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

קשה להגדיר תיבת סימון כדי לקבוע מועד, רק JavaScript יכול להגדיר אותה:

<label for="switch-indeterminate" class="gui-switch">
  Indeterminate
  <input type="checkbox" role="switch" id="switch-indeterminate">
  <script>document.getElementById('switch-indeterminate').indeterminate = true</script>
</label>

מצב ביניים שבו יש את
באמצע, כדי לציין שהם לא חד-משמעיים.

מאחר שהמדינה, מבחינתי, צנועה ומזמינה, הרגשתי מקובל להציג מיקום אגודל המתג באמצע:

.gui-switch > input:indeterminate {
  --thumb-position: calc(
    calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
    * var(--isLTR)
  );
}

העברת העכבר מלמעלה

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

ה'הדגשה' האפקט מסתיים ב-box-shadow. כשמעבירים את העכבר מעל קלט שאינו מושבת, מגדילים את הערך של --highlight-size. אם התנועה מקובלת על המשתמש, אנחנו מחליפים את box-shadow ונראה שהוא גדל. אם התנועה לא מקובלת עליו, ההדגשה תופיע מיד:

.gui-switch > input::before {
  box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);

  @media (--motionOK) { & {
    transition:
      transform var(--thumb-transition-duration) ease,
      box-shadow .25s ease;
  }}
}

.gui-switch > input:not(:disabled):hover::before {
  --highlight-size: .5rem;
}

JavaScript

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

סימוני 'אהבתי' שניתן לגרור

הרכיב המדומה מקבל את המיקום שלו מ-.gui-switch > input בהיקף var(--thumb-position), JavaScript יכול לספק ערך סגנון מוטבע את הקלט כדי לעדכן באופן דינמי את מיקום האגודל, כך שייראה כאילו הוא תנועת הסמן. לאחר שחרור הסמן, מסירים את הסגנונות המוטבעים ואז לקבוע אם הגרירה הייתה קרובה יותר למצב כבוי או יותר באמצעות המאפיין המותאם אישית --thumb-position זה עמוד התווך של הפתרון; אירועי תנועה מעקב מותנה אחר מיקומי הסמן כדי לשנות מאפיינים מותאמים אישית של CSS.

הרכיב כבר היה 100% פונקציונליות לפני שהסקריפט הזה הוצג נדרש לא מעט עבודה כדי לשמור על ההתנהגות הקיימת, כמו לחיצה על תווית כדי להחליף את מצב הקלט. קוד ה-JavaScript שלנו לא אמור להוסיף תכונות על חשבון התכונות הקיימות.

touch-action

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

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

.gui-switch > input {
  touch-action: pan-y;
}

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

כלי עזר לסגנון של ערך Pixel

בתהליך ההגדרה ובמהלך הגרירה, יהיה צורך לשלוף ערכים שונים של מספרים שמחושבים מאלמנטים. פונקציות ה-JavaScript הבאות מחזירות ערכי פיקסלים מחושבים שניתנו במאפיין CSS. הוא משמש בסקריפט ההגדרה כך getStyle(checkbox, 'padding-left')

​​const getStyle = (element, prop) => {
  return parseInt(window.getComputedStyle(element).getPropertyValue(prop));
}

const getPseudoStyle = (element, prop) => {
  return parseInt(window.getComputedStyle(element, ':before').getPropertyValue(prop));
}

export {
  getStyle,
  getPseudoStyle,
}

שימו לב איך window.getComputedStyle() מקבל ארגומנט שני, רכיב פסאודו של יעד. די מדליק ש-JavaScript יכול לקרוא כל כך הרבה ערכים מאלמנטים, אפילו מאלמנטים של פסאודו.

dragging

זהו רגע מרכזי בלוגיקת הגרירה, ויש כמה דברים שחשוב לשים לב אליהם מגורם המטפל באירועים של הפונקציה:

const dragging = event => {
  if (!state.activethumb) return

  let {thumbsize, bounds, padding} = switches.get(state.activethumb.parentElement)
  let directionality = getStyle(state.activethumb, '--isLTR')

  let track = (directionality === -1)
    ? (state.activethumb.clientWidth * -1) + thumbsize + padding
    : 0

  let pos = Math.round(event.offsetX - thumbsize / 2)

  if (pos < bounds.lower) pos = 0
  if (pos > bounds.upper) pos = bounds.upper

  state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
}

הגיבור של התסריט הוא state.activethumb, העיגול הקטן הזה שהסקריפט הזה את המיקום יחד עם מצביע. האובייקט switches הוא Map() שבו המפתחות הם של .gui-switch והערכים הם גבולות וגדלים שנשמרו במטמון ביעילות התסריט. מתבצע עיבוד מימין לשמאל באמצעות אותו מאפיין מותאם אישית ששירות ה-CSS הוא --isLTR, ויכול להשתמש בו כדי להפוך לוגיקה ולהמשיך שתומך ב-RTL. גם הערך של event.offsetX חשוב, כי הוא מכיל דלתא שימושי למיקום האגודל.

state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)

השורה האחרונה של ה-CSS מגדירה את המאפיין המותאם אישית שבו משתמש הרכיב הראשי. הזה אחרת, הקצאת הערכים תשתנה עם הזמן, אבל מצביע קודם האירוע הגדיר באופן זמני את הערך --thumb-transition-duration לערך 0s, והמערכת מסירה את מה האינטראקציה הייתה איטית.

dragEnd

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

window.addEventListener('pointerup', event => {
  if (!state.activethumb) return

  dragEnd(event)
})

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

const dragEnd = event => {
  if (!state.activethumb) return

  state.activethumb.checked = determineChecked()

  if (state.activethumb.indeterminate)
    state.activethumb.indeterminate = false

  state.activethumb.style.removeProperty('--thumb-transition-duration')
  state.activethumb.style.removeProperty('--thumb-position')
  state.activethumb.removeEventListener('pointermove', dragging)
  state.activethumb = null

  padRelease()
}

האינטראקציה עם האלמנט הסתיימה, הגיע הזמן להגדיר את הקלט שנבדק מאפיין ולהסיר את כל אירועי התנועה. תיבת הסימון תשתנה עם state.activethumb.checked = determineChecked()

determineChecked()

הפונקציה הזו, שנקראת על ידי dragEnd, קובעת את המיקום של האגודל בתוך גבולות הטראק, ומחזירה את הערך true אם הוא שווה ל- או מעל באמצע המסלול:

const determineChecked = () => {
  let {bounds} = switches.get(state.activethumb.parentElement)

  let curpos =
    Math.abs(
      parseInt(
        state.activethumb.style.getPropertyValue('--thumb-position')))

  if (!curpos) {
    curpos = state.activethumb.checked
      ? bounds.lower
      : bounds.upper
  }

  return curpos >= bounds.middle
}

מחשבות נוספות

תנועת הגרירה גרמה למעט חוב בקוד בגלל מבנה ה-HTML הראשוני נבחר, בעיקר ועוטף את הקלט בתווית. את התווית, כהורה יקבל אינטראקציות של קליקים אחרי הקלט. בסוף המשימה אירוע מסוג dragEnd, יכול להיות שהבחנת ב-padRelease() כאירוע מוזר מותאמת אישית.

const padRelease = () => {
  state.recentlyDragged = true

  setTimeout(_ => {
    state.recentlyDragged = false
  }, 300)
}

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

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

סוג ה-JavaScript הזה הוא הכי פחות אהוב עליי, אני לא רוצה לנהל מבעבוע של אירוע מותנה:

const preventBubbles = event => {
  if (state.recentlyDragged)
    event.preventDefault() && event.stopPropagation()
}

סיכום

רכיב ההחלפה הזה הוא בסופו של דבר היצירה הקשה ביותר מכל אתגרי GUI עד עכשיו! עכשיו, אחרי שהסברתי איך עשיתי את זה, איך היית? 🙂

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

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

משאבים

איתור קוד המקור של .gui-switch באתר מ-GitHub.