פיתוח לאינטרנט מאפשר לכם להגיע לקהל רחב יותר. אפליקציית האינטרנט שלכם נמצאת במרחק קליק, והיא זמינה כמעט בכל מכשיר מחובר – סמארטפון, טאבלט, מחשב נייד ומחשב, טלוויזיה ועוד – ללא קשר למותג או לפלטפורמה. כדי לספק את חוויית השימוש הטובה ביותר, יצרתם אתר רספונסיבי שמתאים את המראה והפונקציונליות לכל גורם צורה. עכשיו אתם בודקים את רשימת המשימות לביצוע אופטימיזציה של הביצועים כדי לוודא שהאפליקציה נטענת מהר ככל האפשר: ביצעתם אופטימיזציה של נתיב העיבוד הקריטי, דחסתם את משאבי הטקסט ושמרתם אותם במטמון, ועכשיו אתם בודקים את משאבי התמונות, שבדרך כלל אחראים לרוב הבייטים המועברים. הבעיה היא שאופטימיזציה של תמונות קשה:
- בחירת הפורמט המתאים (וקטור לעומת רסטר)
- קביעת הפורמטים האופטימליים לקידוד (jpeg, webp וכו')
- איך קובעים את הגדרות הדחיסה המתאימות (דחיסה עם אובדן נתונים לעומת דחיסה ללא אובדן נתונים)
- קובעים אילו מטא-נתונים צריך לשמור או להסיר
- ליצור כמה וריאנטים של כל אחד מהם לכל מסך ורזולוציית DPR
- ...
- התחשבות בסוג הרשת, במהירות ובהעדפות של המשתמש
כל אחת מהבעיות האלה היא בעיה מוכרת. ביחד, הם יוצרים מרחב אופטימיזציה גדול שאנחנו (המפתחים) לעיתים קרובות מתעלמים ממנו או מזניחים אותו. בני אדם לא מצליחים לחקור שוב ושוב את אותו מרחב חיפוש, במיוחד כשיש הרבה שלבים. לעומת זאת, מחשבים מצטיינים במשימות מהסוג הזה.
התשובה לשיטת אופטימיזציה טובה ומתמשכת לתמונות ולמשאבים אחרים עם מאפיינים דומים היא פשוטה: אוטומציה. אם אתם משנים את ההגדרות של המשאבים באופן ידני, אתם עושים זאת בצורה שגויה: אתם תשכחו, תתעצלו או שמישהו אחר יעשה את הטעות הזו בשבילכם – מובטח.
הסאגה של המפתח שמתחשב בביצועים
החיפוש במרחב של אופטימיזציית תמונות מתבצע בשני שלבים נפרדים: בשלב ה-build ובזמן הריצה.
- חלק מהאופטימיזציות הן ייחודיות למשאב עצמו – למשל, בחירת הפורמט וסוג הקידוד המתאימים, שינוי הגדרות הדחיסה לכל מקודד, הסרת מטא-נתונים מיותרים וכו'. אפשר לבצע את השלבים האלה בזמן ה-build.
- אופטימיזציות אחרות נקבעות לפי הסוג והמאפיינים של הלקוח שמבקש אותן, וצריך לבצע אותן בזמן הריצה: בחירת המשאב המתאים ל-DPR ולרוחב התצוגה המיועד של הלקוח, תוך התחשבות במהירות הרשת של הלקוח, בהעדפות של המשתמש והאפליקציה וכו'.
כלי הפיתוח בזמן ה-build קיימים, אבל אפשר לשפר אותם. לדוגמה, אפשר לחסוך הרבה כסף על ידי כוונון דינמי של ההגדרה 'איכות' לכל תמונה לכל פורמט תמונה, אבל עדיין לא ראיתי אף אחד שמשתמש בהגדרה הזו בפועל מחוץ למחקר. זהו תחום שמתאים לחדשנות, אבל למטרות הפוסט הזה אשאיר את זה כאן. נתמקד בחלק של הסיפור שקשור לזמן הריצה.
<img src="/image/thing" sizes="50vw"
alt="image thing displayed at 50% of viewport width">
הכוונה של האפליקציה פשוטה מאוד: לאחזר את התמונה ולהציג אותה ב-50% מחלון התצוגה של המשתמש. זה המקום שבו רוב המעצבים שוטפים את הידיים וממשיכים לבר. בינתיים, המפתח בצוות שמתעניין בביצועים צפוי לילה ארוך:
- כדי לקבל את הדחיסה הטובה ביותר, היא רוצה להשתמש בפורמט התמונה האופטימלי לכל לקוח: WebP ל-Chrome, JPEG XR ל-Edge ו-JPEG לשאר.
- כדי לקבל את האיכות החזותית הטובה ביותר, היא צריכה ליצור כמה וריאנטים של כל תמונה ברזולוציות שונות: 1x, 1.5x, 2x, 2.5x, 3x ואולי גם כמה וריאנטים נוספים באמצע.
- כדי להימנע מהצגת פיקסלים מיותרים, היא צריכה להבין מה המשמעות של '50% מחלון הצפייה של המשתמש' – יש הרבה רוחבים שונים של חלונות צפייה.
- באופן אידיאלי, היא גם רוצה לספק חוויית משתמש עמידה, שבה משתמשים ברשתות איטיות יותר יקבלו אוטומטית גרסת אחזור ברזולוציה נמוכה יותר. אחרי הכול, הכול תלוי בזמן הזכוכית.
- האפליקציה גם חושפת כמה אמצעי בקרה של משתמשים שמשפיעים על משאב התמונה שצריך לאחזר, כך שצריך להביא בחשבון גם את הגורם הזה.
אה, ואז המעצבת מבינה שהיא צריכה להציג תמונה אחרת ברוחב 100% אם גודל חלון התצוגה קטן, כדי לשפר את הקריאוּת. כלומר, עכשיו צריך לחזור על אותו תהליך לנכס נוסף, ואז להגדיר את האחזור כמותנה לפי גודל חלון התצוגה. הזכרתי שהדברים האלה קשים? אוקיי, בואו נתחיל. הרכיב picture
יעזור לנו להגיע רחוק:
<picture>
<!-- serve WebP to Chrome and Opera -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
/image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
/image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
type="image/webp">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
/image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
/image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
type="image/webp">
<!-- serve JPEGXR to Edge -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
/image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
/image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
type="image/vnd.ms-photo">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
/image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
/image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
type="image/vnd.ms-photo">
<!-- serve JPEG to others -->
<source
media="(min-width: 50em)"
sizes="50vw"
srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
/image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
/image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
<source
sizes="(min-width: 30em) 100vw"
srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
/image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
/image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
<!-- fallback for browsers that don't support picture -->
<img src="/image/thing.jpg" width="50%">
</picture>
טיפלנו בכיוון האמנותי, בבחירת הפורמט וסיפקנו שישה וריאנטים של כל תמונה כדי להתחשב בתנודות ברזולוציית המסך וברוחב שדה הראייה של המכשיר של הלקוח. מרשים!
לצערנו, הרכיב picture
לא מאפשר לנו להגדיר כללים לגבי האופן שבו הוא יפעל על סמך סוג החיבור או המהירות של הלקוח. עם זאת, אלגוריתם העיבוד שלו מאפשר לסוכנות המשתמשים לשנות את המשאב שאיתו היא מאחזרת במקרים מסוימים – ראו שלב 5. נצטרך רק לקוות שסוכן המשתמש מספיק חכם. (הערה: אף אחת מההטמעות הנוכחיות לא עומדת בדרישות האלה). באופן דומה, אין ברכיב picture
ווקים שמאפשרים לכתוב לוגיקה ספציפית לאפליקציה שתתחשב בהעדפות של האפליקציה או של המשתמש. כדי לקבל את שני הנתונים האחרונים האלה, נצטרך להעביר את כל הלוגיקה שלמעלה ל-JavaScript, אבל כך נפסיד את האופטימיזציות של הסורק לטעינה מראש שמוצעות על ידי picture
. הממ…
מלבד המגבלות האלה, הפתרון הזה עובד. טוב, לפחות לגבי הנכס הספציפי הזה. האתגר האמיתי והארוך טווח הוא שאי אפשר לצפות מהמעצב או מהמפתח לכתוב קוד כזה ביד לכל נכס. זוהי חידת מוח מהנה בניסיון הראשון, אבל היא מאבדת את האטרקטיביות שלה מיד אחרי כן. אנחנו זקוקים לאוטומציה. אולי אפשר להשתמש בכלי של סביבת הפיתוח המשולבת או בכלי אחר לטרנספורמציה של תוכן כדי ליצור את הקוד הסטנדרטי שלמעלה באופן אוטומטי.
אוטומציה של בחירת המשאבים באמצעות רמזים מהלקוח
נושמים עמוק, משהים את חוסר האמון ומעיינים בדוגמה הבאה:
<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
<source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
<img sizes="100vw" src="/image/thing-crop">
</picture>
לא תאמינו, אבל הדוגמה שלמעלה מספיקה כדי לספק את כל אותן יכולות כמו אותה רכיב קוד ארוך יותר של תמונה שצוין למעלה. בנוסף, כפי שנראה בהמשך, היא מאפשרת למפתחים לשלוט באופן מלא באופן שבו משאבים של תמונות מאוחזרים, אילו משאבים מאוחזרים ומתי הם מאוחזרים. 'הקסם' נמצא בשורה הראשונה, שמאפשרת דיווח על רמזים מהלקוח ומורה לדפדפן לפרסם לשרת את יחס הפיקסלים של המכשיר (DPR
), את רוחב אזור התצוגה של הפריסה (Viewport-Width
) ואת רוחב התצוגה המיועד (Width
) של המשאבים.
כשהרמזים על הלקוח מופעלים, התגים שנוצרים בצד הלקוח שומרים רק על דרישות התצוגה. המעצב לא צריך לדאוג לגבי סוגי תמונות, רזולוציות של לקוחות, נקודות עצירה אופטימליות כדי לצמצם את מספר הבייטים שנשלחים או קריטריונים אחרים לבחירת משאבים. בואו נודה בזה, הם אף פעם לא עשו זאת, וגם לא צריכים. יתרון נוסף הוא שהמפתח לא צריך גם לכתוב מחדש ולהרחיב את ה-Markup שלמעלה, כי בחירת המשאב בפועל מתבצעת במשא ומתן בין הלקוח לשרת.
ב-Chrome 46 יש תמיכה מובנית בהצעות DPR
, Width
ו-Viewport-Width
. ההצעות מושבתות כברירת מחדל, והקוד <meta http-equiv="Accept-CH" content="...">
שלמעלה משמש כאות להצטרפות שמורה ל-Chrome לצרף את הכותרות שצוינו לבקשות היוצאות. עכשיו נבחן את הכותרות של הבקשה והתגובה לבקשת תמונה לדוגמה:
Chrome מכריז על התמיכה שלו בפורמט WebP דרך כותרת הבקשה Accept. בדומה, דפדפן Edge החדש מכריז על תמיכה ב-JPEG XR דרך כותרת הבקשה Accept.
שלוש כותרות הבקשה הבאות הן כותרות של רמזים ללקוח שמפרסמות את יחס הפיקסלים של המכשיר של הלקוח (3x), את רוחב שדה התצוגה של הפריסה (460px) ואת רוחב התצוגה המיועד של המשאב (230px). כך השרת מקבל את כל המידע הדרוש כדי לבחור את גרסת התמונה האופטימלית על סמך קבוצת המדיניות שלו: זמינות של משאבים שנוצרו מראש, עלות של קידוד מחדש או שינוי גודל של משאב, הפופולריות של משאב, העומס הנוכחי על השרת וכו'. במקרה הזה, השרת משתמש בהצעות DPR
ו-Width
ומחזיר משאב WebP, כפי שמצוין בכותרות Content-Type
, Content-DPR
ו-Vary
.
אין כאן קסם. העברנו את בחירת המשאבים מתיוג HTML לתוך המשא ומתן של בקשה-תגובה בין הלקוח לשרת. כתוצאה מכך, ה-HTML מתייחס רק לדרישות התצוגה, וכל מעצב ומפתח יכול לכתוב אותו. לעומת זאת, החיפוש במרחב של אופטימיזציית התמונות מועבר למחשבים, ועכשיו אפשר לבצע אותו בקלות באופן אוטומטי בקנה מידה נרחב. זכורים לכם המפתחים שחשוב להם לשפר את הביצועים? עכשיו היא צריכה לכתוב שירות תמונות שיכול להשתמש בטיפים שסופקו ולהחזיר את התגובה המתאימה: היא יכולה להשתמש בכל שפה או שרת שתרצה, או לאפשר לשירות צד שלישי או ל-CDN לעשות זאת בשמה.
<img src="/image/thing" sizes="50vw"
alt="image thing displayed at 50% of viewport width">
בנוסף, זוכרים את הבחור שלמעלה? בעזרת רמזים ללקוח, תג התמונה הפשוט מודע עכשיו ל-DPR, לתצוגה הווירטואלית ולרוחב, בלי תגי markup נוספים. אם אתם צריכים להוסיף הנחיות ליצירת גרפיקה, תוכלו להשתמש בתג picture
, כפי שראינו למעלה. אחרת, כל תגי התמונות הקיימים שלכם הפכו חכמים יותר. רמזים ללקוח משפרים את הרכיבים הקיימים img
ו-picture
.
שליטה בבחירת המשאבים באמצעות service worker
ServiceWorker הוא למעשה שרת proxy מצד הלקוח שפועל בדפדפן. הוא מיירט את כל הבקשות היוצאות ומאפשר לבדוק, לשכתב, לשמור במטמון ואפילו לסנתז תשובות. זה נכון גם לגבי תמונות. כשהטיפים ללקוח מופעלים, ה-ServiceWorker הפעיל יכול לזהות את הבקשות לתמונות, לבדוק את הטיפים ללקוח שסופקו ולהגדיר את לוגיקת העיבוד שלו.
self.onfetch = function(event) {
var req = event.request.clone();
console.log("SW received request for: " + req.url)
for (var entry of req.headers.entries()) {
console.log("\t" + entry[0] +": " + entry[1])
}
...
}
ServiceWorker מאפשר לכם לשלוט באופן מלא בצד הלקוח בבחירת המשאבים. זה קריטי. חשוב להבין את זה, כי האפשרויות הן כמעט אינסופיות:
- אפשר לשכתב את ערכי הכותרות של רמזים על הלקוח שהוגדרו על ידי סוכן המשתמש.
- אפשר לצרף לבקשה ערכים חדשים של כותרות של רמזים ללקוח.
- אפשר לכתוב מחדש את כתובת ה-URL ולהפנות את הבקשה לתמונה לשרת חלופי (למשל CDN).
- אפשר אפילו להעביר את ערכי ההנחיה מהכותרות לכתובת ה-URL עצמה, אם זה מקל על הפריסה בתשתית.
- אפשר לשמור תשובות במטמון ולהגדיר לוגיקה משלכם לגבי המשאבים שיוצגו.
- אתם יכולים להתאים את התגובה שלכם בהתאם לקישוריות של המשתמשים.
- אתם יכולים להשתמש ב-NetInfo API כדי לשלוח לשרת שאילתה לגבי ההעדפות שלכם ולהציג אותן.
- אם האחזור איטי, אפשר להחזיר תשובה חלופית.
- אפשר להביא בחשבון שינויים בהעדפות של האפליקציה והמשתמשים.
- אתם יכולים לעשות כל מה שאתם רוצים.
הרכיב picture
מספק את אמצעי הבקרה הנדרשים על כיוון האמנות בסימני ה-HTML.
הטיפים ללקוח מספקים הערות לבקשות התמונות שמתקבלות, שמאפשרות אוטומציה של בחירת המשאבים. ServiceWorker מספק יכולות לניהול בקשות ותשובות בצד הלקוח. זהו האינטרנט המורחב בפעולה.
שאלות נפוצות על רמזים על הלקוח
איפה זמינות ההנחיות ללקוח? התכונה הזו נוספה ב-Chrome 46. נמצאת בבדיקה ב-Firefox וב-Edge.
למה צריך להביע הסכמה לשימוש בטיפים ללקוח? אנחנו רוצים למזער את התקורה של אתרים שלא ישתמשו בטיפים ללקוח. כדי להפעיל רמזים ללקוח, צריך לספק באתר את הכותרת
Accept-CH
או הוראה<meta http-equiv>
מקבילה בסימון הדף. אם אחד מהם קיים, סוכן המשתמש יתווסף את הטיפים המתאימים לכל הבקשות של משאבי המשנה. בעתיד, יכול להיות שנציע מנגנון נוסף לשמירת ההעדפה הזו למקור מסוים, שיאפשר לשלוח את אותן הנחיות לבקשות ניווט.למה אנחנו צריכים רמזים ללקוח אם יש לנו ServiceWorker? ל-ServiceWorker אין גישה למידע על הפריסה, המשאבים ורוחבו של אזור התצוגה. לפחות, לא בלי להוסיף נסיעות הלוך ושוב יקרות ולהשהות משמעותית את בקשת התמונה – למשל, כשבקשת התמונה מופעלת על ידי מנתח הטעינה מראש. הטיפים ללקוח משולבים בדפדפן כדי שהנתונים האלה יהיו זמינים כחלק מהבקשה.
האם טיפים ללקוח מיועדים למשאבי תמונות בלבד? התרחיש העיקרי לשימוש ב-DPR, ב-Viewport-Width וברמזים לגבי רוחב הוא לאפשר בחירת משאבים לנכסי תמונה. עם זאת, אותן הנחיות מועברות לכל המשאבים המשניים, ללא קשר לסוג שלהם. לדוגמה, גם לבקשות CSS ו-JavaScript מגיע אותו מידע, ואפשר להשתמש בהן כדי לבצע אופטימיזציה של המשאבים האלה.
בקשות מסוימות לתמונות לא מדווחות על רוחב. למה? יכול להיות שהדפדפן לא יידע מהו רוחב התצוגה המיועד כי האתר מסתמך על הגודל הפנימי של התמונה. כתוצאה מכך, ההצעה לגבי הרוחב לא תופיע בבקשות כאלה, ובבקשות שאין להן 'רוחב תצוגה' – למשל, משאב JavaScript. כדי לקבל רמזים לגבי רוחב, חשוב לציין ערך של sizes בתמונות.
מה לגבי <insert my favorite hint>? ServiceWorker מאפשר למפתחים ליירט ולשנות (למשל, להוסיף כותרות חדשות) את כל הבקשות היוצאות. לדוגמה, קל להוסיף מידע שמבוסס על NetInfo כדי לציין את סוג החיבור הנוכחי. אפשר לקרוא מידע נוסף במאמר דיווח על יכולות באמצעות ServiceWorker. ההנחיות 'הילידים' שנשלחות ב-Chrome (DPR, Width, Resource-Width) מוטמעות בדפדפן כי הטמעה טהורה שמבוססת על תוכנה תעכב את כל בקשות התמונות.
איפה אפשר לקבל מידע נוסף, לראות הדגמות נוספות ומה עוד? מומלץ לעיין במסמך ההסבר, ואפשר לפתוח פנייה בנושא ב-GitHub אם יש לכם משוב או שאלות נוספות.