Parallaxin'

מבוא

אתרי Parallax הם זעזועים רבים לאחרונה, כדאי שתסתכל על הדברים הבאים:

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

דף פרלקס להדגמה
דף ההדגמה שלנו כולל אפקט של פרלקס

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

הגיוני לכלול אתר פרלקס באופן הבא:

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

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

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

אפשרות 1: שימוש ברכיבי DOM ובמיקומים מוחלטים

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

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

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

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

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

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

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

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

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

אפשרות 2: שימוש ברכיבי DOM ובהמרות תלת-ממדיות

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

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

הרבה פעמים אנשים משתמשים בפריצה של -webkit-transform: translateZ(0); ורואים שיפורי ביצועים פלאים, ולמרות שהשיטה הזו עובדת כיום, יש בעיות:

  1. הוא לא תואם לדפדפנים שונים.
  2. הטכנולוגיה הזאת מאלצת את הדפדפן ליצור שכבה חדשה לכל רכיב שהשתנה. שכבות רבות עלולות לגרום לצווארי בקבוק אחרים בביצועים, לכן מומלץ להשתמש בחוכמה!
  3. היא הושבתה בחלק מיציאות WebKit (תבליט רביעי מלמטה!).

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

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

אפשרות 3: שימוש בבד ציור עם מיקום קבוע או ב-WebGL

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

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

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


/**
 * Updates and draws in the underlying visual elements to the canvas.
 */
function updateElements () {

  var relativeY = lastScrollY / h;

  // Fill the canvas up
  context.fillStyle = "#1e2124";
  context.fillRect(0, 0, canvas.width, canvas.height);

  // Draw the background
  context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));

  // Draw each of the blobs in turn
  context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
  context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
  context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
  context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
  context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
  context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
  context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
  context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
  context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));

  // Allow another rAF call to be scheduled
  ticking = false;
}

/**
 * Calculates a relative disposition given the page's scroll
 * range normalized from 0 to 1
 * @param {number} base The starting value.
 * @param {number} range The amount of pixels it can move.
 * @param {number} relY The normalized scroll value.
 * @param {number} offset A base normalized value from which to start the scroll behavior.
 * @returns {number} The updated position value.
 */
function pos(base, range, relY, offset) {
  return base + limit(0, 1, relY - offset) * range;
}

/**
 * Clamps a number to a range.
 * @param {number} min The minimum value.
 * @param {number} max The maximum value.
 * @param {number} value The value to limit.
 * @returns {number} The clamped value.
 */
function limit(min, max, value) {
  return Math.max(min, Math.min(max, value));
}

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

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

התגובה המיידית שלכם עשויה להיות ש-WebGL עלול להיגמר, או שהוא לא מופיע בכל מקום במונחים של תמיכה, אבל אם תשתמשו בערך כמו Three.js, תמיד תוכלו להשתמש ברכיב בד ציור והקוד מופשט בצורה עקבית וידידותית. כל מה שאנחנו צריכים לעשות הוא להשתמש ב-Modernizr כדי לבדוק אם יש תמיכה ב-API המתאימה:

// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
  renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
  renderer = new THREE.CanvasRenderer();
}

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

הבחירה היא שלך

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

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

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

סיכום

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

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