אוטומציה של בחירת משאבים באמצעות רמזים ללקוח

איליה גריגוריק

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

  • קבע את הפורמט המתאים (וקטורי לעומת רשת נקודות)
  • לקבוע מהם הפורמטים האופטימליים של הקידוד (jpeg , webp וכו')
  • קביעת הגדרות הדחיסה הנכונות (Losy לעומתlossless)
  • קביעה אילו מטא-נתונים יש לשמור או להסיר
  • מכינים כמה וריאציות לכל מסך ורזולוציית DPR
  • ...
  • צריך להביא בחשבון את סוג הרשת, המהירות וההעדפות של המשתמש

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

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

הסאגה של מפתח בעל מודעות לביצועים

לחיפוש באמצעות תמונות יש שני שלבים נפרדים: זמן הבנייה (build-time) וזמן ריצה.

  • חלק מהאופטימיזציות הן מהותיות למשאב עצמו. למשל, בחירה בפורמט ובסוג הקידוד המתאימים, כוונון הגדרות הדחיסה של כל מקודד, הסרת המטא-נתונים המיותרים וכן הלאה. ניתן לבצע את השלבים האלה ב-"build-time".
  • אופטימיזציות אחרות נקבעות לפי הסוג והמאפיינים של הלקוח שביקש אותן, וצריך לבצע אותן בזמן הריצה: בחירת המשאב המתאים לתדירות הפרסום (DPR) ולרוחב התצוגה המיועד של הלקוח, תוך התחשבות במהירות הרשת של הלקוח, בהעדפות המשתמש והאפליקציה וכו'.

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

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

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

  1. כדי לקבל את הדחיסה הטובה ביותר, היא רוצה להשתמש בפורמט התמונה האופטימלי לכל לקוח: WebP ל-Chrome, JPEG XR for Edge ו-JPEG לכל השאר.
  2. כדי להשיג את האיכות הוויזואלית הטובה ביותר, היא צריכה ליצור מספר גרסאות של כל תמונה ברזולוציות שונות: 1x, 1.5x, 2x, 2.5x, 3x ואולי אפילו עוד כמה ביניהן.
  3. כדי להימנע מהעברת פיקסלים מיותרים, היא צריכה להבין מה המשמעות של "50% מאזור התצוגה של המשתמש" – יש הרבה רוחבי תצוגה שונים!
  4. היא גם מעוניינת לספק חוויית שימוש עמידה, שבה משתמשים ברשתות איטיות יותר יקבלו באופן אוטומטי רזולוציה נמוכה יותר. אחרי הכול, זה הזמן לזכוכית.
  5. האפליקציה גם חושפת מספר פקדי משתמש שמשפיעים על המשאב של התמונות שיש לאחזר, ולכן צריך להביא בחשבון גם את זה.

אה, ואז המעצבת מבינה שעליה להציג תמונה שונה ברוחב של 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>

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

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

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

אוטומציה של בחירת משאבים באמצעות רמזים ללקוחות

קחו נשימה עמוקה, השעו את אי אמונתכם, ועכשיו קחו בחשבון את הדוגמה הבאה:

<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) של המשאבים.

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

ב-Chrome 46 יש תמיכה מובנית ברמזים לDPR, ל-Width ול-Viewport-Width. הרמזים מושבתים כברירת מחדל, והסימן <meta http-equiv="Accept-CH" content="..."> שלמעלה משמש כאות הסכמה ל-Chrome להוסיף את הכותרות שצוינו לבקשות יוצאות. עכשיו נבדוק את הכותרות של הבקשות והתגובות של בקשת תמונה לדוגמה:

תרשים משא ומתן של רמזים ללקוחות

Chrome מפרסם את התמיכה שלו בפורמט WebP דרך הכותרת Accept request (אישור הבקשה), ואילו דפדפן 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, לאזור התצוגה ולרוחב, ללא תגי עיצוב נוספים. אם אתם צריכים להוסיף ארט דירקטיבה, אתם יכולים להשתמש בתג 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 של רמזים ללקוחות.

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

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

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

שאלות נפוצות בנושא טיפים ללקוחות

  1. איפה זמינים טיפים ללקוחות? נשלח ב-Chrome 46. בבדיקה ב-Firefox וב-Edge.

  2. למה מביעים הסכמה לרמזים של הלקוח? אנחנו רוצים לצמצם את התקורה של אתרים שלא משתמשים ברמזים ללקוחות. כדי להפעיל את הרמזים ללקוחות, האתר צריך לספק את הכותרת Accept-CH או הוראת <meta http-equiv> מקבילה בתגי העיצוב של הדף. בכל אחת מהבקשות האלו, סוכן המשתמש יצרף את הרמזים המתאימים לכל הבקשות למשאבי משנה. בעתיד יכול להיות שנספק מנגנון נוסף כדי לשמור על ההעדפה הזו למקור מסוים, וכך נוכל לשלוח את אותם רמזים בבקשות ניווט.

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

  4. האם ההטיפים של הלקוח מתייחסים רק למשאבים של תמונות? התרחיש העיקרי לדוגמה של רמזים DPR, Viewport-Width ו-width הוא לאפשר בחירת משאבים לנכסי תמונות. עם זאת, אותם רמזים מועברים לכל משאבי המשנה ללא קשר לסוג שלהם. למשל, בקשות CSS ו-JavaScript מקבלות גם את אותו מידע, ואפשר להשתמש בהן גם כדי לבצע אופטימיזציה של המשאבים האלה.

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

  6. מה לגבי <insert my favorite hint>? ServiceWorker מאפשר למפתחים ליירט ולשנות את כל הבקשות היוצאות (למשל, להוסיף כותרות חדשות). לדוגמה, קל להוסיף מידע שמבוסס על NetInfo כדי לציין את סוג החיבור הנוכחי – ראו "דוחות יכולות עם ServiceWorker". הרמזים "המבוססים" שנשלחים ב-Chrome (DPR, Width, Resource-Width) מיושמים בדפדפן כי הטמעה מבוססת SW טהורה תעכב את כל הבקשות לתמונות.

  7. איפה אפשר לקבל מידע נוסף, הדגמות נוספות ומה לגבי? אתם מוזמנים לעיין במסמך ההסבר, ואם יש לכם משוב או שאלות נוספות, אתם מוזמנים לפתוח פנייה ב-GitHub.