שיטות מומלצות לטעינה מדורגת

לטעינה עצלה של תמונות וסרטונים יש יתרונות חיוביים ומדידים, אבל לא צריך להתייחס בקלי קלות. אם תעשו זאת בצורה שגויה, יכולות להיות לכך השלכות לא רצויות. לכן, חשוב להביא בחשבון את החששות הבאים.

טיפול בקיפול

נכון שזה מפתה לטעון באופן הדרגתי כל משאב מדיה בודד בדף באמצעות JavaScript, אבל עליכם לעמוד בפני פיתוי זה. כל מה שנמצא בחלק העליון והקבוע לא אמור להיטען בהדרגה. משאבים כאלה צריכים להיחשב כנכסים קריטיים, ולכן יש לטעון אותם כרגיל.

טעינה עצלה משהה את טעינת המשאבים עד שה-DOM הופך לאינטראקטיבי, אחרי שהסקריפטים מסיימים את הטעינה ומתחילים בביצוע. בתמונות בחלק הנגלל, זה בסדר, אבל משאבים קריטיים בחלק העליון והקבוע צריך לטעון באמצעות אלמנט <img> הסטנדרטי כדי שהם יוצגו בהקדם האפשרי.

כמובן, מיקום החלק העליון של הדף לא כל כך ברור בימים אלה, שבהם אתרים נצפים בכל כך הרבה מסכים בגדלים שונים. במכשירים ניידים, החלק העליון והקבוע של מחשב נייד עשוי להימצא מתחת אליו. אין עצות מובטחות לטיפול במקרים כאלה בצורה אופטימלית בכל מצב. צריך לערוך את רשימת הנכסים הקריטיים של הדף ולטעון את התמונות בשיטה האופיינית.

בנוסף, ייתכן שלא תרצו להיות קפדניים כל כך לגבי קו הקיפול בתור הסף להפעלה של טעינה עצלה. מומלץ להגדיר אזור חיץ במרחק מסוים מהחלק הנגלל כדי שהתמונות יתחילו להיטען הרבה לפני שהמשתמש יגלול אותן לאזור התצוגה. לדוגמה, כאשר יוצרים מכונה חדשה של IntersectionObserver, ה-Intersection Writeer API מאפשר לציין מאפיין rootMargin באובייקט אפשרויות. כך נוצר לרכיבים מאגר נתונים זמני, שמפעיל התנהגות טעינה עצלה לפני שהאלמנט נמצא באזור התצוגה:

let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
  // lazy-loading image code goes here
}, {
  rootMargin: "0px 0px 256px 0px"
});

אם הערך של rootMargin דומה לערכים שציינתם במאפיין CSS margin, הסיבה לכך היא שהוא כן! במקרה הזה, השוליים התחתון של הרכיב שנצפה (ברירת המחדל של אזור התצוגה של הדפדפן, אבל אפשר לשנות אותו לרכיב ספציפי באמצעות המאפיין root) יורחב ב-256 פיקסלים. כלומר, פונקציית הקריאה החוזרת תופעל כשרכיב תמונה נמצא בטווח של 256 פיקסלים מאזור התצוגה, והתמונה תתחיל להיטען לפני שהמשתמש יראה אותה בפועל.

כדי להשיג את אותה ההשפעה בדפדפנים שלא תומכים ב-Intersection שפותח, משתמשים בקוד טיפול באירועי גלילה ומשנים את הסימון של getBoundingClientRect כך שיכלול מאגר נתונים זמני.

שינויי פריסה ו-placeholders

אם לא משתמשים ב-placeholders, אם מדיה בטעינה לא פעילה, יכולה להיות תזוזה בפריסה. השינויים האלה יכולים לבלבל את המשתמשים ולגרום להם לבצע פעולות יקרות של פריסת DOM שצורכות משאבי מערכת ותורמות ל-jank. לכל הפחות, כדאי להשתמש ב-placeholder של צבע אחיד שיש לו אותם מימדים כמו תמונת היעד, או להשתמש בטכניקות כמו LQIP או SQIP עם רמזים לגבי התוכן של פריט מדיה לפני שהוא נטען.

לתגי <img>, src צריך להפנות בהתחלה ל-placeholder עד שהמאפיין הזה יעודכן בכתובת ה-URL של התמונה הסופית. משתמשים במאפיין poster ברכיב <video> כדי להצביע על תמונה של placeholder. בנוסף, צריך להשתמש במאפיינים width ו-height גם בתג <img> וגם בתג <video>. כך, המעבר מ-placeholders לתמונות סופיות לא ישנה את הגודל של הרכיב שעבר רינדור בזמן טעינת המדיה.

עיכובים בפענוח התמונה

כשטוענים תמונות גדולות ב-JavaScript ומשחררים אותן ב-DOM, ה-thread הראשי עלול לגרום לכך שממשק המשתמש לא יגיב לפרק זמן קצר בזמן שהפענוח מתרחש. פענוח אסינכרוני של תמונות באמצעות השיטה decode לפני שמוסיפים אותן ל-DOM יכול לצמצם את ה-jank מהסוג הזה, אבל חשוב לזכור: הוא עדיין לא זמין בכל מקום, והוא מגביר את המורכבות של הלוגיקה של הטעינה המושהית. אם ברצונך להשתמש בו, עליך לבדוק אותו. בהמשך מוצג האופן שבו אפשר להשתמש ב-Image.decode() עם חלופה:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

if ("decode" in newImage) {
  // Fancy decoding logic
  newImage.decode().then(function() {
    imageContainer.appendChild(newImage);
  });
} else {
  // Regular image load
  imageContainer.appendChild(newImage);
}

ראו את הקישור הזה ל-CodePen כדי לראות קוד שדומה לדוגמה הזו בפעולה. אם רוב התמונות קטנות למדי, יכול להיות שזה לא יתרום הרבה, אבל הוא בהחלט יצמצם את בעיות ה-jank במהלך טעינה מושהית של תמונות גדולות והוספה שלהן ל-DOM.

מתי דברים לא נטענים

לפעמים משאבי מדיה לא נטענים בגלל סיבה זו או אחרת, וקורות שגיאות. מתי זה עשוי לקרות? זה תלוי, אבל הנה תרחיש היפותטי אחד: יש לכם מדיניות שמירה במטמון של HTML לפרק זמן קצר (למשל, חמש דקות), והמשתמש מבקר באתר או שהמשתמש השאיר כרטיסייה לא פעילה למשך זמן רב (למשל, מספר שעות) וחוזר כדי לקרוא את התוכן. בשלב כלשהו בתהליך הזה מתרחשת פריסה מחדש. במהלך הפריסה, השם של משאב התמונה משתנה בגלל ניהול גרסאות שמבוססות על גיבוב (hash), או שהוא נמחק כולו. עד שהמשתמש יטען את התמונה בהדרגה, המשאב כבר לא יהיה זמין, וכתוצאה מכך ייכשל.

מדובר באירועים נדירים יחסית, אבל יכול להיות שיהיה צורך בתוכנית גיבוי אם הטעינה המושהית תיכשל. בתמונות, פתרון כזה עשוי להיראות כך:

var newImage = new Image();
newImage.src = "my-awesome-image.jpg";

newImage.onerror = function(){
  // Decide what to do on error
};
newImage.onload = function(){
  // Load the image
};

מה שתחליטו לעשות במקרה של שגיאה תלוי באפליקציה שלכם. לדוגמה, אפשר להחליף את אזור ה-placeholder של התמונה בלחצן שמאפשר למשתמש לנסות לטעון שוב את התמונה, או פשוט להציג הודעת שגיאה באזור ה-placeholder של התמונה.

יכולים להתרחש גם תרחישים אחרים. בכל מקרה, תמיד כדאי לאותת למשתמש כשמתרחשת שגיאה, ואולי גם לגרום לו לנקוט פעולה במקרה שמשהו משתבש.

הזמינות של JavaScript

אין להניח ש-JavaScript זמין תמיד. אם אתם מתכוונים לטעון תמונות בהדרגה, כדאי להציע תגי עיצוב של <noscript> שמאפשרים להציג תמונות במקרים שבהם JavaScript לא זמין. הדוגמה הפשוטה ביותר האפשרית לחלופה יכולה להשתמש ברכיבי <noscript> כדי להציג תמונות אם JavaScript מושבת:

אני תמונה!

אם JavaScript מושבת, המשתמשים יראו גם את תמונת ה-placeholder וגם את התמונה שמכילה את רכיבי <noscript>. כדי לעקוף את הבעיה, מציבים מחלקה של no-js בתג <html>, באופן הבא:

<html class="no-js">

לאחר מכן צריך להציב שורה אחת של סקריפט מוטבע בשדה <head> לפני בקשה של גיליונות סגנונות באמצעות תגי <link>, שמסירים את המחלקה no-js מהרכיב <html> אם JavaScript מופעל:

<script>document.documentElement.classList.remove("no-js");</script>

לבסוף, השתמשו ב-CSS מסוים כדי להסתיר רכיבים ברמה עצלה כש-JavaScript לא זמין:

.no-js .lazy {
  display: none;
}

הפעולה הזו לא מונעת טעינה של תמונות placeholder, אבל התוצאה טובה יותר. אנשים שהשביתו את JavaScript מקבלים יותר מאשר תמונות placeholder, שהן טובות יותר מ-placeholders ואין שום תוכן משמעותי בתמונות.