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

סקירה כללית על פיתוח למספר מכשירים

Daniel Isaksson
Daniel Isaksson
Einar Öberg
Einar Öberg

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

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

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

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

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

הינשופים פשוט הורידו אותנו לדף הנחיתה.
הנשרים פשוט הורידו אותנו בדף הנחיתה.

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

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

במקרה הזה, העיצובים לא מבוססים על רשתות או כללים, והם ייחודיים למדי בין החלקים השונים. לכן, הבחירה של נקודות העצירה או הסגנונות שבהם משתמשים תלויה ברכיב ובתרחיש הספציפיים. יותר מפעם אחת קבענו את הפריסה המושלמת עם sass-mixins נחמד ו-media-queries, ואז נזקקנו להוסיף אפקט על סמך מיקום העכבר או אובייקטים דינמיים, ובסופו של דבר נאלצנו לכתוב מחדש את הכל ב-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, וזה היה אתגר לא פשוט כשרצינו ליצור חוויית שימוש סוחפת באינטרנט. במחשב, אנחנו מציגים סרגל גלילה רק אם הדף גדול מספיק, כי אנחנו רוצים שהמשתמשים יוכלו ליהנות מחוויית שימוש רחבה יותר באתר, אם אפשר. במכשירים ניידים, החלטנו לאפשר גם את התצוגה בפורמט לאורך וגם את התצוגה בפורמט לרוחב, עד לחוויות האינטראקטיביות, שבהן נבקש מכם להפוך את המכשיר לפורמט לאורך. הטענה נגד השינוי הייתה שהתצוגה לאורך לא מאפשרת חוויית צפייה כה immersive כמו התצוגה בפריסה לרוחב, אבל האתר התאים את עצמו די טוב לפריסה הזו, ולכן השארנו אותה.

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

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

טיפול במצב

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

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

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

הצגת המיקומים

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

האולם של טרנדיל
ציר הזמן של 'אולם טרנדויל'

ציר הזמן

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

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

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

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

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

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

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

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

רצפי תמונות

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

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

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

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

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

המעבר מאב טיפוס שפועל לגרסת build ללא תנודות חדות (jank) הוא מעבר מהשערה לידיעה לגבי מה שקורה בדפדפן. כאן כלי הפיתוח ל-Chrome הם החברים הכי טובים שלכם.

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

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

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

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

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

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

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

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

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

מסך מלא

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

נכסים

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

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

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

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

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

אנימציות

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

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

סיכום

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