קצה הארץ התיכונה

הדרכה מפורטת בנושא פיתוח מרובה מכשירים

Daniel Isaksson
Daniel Isaksson
Einar Öberg
Einar Öberg

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

שלוש גרסאות של אותו אתר

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

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

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

העיטים השליכו אותנו עכשיו בדף הנחיתה.
העיטים בדיוק נטשו אותנו בדף הנחיתה.

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

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

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

אנחנו גם מוסיפים מחלקה עם המצב הנוכחי בתג head, כך שנוכל להשתמש במידע הזה בסגנונות שלנו, כמו בדוגמה הזו (ב-SCSS):

.loc-hobbit-logo {

  // Default values here.

  .desktop & {
     // Applies only in desktop mode.
  }

 .tablet &, .mobile & {
   
   // Different asset for mobile and tablets perhaps.

   @media screen and (max-height: 760px), (max-width: 760px) {
     // Breakpoint-specific styles.
   }

   @media screen and (max-height: 570px), (max-width: 400px) {
     // Breakpoint-specific styles.
   }
 }
}

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

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

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

טיפול במדינה

אחרי דף הנחיתה אנחנו נוחתים במפת הארץ התיכונה. האם הבחנת בכתובת ה-URL משתנה? האתר הוא אפליקציית דף אחת שמשתמשת ב-History API כדי לטפל בניתוב.

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

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

הצגת מיקומים בנפרד

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

אולמו של תרנדויל
ציר הזמן של Thranduil's Hall

ציר הזמן

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

מודולים ורכיבי התנהגות

המודולים השונים שהוספנו בהם תמיכה הם: רצף תמונות, תמונת סטילס, סצנת פרלקס, סצנה של הזזת מיקוד וטקסט.

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

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

התוכן במודול הטקסט מופעל באמצעות גרירה באמצעות הפלאגין של TweenMax Draggable. אפשר גם להשתמש בגלגל הגלילה או להחליק בעזרת שתי אצבעות כדי לגלול אנכית. שימו לב לthrow-props-plugin שמוסיף את חוקי הפיזיקה של התנופה כשמחליקים ומשחררים.

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

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

רצפי תמונות

המאתגר ביותר מבין המודולים מבחינת ביצועים והיבט של גודל הורדה הוא רצף התמונות. יש הרבה דברים לקרוא על הנושא הזה. בניידים ובטאבלטים, אנחנו מחליפים את התמונה הזו בתמונת סטילס. מדובר בכמות גדולה מדי של נתונים שלא ניתן לפענח ולאחסן בזיכרון אם אנחנו רוצים איכות טובה בניידים. ניסינו מספר פתרונות חלופיים; שימוש קודם בתמונת רקע ובגיליון הטמעה, אך הדבר גרם לבעיות זיכרון ולעיכוב כשה-GPU היה צריך לעבור בין Spritesheets. לאחר מכן ניסינו להחליף רכיבי img, אבל גם הפעולה הייתה איטית מדי. הניסיון היה הכי טוב לצייר מסגרת מגיליון Spritesheet לקנבס. לכן התחלנו לבצע אופטימיזציה של התהליך. כדי לחסוך בזמן חישוב בכל פריים, נתוני התמונה שמיועדים לכתיבה באזור העריכה מעובדים מראש באמצעות בד ציור זמני ונשמרים באמצעות getImageData() במערך, מפוענח ומוכן לשימוש. לאחר מכן, ייתכן שה-Spritesheet המקורי יכול לשמש לאיסוף אשפה, ואנחנו מאחסנים רק את כמות הנתונים המינימלית הנדרשת בזיכרון. אולי זה פחות מאמץ לאחסן תמונות לא מפוענחות, אבל אנחנו משיגים ביצועים טובים יותר כשמנסים לקצץ את הרצף בצורה הזו. המסגרות קטנות למדי, 640x400 בלבד, אבל ניתן לראות אותן בזמן הניקוי. כשמפסיקים, תמונה ברזולוציה גבוהה נטענת ומתעמעמת במהירות.

var canvas = document.createElement('canvas');
canvas.width = imageWidth;
canvas.height = imageHeight;

var ctx = canvas.getContext('2d');
ctx.drawImage(sheet, 0, 0);

var tilesX = imageWidth / tileWidth;
var tilesY = imageHeight / tileHeight;

var canvasPaste = canvas.cloneNode(false);
canvasPaste.width = tileWidth;
canvasPaste.height = tileHeight;

var i, j, canvasPasteTemp, imgData, 
var currentIndex = 0;
var startIndex = index * 16;
for (i = 0; i < tilesY; i++) {
  for (j = 0; j < tilesX; j++) {
    // Store the image data of each tile in the array.
    canvasPasteTemp = canvasPaste.cloneNode(false);
    imgData = ctx.getImageData(j * tileWidth, i * tileHeight, tileWidth, tileHeight);
    canvasPasteTemp.getContext('2d').putImageData(imgData, 0, 0);

    list[ startIndex + currentIndex ] = imgData;

    currentIndex++;
  }
}

גיליונות ה-Sprite נוצרים באמצעות Imagemagick. הנה דוגמה פשוטה ב-GitHub שמראה איך ליצור גיליון Spritesheet של כל התמונות בתוך תיקייה.

אנימציה של המודולים

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

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

ביצועי דפים

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

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

אני אוהבת להשתמש ב-TweenMax מ-Greensock כדי ליצור שילוב בין נכסים, המרות ו-CSS. תוך כדי הוספת שכבות חדשות, חשוב להמחיש את המבנה בקונטיינרים. חשוב לזכור: טרנספורמציות חדשות יכולות להחליף את הטרנספורמציות הקיימות. ה-TranslateZ(0) שאילוץ האצת חומרה במחלקת ה-CSS יוחלף במטריצה דו-ממדית אם רק מקשרים ערכים דו-ממדיים. כדי שהשכבה תישאר במצב תאוצה במקרים כאלה, צריך להשתמש במאפיין "force3D:true" בתקופה שבין יתרון כדי ליצור מטריצה תלת-ממדית במקום מטריצה דו-ממדית. קל לשכוח אותה כשמשלבים בין CSS לבין JavaScript כדי להגדיר סגנונות.

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

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

יציאה מקטע עם פונקציית סילוק שנכשלה.
יציאה מקטע עם פונקציה להיפטרות נכשלת.
הרבה יותר טוב!
הרבה יותר טוב!

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

ב-EffectComposer הייתה הפניה לסצנה.
הייתה הפניה לסצנה ב-EffectComposer.

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

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

מסך מלא

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

נכסים

הוראות מונפשות לניסויים.
הוראות מונפשות לניסויים.

ברחבי האתר יש מגוון סוגים של נכסים: תמונות (PNG ו-JPEG), SVG (מוטבע ורקע), Spritesheets (PNG), גופנים בסמל מותאם אישית ואנימציות של Adobe Edge. אנחנו משתמשים בקובצי PNG בנכסים ובאנימציות (Spritesheets) כאשר האלמנט לא יכול להיות מבוסס על וקטורים. אחרת, אנחנו מנסים להשתמש בפורמטי SVG ככל האפשר.

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

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

לגופנים של סמלים יש אותם יתרונות כמו SVG בכל הנוגע למדרגיות, והם משמשים במקום SVG לרכיבים קטנים כמו סמלים שבהם יש צורך לשנות את הצבע בלבד (העברת העכבר, מצב פעיל וכו'). בנוסף, קל מאוד להשתמש בסמלים האלה, וכל מה שצריך לעשות הוא להגדיר את מאפיין ה-CSS 'תוכן' של רכיב.

אנימציות

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

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

סיכום

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