סקירה בסיסית של תהליך הבנייה של רכיב מתג נגיש ורספונסיבי.
בפוסט הזה אני רוצה לשתף חשיבה על דרך לבניית רכיבי מתג. רוצים לנסות את ההדגמה?
אם ברצונך ליצור סרטון, הנה גרסת 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>
, בקיבוץ כל הקשרים ביניהם כדי למנוע אי בהירות לגבי השיוך של תיבות הסימון והתוויות, ובמקביל לאפשר למשתמשים אינטראקציה עם התווית להחליף את מצב הקלט.
<label for="switch" class="gui-switch">
Label text
<input type="checkbox" role="switch" id="switch">
</label>
<input type="checkbox">
מוגדר מראש עם API וstate. הדפדפן מנהל את המאפיין checked
ואת אירועי הקלט, כמו oninput
ו-onchanged
.
פריסות
Flexbox, grid ומאפיינים מותאמים אישית הם חיוניים לשמירה על הסגנונות של הרכיב הזה. הם מרכזים את הערכים, נותנים שמות לחישובים או לאזורים שאינם ברורים, ומאפשרים ממשק API קטן של מאפיין מותאם אישית, שמאפשר התאמה אישית קלה של רכיבים.
.gui-switch
הפריסה ברמה העליונה של המתג היא flexbox. הכיתה .gui-switch
מכילה את המאפיינים הפרטיים והציבוריים המותאמים אישית שבהם הילדים משתמשים כדי לחשב את הפריסות שלהם.
.gui-switch {
display: flex;
align-items: center;
gap: 2ch;
justify-content: space-between;
}
הרחבה ושינוי של פריסת flexbox הם כמו שינוי של כל פריסת flexbox.
לדוגמה, כדי להציב תוויות מעל או מתחת למתג, או כדי לשנות את flex-direction
:
<label for="light-switch" class="gui-switch" style="flex-direction: column">
Default
<input type="checkbox" role="switch" id="light-switch">
</label>
טראק
הקלט של תיבת הסימון מעוצב כמסלול מתגים. לשם כך, מסירים
את השדה appearance: checkbox
הרגיל שלו ומציינים במקום זאת את הגודל שלו:
.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
פסאודו-סיווג בקלט כדי להחליף את האינדיקטור החזותי הזה.
האגודל הוא פסאודו-רכיב צאצא המחובר ל-input[type="checkbox"]
ויושב מעל לטראק במקום מתחתיו על ידי תביעת שטח הרשת
track
:
.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
כי הפריסה שלנו מופיעה משמאל לימין כברירת מחדל. לאחר מכן, באמצעות פסאודו המחלקה :dir()
של CSS, הערך מוגדר ל--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, אבל היא כן מציעה כמה עקרונות יבשיים לתרחישים רבים לדוגמה.
מדינות
כדי להשתמש בגרסה המובנית של input[type="checkbox"]
בלי לטפל במצבים השונים שהיא יכולה להיות: :checked
, :disabled
, :indeterminate
ו-:hover
. :focus
נשארה לבדה בכוונה, עם התאמה רק לאחר קיזוז. טבעת המיקוד נראתה נהדר ב-Firefox וב-Safari:
מסומנת
<label for="switch-checked" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>
המדינה הזו מייצגת את המצב on
. במצב כזה, הרקע של 'הטראק' שמוגדר כקלט מוגדר לצבע הפעיל ומיקום האגודל מוגדר ל'סוף'.
.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 יש את זה כמו שצריך עם המתג שלהם, ואפשר לגרור אותם מצד לצד, וזה מאוד שמח לקבל את האפשרות. לעומת זאת, אם יתבצע ניסיון גרירה ושום דבר לא יקרה, רכיב בממשק המשתמש ירגיש לא פעיל.
אגודלים שניתן לגרור
פסאודו-הרכיב האגודל מקבל את המיקום שלו מה-var(--thumb-position)
בהיקף .gui-switch > input
.
ה-JavaScript יכול לספק ערך סגנון בתוך השורה בקלט כדי לעדכן באופן דינמי את מיקום הסימנייה כך שייראה כאילו הוא עוקב אחר תנועת המצביע. לאחר שחרור הסמן, מסירים את הסגנונות המוטבעים וקובעים אם הגרירה הייתה קרובה יותר להשבתה או להפעלה באמצעות המאפיין המותאם אישית --thumb-position
. זהו המוקד של הפתרון: אירועי הפניה, שעוקבים באופן מותנה אחר מיקומי המצביעים כדי לשנות מאפיינים מותאמים אישית של CSS.
מכיוון שהרכיב כבר היה 100% פעיל לפני שהסקריפט הזה הופיע, זה דורש הרבה עבודה כדי לשמור על ההתנהגות הקיימת, למשל לחיצה על תווית כדי להחליף את מצב הקלט. ה-JavaScript שלנו לא אמור להוסיף תכונות על חשבון התכונות הקיימות.
touch-action
הגרירה היא תנועה מותאמת אישית, ולכן היא מועמדת מעולה
ליתרונות של touch-action
. במקרה של המתג הזה, הסקריפט שלנו צריך לטפל בתנועה אופקית, או בתנועה אנכית שתועדה לווריאנט של המתג האנכי. בעזרת touch-action
אנחנו יכולים להנחות את הדפדפן באילו תנועות לטפל ברכיב הזה, כדי שסקריפט יוכל להתמודד עם תנועה ללא תחרות.
שירות ה-CSS הבא מורה לדפדפן שכאשר מתחילה תנועת מצביע מתוך מסלול המתג הזה, מטפלים בתנועות אנכיות, ולא לעשות דבר עם תנועות אופקיות:
.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! עכשיו, אחרי שאת יודעת איך עשיתי את זה, איך היית רוצה ‽ 🙂
בואו נגוון את הגישות שלנו ונלמד את כל הדרכים לבנות באינטרנט. צור הדגמה (דמו), ציוץ לי קישורים ואני אוסיף אותה לקטע 'רמיקסים של הקהילה' למטה!
רמיקסים של הקהילה
- @KonstantinRouda עם רכיב בהתאמה אישית: demo ו-code.
- @jhvanderschee עם לחצן: Codepen.
משאבים
מאתרים את קוד המקור ב-GitHub.gui-switch
.