סקירה כללית בסיסית על בניית רכיב הודעה קופצת (toast) שניתן להתאמה ונגיש.
בפוסט הזה אני רוצה לשתף את המחשבות שלי לגבי בניית רכיב טוסט. כדאי לנסות את ההדגמה.
אם אתם מעדיפים לצפות בסרטון, הנה גרסת YouTube של הפוסט הזה:
סקירה כללית
הודעות טוסט הן הודעות קצרות, פסיביות, אסינכרוניות ולא אינטראקטיביות שמוצגות למשתמשים. בדרך כלל משתמשים בהם כדפוס משוב בממשק כדי ליידע את המשתמש על התוצאות של פעולה מסוימת.
אינטראקציות
הודעות טוסט שונות מהתראות, התראות ו הודעות קופצות כי הן לא אינטראקטיביות. הן לא נועדו להיסגר או להישאר פתוחות. ההתראות מיועדות למידע חשוב יותר, להודעות סינכרוניות שנדרשת לגביהן אינטראקציה או להודעות ברמת המערכת (בניגוד לרמת הדף). הודעות קצרות הן פסיביות יותר מאסטרטגיות אחרות להצגת הודעות.
Markup
רכיב <output>
הוא בחירה טובה לטקסט הקצר כי הוא מוכרז לקוראי מסך. קוד 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
של אזור התצוגה, ואם נוספות עוד הודעות קופצות, הן מוצגות אחת מעל השנייה מהקצה של המסך.
מאגר תגים של ממשק משתמש גרפי
הקונטיינר של ההודעות הקצרות מבצע את כל עבודת הפריסה להצגת ההודעות הקצרות. הוא 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 שעוזר להתאים את האתר להגדרות ולפעולות של המשתמש.
מיכל טוסט
הודעות טוסט הן לא אינטראקטיביות, הקשה עליהן או החלקה עליהן לא עושות כלום, אבל כרגע הן צורכות אירועי הצבעה. כדי למנוע את ההשתלטות של ההודעות הקופצות על הקליקים, משתמשים ב-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)) }
}
לאחר מכן, רכיב הטוסט מגדיר את המשתנים ומתזמן את מסגרות המפתח.
.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 כדי לתזמן את היצירה, ההוספה וההסרה של ההודעות הקופצות בהתאם לאירועים שקורים אצל המשתמש. חוויית המפתח של רכיב הטוסט צריכה להיות מינימלית וקלה להתחלה, כמו בדוגמה הבאה:
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 של ההודעה הקופצת מתבצעת באמצעות הפונקציה createToast()
. הפונקציה דורשת טקסט מסוים להודעה הקופצת, יוצרת רכיב <output>
, מוסיפה לו כמה מחלקות ומאפיינים, מגדירה את הטקסט ומחזירה את הצומת.
const createToast = text => {
const node = document.createElement('output')
node.innerText = text
node.classList.add('gui-toast')
node.setAttribute('role', 'status')
return node
}
ניהול של הודעה אחת או יותר
JavaScript מוסיף עכשיו מאגר למסמך כדי להכיל הודעות קצרות, ומוכן להוסיף הודעות קצרות שנוצרו. הפונקציה addToast()
מתזמנת את הטיפול בהודעה אחת או בכמה הודעות קצרות. קודם בודקים את מספר ההודעות הקופצות ואם התנועה בסדר, ואז משתמשים במידע הזה כדי להוסיף את ההודעה הקופצת או להשתמש באנימציה מיוחדת כדי שההודעות הקופצות האחרות יפנו מקום להודעה הקופצת החדשה.
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 Grid היא זו שמבצעת את ההרמה של הפריסה. כשמוסיפים טוסט חדש, הוא מופיע בתחילת הרשימה עם רווח בינו לבין הטוסטים האחרים. בינתיים, אנימציה באינטרנט משמשת להנפשת הרכיב מהמיקום הישן.
איך משלבים את כל קוד ה-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()
. מכיוון שהשתמשתי בכמה אנימציות של פריים מרכזי להודעה הקופצת, כדי לוודא שכולן הסתיימו, צריך לבקש כל אחת מהן מ-JavaScript ולעקוב אחרי ההבטחות של כל אחת מהן finished
עד שהן יסתיימו.
allSettled
היא פועלת בשבילנו, ונפתרת כהשלמה אחרי שכל ההבטחות שלה
מתקיימות. השימוש ב-await Promise.allSettled()
מאפשר לשורה הבאה של הקוד להסיר את הרכיב בביטחון, ולהניח שההודעה הקופצת סיימה את מחזור החיים שלה. לבסוף, הקריאה ל-resolve()
מממשת את ההבטחה ברמה הגבוהה של Toast, כך שמפתחים יכולים לנקות או לבצע עבודה אחרת אחרי שההודעה הקופצת מוצגת.
export default 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: הדגמה וקוד