הצגת לבני LEGO® באינטרנט במכשירים מרובים
Build with Chrome הוא ניסוי מהנה למשתמשי Chrome במחשב שנפתח במקור באוסטרליה. הוא הופץ מחדש בשנת 2014 עם זמינות גלובלית, שיתופי פעולה עם הסרט 'לגו® מובייל' ותמיכה חדשה במכשירים ניידים. במאמר הזה נשתף כמה מהלקחים מהפרויקט, במיוחד בנוגע למעבר מחוויית שימוש במחשב בלבד לפתרון עם כמה מסכים שתומך גם בהזנת נתונים באמצעות עכבר וגם בהזנת נתונים באמצעות מגע.
ההיסטוריה של Build with Chrome
הגרסה הראשונה של Build with Chrome הושקה באוסטרליה בשנת 2012. רצינו להראות את העוצמה של האינטרנט בדרך חדשה לגמרי ולהציג את Chrome לקהל חדש לגמרי.
האתר כלל שני חלקים עיקריים: מצב 'יצירה' שבו המשתמשים יכולים ליצור יצירות באמצעות לבני LEGO, ומצב 'סיור' לגלישה ביצירות בגרסה של מפות Google שעשויה מ-LEGO.
התכונה '3D אינטראקטיבי' הייתה חיונית כדי לספק למשתמשים את חוויית הבנייה הטובה ביותר ב-LEGO. בשנת 2012, WebGL היה זמין לכולם רק בדפדפנים למחשב, ולכן Build התמקד בחוויה למחשב בלבד. ב-Explore נעשה שימוש במפות Google כדי להציג את היצירות, אבל כשמצמידים את התצוגה, המערכת עוברת להטמעה של WebGL של המפה שבה מוצגות היצירות בתלת-ממד, ועדיין משתמשת במפות Google כטקסטורה של רצפת הבסיס. רצינו ליצור סביבה שבה חובבי LEGO בכל הגילאים יוכלו להביע את היצירתיות שלהם בקלות ובאופן אינטואיטיבי, ולעיין ביצירות של אחרים.
בשנת 2013 החלטנו להרחיב את Build with Chrome לטכנולוגיות אינטרנט חדשות. אחת מהטכנולוגיות האלה הייתה WebGL ב-Chrome ל-Android, שאפשרה ל-Build with Chrome להתפתח לחוויה בנייד. כדי להתחיל, פיתחנו תחילה אב טיפוס של מגע לפני שבדקנו את החומרה של 'כלי ה-Builder' כדי להבין את התנהגות התנועות ואת תגובת המגע שעשויות להתרחש בדפדפן בהשוואה לאפליקציה לנייד.
ממשק קצה רספונסיבי
היינו צריכים לתמוך במכשירים עם קלט מגע וקלט עכבר. עם זאת, השימוש באותו ממשק משתמש במסכי מגע קטנים התברר כפתרון לא אופטימלי בגלל מגבלות מקום.
ב-Build יש הרבה אינטראקציה: הגדלה והקטנה של התצוגה, שינוי צבעי הלבנים וכמובן בחירה, סיבוב והנחה של לבנים. זהו כלי שבו משתמשים מבלים הרבה זמן, ולכן חשוב שתהיה להם גישה מהירה לכל מה שהם משתמשים בו לעיתים קרובות, ושהאינטראקציה איתו תהיה נוחה.
כשמעצבים אפליקציית מגע אינטראקטיבית מאוד, מגלים במהירות שהמסך נראה קטן והאצבעות של המשתמש נוטות לכסות חלק גדול מהמסך במהלך האינטראקציה. הבנו את זה כשעבדנו עם ה-Builder. כשאתם מעצבים, חשוב להביא בחשבון את גודל המסך הפיזי ולא את הפיקסלים בגרפיקה. חשוב לצמצם את מספר הלחצנים ואמצעי הבקרה כדי להגדיל את שטח המסך שמיועד לתוכן בפועל.
המטרה שלנו הייתה ליצור תחושה טבעית ב-Build במכשירי מגע, לא רק להוסיף קלט מגע להטמעה המקורית במחשב, אלא ליצור תחושה כאילו הוא באמת מיועד למגע. בסופו של דבר יצרנו שתי גרסאות של ממשק המשתמש, אחת למחשבים ולטאבלטים עם מסכים גדולים ואחת למכשירים ניידים עם מסכים קטנים יותר. כשהדבר אפשרי, מומלץ להשתמש בהטמעה אחת ולאפשר מעבר חלק בין המצבים. במקרה שלנו, הגענו למסקנה שיש הבדל משמעותי כל כך בחוויית השימוש בין שני המצבים האלה, ולכן החלטנו להסתמך על נקודת עצירה ספציפית. לשתי הגרסאות יש הרבה תכונות משותפות, וניסינו לבצע את רוב הפעולות באמצעות הטמעת קוד אחת בלבד, אבל יש הבדלים מסוימים בין שתי הגרסאות לגבי היבטים מסוימים בממשק המשתמש.
אנחנו משתמשים בנתוני סוכן המשתמש כדי לזהות מכשירים ניידים, ואז בודקים את גודל שדה התצוגה כדי להחליט אם להשתמש בממשק המשתמש לנייד עם מסך קטן. קשה לבחור נקודת עצירה כדי לקבוע מהו 'מסך גדול', כי קשה לקבל ערך מהימן של גודל המסך הפיזי. למזלנו, במקרה שלנו לא משנה אם אנחנו מציגים את ממשק המשתמש של המסך הקטן במכשיר מגע עם מסך גדול, כי הכלי עדיין יפעל כמו שצריך, רק שחלק מהלחצנים עשויים להיראות קצת גדולים מדי. בסופו של דבר, הגדרנו את נקודת העצירה ל-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 חזקים למדי, אבל הם עדיין רחוקים ממעבדיהם המקבילים במחשבים, לכן ידענו שיהיו לנו כמה אתגרים לגבי הביצועים, במיוחד במצב 'סיור 3D' שבו אנחנו צריכים ליצור רינדור של הרבה יצירות בו-זמנית.
מבחינה יצירתית, רצינו להוסיף כמה סוגים חדשים של לבנים עם צורות מורכבות ואפילו שקופות – תכונות שגורמות בדרך כלל לעומס רב על המעבד הגרפי (GPU). עם זאת, היינו צריכים לשמור על תאימות לאחור ולהמשיך לתמוך ביצירות מהגרסה הראשונה, ולכן לא יכולנו להגדיר הגבלות חדשות, כמו צמצום משמעותי של המספר הכולל של הלבנים ביצירות.
בגרסה הראשונה של Build הייתה לנו מגבלת מספר מקסימלי של לבנים שניתן להשתמש בהן ביצירה אחת. היה 'מטר לבנים' שציין כמה לבנים נותרו. בהטמעה החדשה, חלק מהלבנים החדשות השפיעו על מדד הלבנים יותר מהלבנים הרגילות, וכך צמצמנו מעט את המספר הכולל המקסימלי של לבנים. זו הייתה אחת הדרכים לכלול אבנים חדשות ועדיין לשמור על ביצועים סבירים.
במצב 'סיור 3D' מתרחשים בו-זמנית הרבה דברים: טקסטורות של משטח הבסיס נטענות, יצירות נטענות, יצירות מקבלות אנימציה ורינדור וכו'. הפעולה הזו דורשת הרבה מ-GPU ומ-CPU, ולכן ביצענו הרבה ניתוח פרופילים של פריימים בכלי הפיתוח של Chrome כדי לבצע אופטימיזציה של החלקים האלה ככל האפשר. במכשירים ניידים, החלטנו להתקרב קצת יותר ליצירות כדי שלא נצטרך ליצור עיבוד (רנדור) של יצירות רבות בו-זמנית.
בחלק מהמכשירים נאלצנו לחזור על חלק מהשידרוגים של WebGL ולפשט אותם, אבל תמיד מצאנו דרך לפתור את הבעיה ולהמשיך הלאה.
תמיכה במכשירים ללא WebGL
רצינו שהאתר יהיה שימושי במידה מסוימת גם אם המכשיר של המבקר לא תומך ב-WebGL. לפעמים יש דרכים לייצג את התלת-ממד בצורה פשוטה יותר באמצעות פתרון בד הציור או תכונות של CSS3D. לצערנו, לא מצאנו פתרון טוב מספיק כדי לשכפל את התכונות של 'יצירה' ו'עיון' בתלת-ממד בלי להשתמש ב-WebGL.
כדי לשמור על עקביות, הסגנון החזותי של הנכסים הדיגיטליים צריך להיות זהה בכל הפלטפורמות. יכול להיות שהיינו מנסים פתרון 2.5D, אבל זה היה גורם ליצירות להיראות שונות במובנים מסוימים. כמו כן, נדרשנו לחשוב איך לוודא שהיצירות שנוצרו בגרסה הראשונה של 'יצירה ב-Chrome' ייראו אותו דבר ויפעלו בצורה חלקה בגרסה החדשה של האתר, כמו בגרסה הראשונה.
עדיין אפשר לגשת למצב 'עיון' ב-2D במכשירים שלא תומכים ב-WebGL, אבל אי אפשר ליצור יצירות חדשות או לעיין ביצירות ב-3D. כך המשתמשים עדיין יכולים לקבל מושג על עומק הפרויקט ועל מה שהם יכולים ליצור באמצעות הכלי הזה אם הם משתמשים במכשיר עם תמיכה ב-WebGL. יכול להיות שהאתר לא יהיה שימושי למשתמשים ללא תמיכה ב-WebGL, אבל הוא לפחות יכול לשמש כטיזר ולעודד אותם לנסות אותו.
לפעמים פשוט אי אפשר לשמור גרסאות חלופיות לפתרונות WebGL. יש הרבה סיבות אפשריות: ביצועים, סגנון חזותי, עלויות פיתוח ותחזוקה ועוד. עם זאת, אם תחליטו לא להטמיע חלופה, עליכם לכל הפחות לדאוג למבקרים שלא תומכים ב-WebGL, להסביר להם למה אין להם גישה מלאה לאתר ולתת להם הוראות לפתרון הבעיה באמצעות דפדפן שתומך ב-WebGL.
ניהול נכסים
בשנת 2013, Google השיקה גרסה חדשה של מפות Google עם השינויים המשמעותיים ביותר בממשק המשתמש מאז ההשקה. לכן החלטנו לעצב מחדש את Build with Chrome כך שיתאים לממשק המשתמש החדש של מפות Google, תוך התחשבות בגורמים נוספים. העיצוב החדש שטוח יחסית, עם צבעים נקיים וצורות פשוטות. כך הצלחנו להשתמש ב-CSS טהור בחלק גדול מרכיבי ממשק המשתמש, ולצמצם את השימוש בתמונות.
ב-Explore אנחנו צריכים לטעון הרבה תמונות: תמונות ממוזערות של היצירות, טקסטורות של מפות לפלחי הבסיס ולבסוף את היצירות התלת-ממדיות עצמן. אנחנו מקפידים מאוד שלא תהיה דליפה של זיכרון כשאנחנו טוענים תמונות חדשות כל הזמן.
היצירות בתלת-ממד מאוחסנות בפורמט קובץ מותאם אישית שמארז כתמונה בפורמט PNG. שמירת הנתונים של היצירות ב-3D כתמונה אפשרה לנו למעשה להעביר את הנתונים ישירות לשיחרים (shaders) שמרינדרים את היצירות.
בעזרת העיצוב הזה הצלחנו להשתמש באותם גדלים של תמונות בכל הפלטפורמות של התמונות שנוצרו על ידי משתמשים, וכך לצמצם את השימוש בנפח האחסון וברוחב הפס.
ניהול כיוון המסך
קל לשכוח כמה יחס הגובה-רוחב של המסך משתנה כשעוברים ממצב לאורך למצב לרוחב או להפך. חשוב להביא את הנושא הזה בחשבון כבר מההתחלה כשמתאימים את האתר למכשירים ניידים.
באתר רגיל שבו התכונה 'גלילה' מופעלת, אפשר להחיל כללי CSS כדי ליצור אתר רספונסיבי שמסדר מחדש את התוכן והתפריטים. כל עוד אפשר להשתמש בפונקציונליות של גלילה, זה קל יחסית.
השתמשנו בשיטה הזו גם ב-Build, אבל האפשרויות שלנו לפתרון הבעיה של הפריסה היו מוגבלות, כי היה עלינו לוודא שהתוכן יהיה גלוי בכל זמן, ועדיין תהיה גישה מהירה למספר פקדים וללחצנים. באתרי תוכן טהורים כמו אתרי חדשות, פריסת תוכן דינמית היא הגיונית מאוד, אבל באפליקציית משחק כמו שלנו זה היה מאבק. היה לנו אתגר למצוא פריסה שתעבוד גם בכיוון לאורך וגם בכיוון לרוחב, תוך שמירה על סקירה כללית טובה של התוכן ועל דרך נוחה לאינטראקציה. בסופו של דבר החלטנו להשאיר את Build במצב לרוחב בלבד, ומבקשים מהמשתמש לסובב את המכשיר.
היה הרבה יותר קל לפתור את הבעיה בדף 'מה חדש' בשתי התצוגות. כדי לקבל חוויה עקבית, היינו צריכים רק לשנות את רמת הזום של התצוגה התלת-ממדית בהתאם לכיוון.
רוב הפריסה של התוכן נשלטת על ידי CSS, אבל חלק מהדברים שקשורים לכיוון נדרשו להטמעה ב-JavaScript. גילינו שאין פתרון טוב לזיהוי הכיוון במכשירים שונים באמצעות window.orientation, ולכן בסופו של דבר השווינו בין window.innerWidth לבין window.innerHeight כדי לזהות את הכיוון של המכשיר.
if( window.innerWidth > window.innerHeight ){
//landscape
} else {
//portrait
}
הוספת תמיכה במגע
הוספת תמיכה במגע לתוכן אינטרנט היא פשוטה למדי. אינטראקציה בסיסית, כמו אירוע הקליק, פועלת באופן זהה במחשבים ובמכשירים עם תמיכה במגע, אבל כשמדובר באינטראקציות מתקדמות יותר, צריך לטפל גם באירועי המגע: touchstart, touchmove ו-touchend. במאמר הזה מוסבר איך משתמשים באירועים האלה. הדפדפן Internet Explorer לא תומך באירועי מגע, אלא משתמש באירועי סמן (pointerdown, pointermove, pointerup). אירועי סמן הוצגו ל-W3C לצורך סטנדרטיזציה, אבל נכון לעכשיו הם מיושמים רק ב-Internet Explorer.
במצב 'סיור 3D', רצינו שתהיה אותה ניווט כמו בהטמעה הרגילה של מפות Google: שימוש באצבע אחת כדי להזיז את המפה, וצביטה בשתי אצבעות כדי לשנות את מרחק התצוגה. מכיוון שהיצירות הן תלת-ממדיות, הוספנו גם את התנועת הסיבוב בשתי אצבעות. בדרך כלל, כדי לעשות זאת צריך להשתמש באירועי מגע.
מומלץ להימנע מפעולות מחשוב כבדות, כמו עדכון או עיבוד של התלת-ממד בטיפולי האירועים. במקום זאת, שומרים את קלט המגע במשתנה ומגיבים לקלט בלולאת הרינדור של requestAnimationFrame. כך גם קל יותר להטמיע עכבר בו-זמנית, פשוט שומרים את ערכי העכבר התואמים באותם משתנים.
מתחילים ביוצרות אובייקט לאחסון הקלט ומוסיפים את מאזין האירועים touchstart. בכל פונקציית טיפול באירוע אנחנו קוראים ל-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);
}
}
אנחנו לא מאחסנים את הקלט בפועל בגורמים המטפלים באירועים, אלא בגורמים נפרדים: handleDragStart, handleDragging ו-handleDragStop. הסיבה לכך היא שאנחנו רוצים שאפשר יהיה להפעיל אותם גם מהגורמים שמטפלים באירועי העכבר. חשוב לזכור שלמרות שהסיכוי לכך נמוך, המשתמש עשוי להשתמש במגע ובעכבר בו-זמנית. במקום לטפל בפנייה הזו ישירות, אנחנו רק מוודאים שלא יקרה משהו רציני.
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;
}
}
כשמשתמשים באנימציות שמבוססות על תנועת מגע, לרוב כדאי לשמור גם את המעבר של הדלתה מאז האירוע האחרון. לדוגמה, השתמשנו בנתון הזה כפרמטר למהירות המצלמה כשהיא נעה בין כל פלטות הבסיס ב-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;
}
דוגמה מוטמעת: גרירה של אובייקט באמצעות אירועי מגע. הטמעה דומה לזו של גרירה של מפה תלת-ממדית ב-Explore ב-Build with Chrome: http://cdpn.io/qDxvo
תנועות מולטי-טאץ'
יש כמה מסגרות או ספריות, כמו 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;
}
אם רוצים, אפשר גם לאפשר גרירה של האובייקט בזמן ביצוע התנועות של הצמדה וסיבוב. במקרה כזה, צריך להשתמש בנקודת המרכז בין שתי האצבעות כקלט למטפל בגרירה.
דוגמה מוטמעת: סיבוב של אובייקט והתאמת קנה המידה שלו ב-2D. בדומה לאופן שבו המפה בכרטיסייה 'מה יש באזור' מוטמעת: http://cdpn.io/izloq
תמיכה בעכבר ובמגע באותה חומרה
כיום יש כמה מחשבים ניידים, כמו Chromebook Pixel, שתומכים גם בהזנת עכבר וגם בהזנת מגע. אם לא תהיו זהירים, הדבר עלול לגרום להתנהגויות לא צפויות.
חשוב לזכור: לא מספיק לזהות תמיכה במגע ולאחר מכן להתעלם מהקלדה בעכבר, אלא צריך לתמוך בשניהם בו-זמנית.
אם אתם לא משתמשים ב-event.preventDefault()
במטפלי האירועים של המגע, יופעלו גם כמה אירועי עכבר שהועתקו, כדי שרוב האתרים שלא אופטימיזציה למגע ימשיכו לפעול. לדוגמה, אחרי הקשה אחת על המסך, האירועים האלה עשויים להתרחש ברצף מהיר ובסדר הזה:
- touchstart
- touchmove
- touchend
- שימוש בעכבר
- mousemove
- mousedown
- mouseup
- click
אם יש לכם אינטראקציות מורכבות יותר, אירועי העכבר האלה עשויים לגרום להתנהגות בלתי צפויה ולשבש את ההטמעה. בדרך כלל עדיף להשתמש ב-event.preventDefault()
בגורמים המטפלים באירועי המגע ולנהל את הקלט מהעכבר בגורמים נפרדים לטיפול באירועים. חשוב לזכור ששימוש ב-event.preventDefault()
בטיפול באירועי מגע ימנע גם התנהגות ברירת מחדל מסוימת, כמו גלילה ואירוע הקליק.
"ב-Build with Chrome לא רצינו שהתמונה תתרחב כשמשתמשים מקישים הקשה כפולה על האתר, למרות שזו תכונה סטנדרטית ברוב הדפדפנים. לכן אנחנו משתמשים במטא תג של אזור התצוגה כדי להורות לדפדפן לא להתקרב כשהמשתמש מקייש הקשה כפולה. כך גם מסירים את עיכוב הלחיצה של 300 אלפיות השנייה, וכך משפרים את תגובת האתר. (השהיית הקליק נועדה להבדיל בין הקשה אחת להקשה כפולה כשהתכונה 'הגדלת התצוגה בהקשה כפולה' מופעלת).
<meta name="viewport" content="width=device-width,user-scalable=no">
חשוב לזכור: כשמשתמשים בתכונה הזו, אתם צריכים לוודא שהאתר קריא בכל גדלי המסכים, כי המשתמש לא יוכל להתקרב אליו.
קלט מהעכבר, מהמגע ומהמקלדת
במצב 'סיור 3D', רצינו שיהיו שלוש דרכים לנווט במפה: עכבר (גרירה), מגע (גרירה, צביטה כדי לשנות את מרחק התצוגה וסיבוב) ומקלדת (ניווט באמצעות מקשי החיצים). כל שיטות הניווט האלה פועלות בצורה שונה במקצת, אבל השתמשנו באותה גישה בכל אחת מהן: הגדרת משתנים במטפלי אירועים וביצוע פעולה על סמך זה בלול 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;
}
דוגמה מוטמעת: naviguation using mouse, touch and keyboard: http://cdpn.io/catlf
סיכום
התאמת Build with Chrome כך שיתמוך במכשירי מגע עם גדלים שונים של מסכים הייתה חוויית למידה. לצוות לא היה הרבה ניסיון ביצירת רמת אינטראקטיביות כזו במכשירי מגע, ולמדנו הרבה לאורך הדרך.
האתגר הגדול ביותר היה איך לפתור את הבעיות בחוויית המשתמש ובעיצוב. האתגרים הטכניים היו ניהול של הרבה גדלי מסכים, אירועי מגע ובעיות בביצועים.
אמנם היו כמה אתגרים עם שיבובי ה-WebGL במכשירי מגע, אבל זה עבד כמעט טוב יותר מהצפוי. המכשירים נעשים חזקים יותר ויותר והטמעות WebGL הולכות ומשתפרות במהירות. אנחנו צופים שנשתמש ב-WebGL במכשירים הרבה יותר בעתיד הקרוב.
עכשיו, אם עדיין לא עשיתם זאת, יוצרים משהו מגניב!