סקירה בסיסית של תהליך הבנייה של רכיב הודעה גמיש ונגיש.
בפוסט הזה אני רוצה לשתף לחשוב איך לבנות רכיב של טוסט. תוכלו לנסות את ההדגמה.
אם ברצונך ליצור סרטון, הנה גרסת 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
של אזור התצוגה, ואם מוסיפים עוד טוסטים הן נערמות בקצה המסך הזה.
מאגר 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;
}
השקה של GUI
בכל טוסט יש כמה 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;
}
השקה של GUI
תנו לטוסטים עיצוב גמיש בהיר או כהה עם מאפיינים מותאמים אישית, 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 של פול לואיס. הרעיון הוא לחשב את ההבדל בין המיקומים השונים של המכל, לפני ואחרי הוספת הטוסט החדש.
אפשר לחשוב על זה כמו לסמן איפה הטוסטר נמצא עכשיו, איפה הוא אמור להיות, ואז ליצור אנימציה מהמקום שבו הוא נמצא.
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')
מתבצעת, נוצר טוסט ומתווסף לדף (יכול להיות שאפילו הקונטיינר מכיל אנימציה כדי להתאים לטוסט החדש), ההבטחה מוחזרת וצפייה בסרטון החדש שנוצר כדי לראות את השלמת אנימציית ה-CSS (שלוש האנימציות של תמונות המפתח) כדי לפתור את תהליך ההבטחה.
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')
אם המפתח רוצה לנקות עבודות או כל דבר אחר, אחרי הצגת המצגת, הוא יוכל להשתמש באסינכרוני 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: הדגמה וקוד