סקירה כללית בסיסית על אופן הבנייה של רכיב מתג רספונסיבי ונגיש.
בפוסט הזה אני רוצה לשתף מחשבות על דרך לבנות רכיבי מתג. רוצים לנסות את ההדגמה?
אם אתם מעדיפים לצפות בסרטון, הנה גרסת 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 כדי להוסיף שאילתת מדיה של משתמש עם העדפה לתנועה מופחתת למאפיין מותאם אישית, על סמך טיוטת המפרט הזו ב-Media Queries 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 ומצב מובנים. הדפדפן מנהל את המאפיין 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
, והרכיב הווירטואלי thumb משתמש בו כ-translateX
position:
.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, יכול להיות שגם לא יהיו. לאלעד היה רעיון מצוין להשתמש בערך של מאפיין מותאם אישית כדי להפוך אחוזים, וכך לאפשר ניהול של מיקום יחיד של הלוגיקה המותאמת אישית שלנו להמרות לוגיות. השתמשתי באותה טכניקה במעבר הזה ואני חושב שהיא עבדה מצוין:
.gui-switch {
--isLTR: 1;
&:dir(rtl) {
--isLTR: -1;
}
}
מאפיין מותאם אישית בשם --isLTR
מכיל בהתחלה את הערך 1
, כלומר הוא true
כי פריסת התוכן שלנו היא משמאל לימין כברירת מחדל. לאחר מכן, באמצעות מחלקת ה-CSS פסאודו :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:
מסומן
<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
scoped var(--thumb-position)
, ו-JavaScript יכול לספק ערך של סגנון מוטבע בקלט כדי לעדכן באופן דינמי את מיקום האגודל כך שייראה כאילו הוא עוקב אחרי תנועת המצביע. כשמשחררים את מצביע העכבר, מסירים את הסגנונות המוטבעים וקובעים אם הגרירה הייתה קרובה יותר למצב מושבת או למצב מופעל באמצעות המאפיין המותאם אישית --thumb-position
. זהו הבסיס של הפתרון: מעקב מותנה אחרי מיקומי מצביעים כדי לשנות מאפיינים מותאמים אישית של CSS.
מכיוון שהרכיב כבר היה פעיל ב-100% לפני שהסקריפט הזה הופיע, נדרשת עבודה רבה כדי לשמור על ההתנהגות הקיימת, כמו לחיצה על תווית כדי להחליף את הקלט. תג ה-JavaScript שלנו לא אמור להוסיף תכונות על חשבון תכונות קיימות.
touch-action
גרירה היא תנועה, תנועה מותאמת אישית, ולכן היא מתאימה מאוד לשימוש ביתרונות של touch-action
. במקרה של המתג הזה, הסקריפט שלנו צריך לטפל בתנועת החלקה אופקית, או שתנועת החלקה אנכית תתועד עבור גרסת המתג האנכית. בעזרת touch-action
אנחנו יכולים להגדיר לדפדפן אילו תנועות לטפל בהן ברכיב הזה, כך שסקריפט יוכל לטפל בתנועה בלי תחרות.
ה-CSS הבא מורה לדפדפן שכאשר תנועת הצבעה מתחילה בתוך פס המתג הזה, צריך לטפל בתנועות אנכיות ולא לעשות כלום עם תנועות אופקיות:
.gui-switch > input {
touch-action: pan-y;
}
התוצאה הרצויה היא תנועת אצבע אופקית שלא גורמת להזזה או לגלילה של הדף. אפשר להשתמש במצביע כדי לגלול אנכית מהקלט ולהמשיך לגלול את הדף, אבל גלילה אופקית מוגדרת באופן מותאם אישית.
כלי עזר לעיצוב ערכי פיקסלים
במהלך ההגדרה ובזמן הגרירה, צריך לאחזר ערכים מספריים שונים שחושבו מתוך רכיבים. פונקציות ה-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
זהו רגע מרכזי בלוגיקה של הגרירה, ויש כמה דברים שכדאי לשים לב אליהם ב-event handler של הפונקציה:
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
, והוא יכול להשתמש בו כדי להפוך את הלוגיקה ולהמשיך
לתמוך בפריסה מימין לשמאל. הערך 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()
}
האינטראקציה עם הרכיב הסתיימה, הגיע הזמן להגדיר את המאפיין checked של הקלט ולהסיר את כל אירועי התנועה. תיבת הסימון משתנה ל-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()
}
סיכום
המתג הקטן הזה דרש הכי הרבה עבודה מכל האתגרים של ממשק המשתמש הגרפי עד עכשיו! עכשיו כשאתה יודע איך עשיתי את זה, איך היית עושה את זה‽ 🙂
כדאי לגוון את הגישות שלנו וללמוד את כל הדרכים לבנות אתרים. אפשר ליצור סרטון הדגמה, לצייץ לי קישורים, ואוסיף אותו לקטע של רמיקסים מהקהילה שבהמשך!
רמיקסים מהקהילה
- @KonstantinRouda עם רכיב בהתאמה אישית: demo ו-code.
- @jhvanderschee עם לחצן: Codepen.
משאבים
אפשר למצוא את .gui-switch
קוד המקור ב-GitHub.