סקירה כללית בסיסית על יצירת רכיב טוסט מותאם ונגיש.
בפוסט הזה אני רוצה לשתף את הדרך שבה פיתחתי רכיב של הודעה קופצת. כדאי לנסות את ההדגמה.
אם אתם מעדיפים סרטון, הנה גרסה של הפוסט הזה ב-YouTube:
סקירה כללית
הודעות טוסט הן הודעות קצרות, פסיביות ולא אינטראקטיביות למשתמשים. בדרך כלל הן משמשות כתצוגה של משוב בממשק כדי להודיע למשתמש על תוצאות הפעולה.
אינטראקציות
ההודעות הקצרות שמוצגות בחלק העליון של המסך שונות מהתראות, מהתראות ומהנחיות כי הן לא אינטראקטיביות. הן לא נועדו להסרה או להישארות. התראות נועדו למידע חשוב יותר, להודעות סינכרוניות שדורשות אינטראקציה או להודעות ברמת המערכת (בניגוד לרמת הדף). הודעות טוסט הן פחות אקטיביות משיטות אחרות להצגת הודעות.
Markup
הרכיב <output>
הוא בחירה טובה להודעת ה-Toast כי הוא מופיע בקריינות של קוראי המסך. HTML תקין מספק בסיס בטוח שאפשר לשפר באמצעות JavaScript ו-CSS, ואנחנו נשתמש בהרבה JavaScript.
הודעה קופצת
<output class="gui-toast">Item added to cart</output>
כדי שהיא תהיה כלולה יותר, אפשר להוסיף את role="status"
. כך אפשר להשתמש באפשרות חלופית אם הדפדפן לא מקצה לרכיבי <output>
את התפקיד המשתמעים בהתאם למפרט.
<output role="status" class="gui-toast">Item added to cart</output>
מאגר של הודעות טוסט
אפשר להציג יותר מעדכון אחד בו-זמנית. כדי לתזמר כמה הודעות קטנות, משתמשים בקונטיינר. המארז הזה גם מטפל במיקום של ההתראות במסך.
<section class="gui-toast-group">
<output role="status">Wizard Rose added to cart</output>
<output role="status">Self Watering Pot added to cart</output>
</section>
פריסות
בחרתי להצמיד את ההתראות הקצרות לinset-block-end
של אזור התצוגה, ואם מתווספות עוד התראות קצרות, הן נערמות מאותו קצה מסך.
מאגר GUI
קונטיינר ההודעות מגדיר את כל הפרמטרים של הפריסה של ההודעות. הוא fixed
לחלון התצוגה, והוא משתמש במאפיין הלוגי inset
כדי לציין לאילו קצוות להצמיד אותו, וגם קצת padding
מאותו קצה block-end
.
.gui-toast-group {
position: fixed;
z-index: 1;
inset-block-end: 0;
inset-inline: 0;
padding-block-end: 5vh;
}
בנוסף למיקום שלו באזור התצוגה, מאגר ההודעות הוא מאגר רשת שיכול ליישר ולהפיץ הודעות. הפריטים מתמקמים במרכז כקבוצה באמצעות justify-content
ובמרכז בנפרד באמצעות justify-items
.
מוסיפים קצת gap
כדי שהטוסטים לא יגעו זה בזה.
.gui-toast-group {
display: grid;
justify-items: center;
justify-content: center;
gap: 1vh;
}
הודעה קופצת בממשק משתמש גרפי
בתיעוד אירוע יש padding
, פינות מעוגלות יותר עם border-radius
ופונקציה min()
שבעזרתה אפשר להתאים את הגודל לנייד ולמחשב. הגודל הרספונסיבי בקוד ה-CSS הבא מונע מחלונות הקטעים להרחיב ליותר מ-90% מאזור התצוגה או מ-25ch
.
.gui-toast {
max-inline-size: min(25ch, 90vw);
padding-block: .5ch;
padding-inline: 1ch;
border-radius: 3px;
font-size: 1rem;
}
סגנונות
אחרי שמגדירים את הפריסה והמיקום, מוסיפים CSS שעוזר להתאים את הרכיב להגדרות ולאינטראקציות של המשתמשים.
מאגר של הודעות Toast
הודעות הטוסט לא אינטראקטיביות, והקשה או החלקה עליהן לא גורמות לשום דבר, אבל הן צורכות כרגע אירועי סמן. כדי למנוע מההתראות לגנוב קליקים, מוסיפים את הקוד הבא ל-CSS.
.gui-toast-group {
pointer-events: none;
}
הודעה קופצת בממשק משתמש גרפי
אפשר להגדיר לעדכונים האלה עיצוב בהיר או כהה עם מאפיינים מותאמים אישית, HSL ושאילתת מדיה לפי העדפה.
.gui-toast {
--_bg-lightness: 90%;
color: black;
background: hsl(0 0% var(--_bg-lightness) / 90%);
}
@media (prefers-color-scheme: dark) {
.gui-toast {
color: white;
--_bg-lightness: 20%;
}
}
Animation
הודעה חדשה אמורה להופיע עם אנימציה כשהיא נכנסת למסך.
כדי להתאים את התנועה לתנועה מופחתת, מגדירים את הערכים של translate
ל-0
כברירת מחדל, אבל מעדכנים את ערך התנועה לאורך מסוים בשאילתת מדיה של העדפת תנועה . לכולם מוצגת אנימציה כלשהי, אבל רק לחלק מהמשתמשים ההודעה נוסעת למרחק.
אלה נקודות המפתח ששימשו ליצירת האנימציה של ההודעה הקופצת. ה-CSS ישלוט בכניסה, בהמתנה וביציאה של ההודעה הקופצת, והכול באנימציה אחת.
@keyframes fade-in {
from { opacity: 0 }
}
@keyframes fade-out {
to { opacity: 0 }
}
@keyframes slide-in {
from { transform: translateY(var(--_travel-distance, 10px)) }
}
לאחר מכן, רכיב ה-toast מגדיר את המשתנים ומארגן את נקודות ה-keyframe.
.gui-toast {
--_duration: 3s;
--_travel-distance: 0;
will-change: transform;
animation:
fade-in .3s ease,
slide-in .3s ease,
fade-out .3s ease var(--_duration);
}
@media (prefers-reduced-motion: no-preference) {
.gui-toast {
--_travel-distance: 5vh;
}
}
JavaScript
אחרי שמסיימים ליצור את הסגנונות ואת ה-HTML שקורא המסך יכול לקרוא, צריך להשתמש ב-JavaScript כדי לתזמור את היצירה, ההוספה והמחיקה של הודעות הטוסט על סמך אירועים של משתמשים. חוויית המפתח של רכיב ה-Toast צריכה להיות מינימלית וקלה להתחלה, כך:
import Toast from './toast.js'
Toast('My first toast')
יצירת קבוצת ההודעות הקופצות והודעות הקופצות
כשמודול ההודעה הקופצת נטען מ-JavaScript, הוא צריך ליצור מאגר של הודעות קופצות ולהוסיף אותו לדף. בחרתי להוסיף את הרכיב לפני body
, כך שסביר להניח שלא יהיו בעיות של z-index
בערימה כי הקונטיינר נמצא מעל הקונטיינר של כל רכיבי הגוף.
const init = () => {
const node = document.createElement('section')
node.classList.add('gui-toast-group')
document.firstElementChild.insertBefore(node, document.body)
return node
}
הפונקציה init()
נקראת באופן פנימי בתוך המודול, ומטמינת את הרכיב בתור Toaster
:
const Toaster = init()
יצירת רכיב HTML של Toast מתבצעת באמצעות הפונקציה createToast()
. הפונקציה מקבלת טקסט ל-Toast, יוצרת רכיב <output>
, מעטרת אותו בכמה כיתות ומאפיינים, מגדירה את הטקסט ומחזירה את הצומת.
const createToast = text => {
const node = document.createElement('output')
node.innerText = text
node.classList.add('gui-toast')
node.setAttribute('role', 'status')
return node
}
ניהול של הודעה אחת או כמה הודעות
JavaScript מוסיף עכשיו למסמך מאגר לשימוש ב-Toasts, ומאפשר להוסיף הודעות Toast שנוצרו. הפונקציה addToast()
מנהלת את הטיפול בהודעת Toast אחת או יותר. קודם בודקים את מספר ההתראות הקופצות ואת תנועת ההתראות, ואז משתמשים במידע הזה כדי להוסיף את ההתראה הקופצת או להוסיף אנימציה מיוחדת כדי שההתראות הקופצות האחרות ייראו כאילו הן "מפנות מקום" להודעה הקופצת החדשה.
const addToast = toast => {
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
)
Toaster.children.length && motionOK
? flipToast(toast)
: Toaster.appendChild(toast)
}
כשמוסיפים את ההודעה הראשונה, Toaster.appendChild(toast)
מוסיף הודעה לדף שמפעילה את אנימציות ה-CSS: אנימציה של כניסה, המתנה 3s
, אנימציה של יציאה.
flipToast()
נקראת כשיש הודעות קיימות, באמצעות טכניקה שנקראת FLIP של Paul Lewis. הרעיון הוא לחשב את ההבדל במיקומים של הקונטיינר, לפני ואחרי הוספת ההודעה החדשה.
אפשר לחשוב על זה כסימון המיקום הנוכחי של המכשיר, סימון המיקום הרצוי שלו ואז הוספת אנימציה מהמיקום הקודם למיקום הנוכחי.
const flipToast = toast => {
// FIRST
const first = Toaster.offsetHeight
// add new child to change container size
Toaster.appendChild(toast)
// LAST
const last = Toaster.offsetHeight
// INVERT
const invert = last - first
// PLAY
const animation = Toaster.animate([
{ transform: `translateY(${invert}px)` },
{ transform: 'translateY(0)' }
], {
duration: 150,
easing: 'ease-out',
})
}
רשת CSS היא זו שמאפשרת להציג את הפריסה. כשמוסיפים הודעה חדשה, היא מופיעה בתחילת התצוגה ומרווחים אותה מההודעות האחרות. בינתיים, אנימציה לאינטרנט משמשת כדי להציג אנימציה של המאגר מהמיקום הישן.
איך משלבים את כל ה-JavaScript
כשמפעילים את Toast('my first toast')
, נוצרת הודעת טוסט, היא מתווספת לדף (יכול להיות שגם הקונטיינר יתנועע כדי להתאים להודעת הטוסט החדשה), מוחזר promise ומופעל מעקב אחרי הודעת הטוסט שנוצרה כדי לבדוק אם האנימציה ב-CSS הושלמה (שלושת האנימציות של נקודות המפתח) לצורך פתרון ה-promise.
const Toast = text => {
let toast = createToast(text)
addToast(toast)
return new Promise(async (resolve, reject) => {
await Promise.allSettled(
toast.getAnimations().map(animation =>
animation.finished
)
)
Toaster.removeChild(toast)
resolve()
})
}
לדעתי, החלק המבלבל בקוד הזה הוא בפונקציה Promise.allSettled()
ובמיפוי toast.getAnimations()
. מכיוון שהשתמשתי בכמה אנימציות של נקודות מפתח להודעת ה-Toast, כדי לוודא שהן הסתיימו צריך לבקש כל אחת מהן מ-JavaScript ולבדוק את השלמת כל אחת מהfinished
שלהן.
allSettled
עושה את זה בשבילנו, והוא מסתיים כשהוא מקיים את כל ההבטחות שלו. השימוש ב-await Promise.allSettled()
מאפשר לשורה הבאה של הקוד להסיר את הרכיב בביטחון ולהניח שהטוסט סיים את מחזור החיים שלו. לבסוף, הקריאה ל-resolve()
ממלאת את ההתחייבות ברמה הגבוהה של Toast, כך שמפתחים יכולים לנקות או לבצע משימות אחרות אחרי שה-Toast מוצג.
export default Toast
לבסוף, הפונקציה Toast
מיוצאת מהמודול כדי שסקריפטים אחרים יוכלו לייבא אותה ולהשתמש בה.
שימוש ברכיב Toast
כדי להשתמש בהודעת ה-Toast, או בחוויית המפתח של ה-Toast, מייבאים את הפונקציה Toast
ומפעילים אותה עם מחרוזת של הודעה.
import Toast from './toast.js'
Toast('Wizard Rose added to cart')
אם המפתח רוצה לבצע פעולות ניקוי או כל פעולה אחרת אחרי שההודעה מופיעה, הוא יכול להשתמש ב-async וב-await.
import Toast from './toast.js'
async function example() {
await Toast('Wizard Rose added to cart')
console.log('toast finished')
}
סיכום
עכשיו, אחרי שסיפרתי לך איך עשיתי את זה, איך היית עושה את זה? 🙂
נרחיב את הגישות שלנו ונלמד את כל הדרכים לפיתוח באינטרנט. אתם יכולים ליצור גרסת דמו, לשלוח לי קישורים בטוויטר ואוסיף אותה לקטע 'רמיקסים של הקהילה' שבהמשך.
רמיקסים של הקהילה
- @_developit עם HTML/CSS/JS: דוגמה וקוד
- Joost van der Schee עם HTML/CSS/JS: דוגמה וקוד