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

Ilya Grigorik
Ilya Grigorik

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

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

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

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

הסאגה של מפתחים מנוסים

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

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

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

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

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

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

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

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, רוחב, רוחב משאב) מוטמעים בדפדפן, כי הטמעה מבוססת SW בלבד תעכב את כל בקשות התמונה.

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