פיתוח באמצעות Chrome

הבאת לבנים מ-LEGO® אל האינטרנט מרובה המכשירים

Build with Chrome, ניסוי כיפי למשתמשי Chrome במחשב שהושק במקור באוסטרליה, הופץ מחדש ב-2014 והופיע מחדש ב-2014, כולל זמינות ברחבי העולם שקשורה ל- THE LEGO® MOVIETM ותמיכה חדשה נוספה למכשירים ניידים. במאמר הזה נשתף כמה מהלקחים מהפרויקט, בייחוד בנוגע למעבר מחוויית השימוש במחשבים בלבד לפתרון של ריבוי מסכים שתומך בקלט עכבר ובקלט מגע.

ההיסטוריה של Build עם Chrome

הגרסה הראשונה של Build with Chrome הושקה באוסטרליה בשנת 2012. רצינו להדגים את העוצמה של האינטרנט בדרך חדשה לגמרי ולהביא את Chrome לקהל חדש לגמרי.

באתר היו שני חלקים עיקריים: מצב "Build" (בנייה) שבו משתמשים יכולים לבנות יצירות באמצעות אבני לגו, ומצב "Explore" לעיון ביצירות בגרסה מותאמת של לגו של מפות Google.

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

בשנת 2013 החלטנו להרחיב את Build with Chrome לטכנולוגיות אינטרנט חדשות. אחת מהטכנולוגיות האלה הייתה WebGL ב-Chrome ל-Android, שאפשר באופן טבעי ל-Build with Chrome להתפתח לחוויה בנייד. בתור התחלה, פיתחנו אבות-טיפוס למגע לפני שאנחנו מבררים את החומרה של ה-Builder Tool, כדי להבין את התנהגות התנועה ואת יכולת התגובה שאנו עשויים להיתקל בהן דרך דפדפן בהשוואה לאפליקציה לנייד.

ממשק קצה רספונסיבי

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

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

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

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

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

בואו נדבר קצת על שני גדלים וחוויות של מסכים:

מסך גדול, עם תמיכה בעכבר ובמגע

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

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

@media only screen and (max-height: 730px) {
    .zoom-slider {
        display: none;
    }
}

מסך קטן, תמיכה במגע בלבד

הגרסה הזו מוצגת למכשירים ניידים ולטאבלטים קטנים (מכשירי יעד Nexus 4 ו-Nexus 7). לגרסה הזו נדרשת תמיכה בריבוי נקודות מגע.

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

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

ביצועים ותמיכה של WebGL

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

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

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

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

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

תמיכה במכשירים שאינם WebGL

רצינו שניתן יהיה להשתמש באתר במידה מסוימת גם אם המכשיר של המבקר אינו תומך ב-WebGL. לפעמים יש דרכים לייצג את התלת-ממד בדרך פשוטה באמצעות פתרון קנבס או תכונות של CSS3D. לצערנו, לא מצאנו פתרון טוב מספיק כדי לשכפל תכונות Build ו-Explore 3D ללא שימוש ב-WebGL.

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

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

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

ניהול נכסים

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

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

היצירות בתלת-ממד מאוחסנות בפורמט קובץ מותאם אישית באריזה של תמונת PNG. שמירת הנתונים של היצירות בתלת-ממד כתמונה אפשרה לנו למעשה להעביר את הנתונים ישירות למערכות ההצללה (shader) שמעבדות את היצירות.

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

ניהול כיוון המסך

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

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

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

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

רוב פריסת התוכן נשלטת על-ידי CSS, אבל יש דברים מסוימים הקשורים לאוריינטציה שצריך להטמיע ב-JavaScript. גילינו שאין פתרון טוב למעבר בין מכשירים לשימוש ב-window.orientation כדי לזהות את הכיוון, כך שבסוף השווינו רק עם window.innerWidth ו-window.innerHeight כדי לזהות את כיוון המכשיר.

if( window.innerWidth > window.innerHeight ){
  //landscape
} else {
  //portrait
}

הוספת תמיכה במגע

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

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

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

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

var input = {dragStartX:0, dragStartY:0, dragX:0, dragY:0, dragDX:0, dragDY:0, dragging:false};
plateContainer.addEventListener('touchstart', onTouchStart);

function onTouchStart(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
    //start listening to all needed touchevents to implement the dragging
    document.addEventListener('touchmove', onTouchMove);
    document.addEventListener('touchend', onTouchEnd);
    document.addEventListener('touchcancel', onTouchEnd);
  }
}

function onTouchMove(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragging(event.touches[0].clientX, event.touches[0].clientY);
  }
}

function onTouchEnd(event) {
  event.preventDefault();
  if( event.touches.length === 0){
    handleDragStop();
    //remove all eventlisteners but touchstart to minimize number of eventlisteners
    document.removeEventListener('touchmove', onTouchMove);
    document.removeEventListener('touchend', onTouchEnd);
    //also listen to touchcancel event to avoid unexpected behavior when switching tabs and some other situations
    document.removeEventListener('touchcancel', onTouchEnd);
  }
}

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

function handleDragStart(x ,y ){
  input.dragging = true;
  input.dragStartX = input.dragX = x;
  input.dragStartY = input.dragY = y;
}

function handleDragging(x ,y ){
  if(input.dragging) {
    input.dragDX = x - input.dragX;
    input.dragDY = y - input.dragY;
    input.dragX = x;
    input.dragY = y;
  }
}

function handleDragStop(){
  if(input.dragging) {
    input.dragging = false;
    input.dragDX = 0;
    input.dragDY = 0;
  }
}

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

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );

  //execute animation based on input.dragDX, input.dragDY, input.dragX or input.dragY
 /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX=0;
  input.dragDY=0;
}

דוגמה מוטמעת: גרירת אובייקט באמצעות אירועי מגע. יישום דומה לגרירה של המפה 'חקור תלת-ממד' ב-Build with Chrome: http://cdpn.io/qDxvo

תנועות מולטי טאץ'

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

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

//variables representing the actual scale/rotation of the object we are affecting
var currentScale = 1;
var currentRotation = 0;

function onTouchStart(event) {
  event.preventDefault();
  if( event.touches.length === 1){
    handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
  }else if( event.touches.length === 2 ){
    handleGestureStart(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
  }
}

function handleGestureStart(x1, y1, x2, y2){
  input.isGesture = true;
  //calculate distance and angle between fingers
  var dx = x2 - x1;
  var dy = y2 - y1;
  input.touchStartDistance=Math.sqrt(dx*dx+dy*dy);
  input.touchStartAngle=Math.atan2(dy,dx);
  //we also store the current scale and rotation of the actual object we are affecting. This is needed to support incremental rotation/scaling. We can't assume that an object is always the same scale when gesture starts.
  input.startScale=currentScale;
  input.startAngle=currentRotation;
}

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

function onTouchMove(event) {
  event.preventDefault();
  if( event.touches.length  === 1){
    handleDragging(event.touches[0].clientX, event.touches[0].clientY);
  }else if( event.touches.length === 2 ){
    handleGesture(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
  }
}

function handleGesture(x1, y1, x2, y2){
  if(input.isGesture){
    //calculate distance and angle between fingers
    var dx = x2 - x1;
    var dy = y2 - y1;
    var touchDistance = Math.sqrt(dx*dx+dy*dy);
    var touchAngle = Math.atan2(dy,dx);
    //calculate the difference between current touch values and the start values
    var scalePixelChange = touchDistance - input.touchStartDistance;
    var angleChange = touchAngle - input.touchStartAngle;
    //calculate how much this should affect the actual object
    currentScale = input.startScale + scalePixelChange*0.01;
    currentRotation = input.startAngle+(angleChange*180/Math.PI);
    //upper and lower limit of scaling
    if(currentScale<0.5) currentScale = 0.5;
    if(currentScale>3) currentScale = 3;
  }
}

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

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );
  //execute transform based on currentScale and currentRotation
  /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX=0;
  input.dragDY=0;
}

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

דוגמה מוטמעת: סיבוב ושינוי גודל של אובייקט בדו-ממד. בדומה לאופן שבו מיושמת המפה בכלי הניתוחים: http://cdpn.io/izloq

תמיכה בעכבר ובמגע באותה חומרה

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

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

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

  1. הפעלה במגע
  2. Touchmove
  3. קצה מגע
  4. שימוש בעכבר
  5. mousemove
  6. העברת העכבר כלפי מטה
  7. פינוי עכבר
  8. click

אם יש לכם אינטראקציות קצת יותר מורכבות, אירועי העכבר האלה עלולים לגרום להתנהגות לא צפויה ולפגוע בהטמעה. לעיתים קרובות מומלץ להשתמש ב-event.preventDefault() במטפלים באירועי מגע ולנהל את קלט העכבר במטפלים נפרדים באירועים. צריך לדעת שהשימוש ב-event.preventDefault() בגורמים מטפלים באירועים במגע ימנע גם חלק מההתנהגות המוגדרת כברירת מחדל, כמו גלילה ואירוע קליק.

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

<meta name="viewport" content="width=device-width,user-scalable=no">

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

קלט עכבר, מגע ומקלדת

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

לדוגמה, אנחנו יכולים להגדיר את התנועה של המפה (dragDX ו- DragDY) באמצעות כל שלוש שיטות הקלט. כך יישום המקלדת:

document.addEventListener('keydown', onKeyDown );
document.addEventListener('keyup', onKeyUp );

function onKeyDown( event ) {
  input.keyCodes[ "k" + event.keyCode ] = true;
  input.shiftKey = event.shiftKey;
}

function onKeyUp( event ) {
  input.keyCodes[ "k" + event.keyCode ] = false;
  input.shiftKey = event.shiftKey;
}

//this needs to be called every frame before animation is executed
function handleKeyInput(){
  if(input.keyCodes.k37){
    input.dragDX = -5; //37 arrow left
  } else if(input.keyCodes.k39){
    input.dragDX = 5; //39 arrow right
  }
  if(input.keyCodes.k38){
    input.dragDY = -5; //38 arrow up
  } else if(input.keyCodes.k40){
    input.dragDY = 5; //40 arrow down
  }
}

function onAnimationFrame() {
  requestAnimationFrame( onAnimationFrame );
  //because keydown events are not fired every frame we need to process the keyboard state first
  handleKeyInput();
  //implement animations based on what is stored in input
   /*
  /
  */

  //because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
  input.dragDX = 0;
  input.dragDY = 0;
}

דוגמה מוטמעת: שימוש בעכבר, מגע ומקלדת לניווט: http://cdpn.io/catlf

סיכום

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

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

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

עכשיו, אם עדיין לא עשיתם זאת, זה הזמן ליצור משהו מדהים!