פרלקסציה ביצועית

Robert Flack
Robert Flack

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

איור של פרלקס.

אמ;לק

  • אין להשתמש באירועי גלילה או ב-background-position כדי ליצור אנימציות של פרלקס.
  • השתמשו בשינויים ב-CSS תלת-ממדי כדי ליצור אפקט פרלקס מדויק יותר.
  • ב-Mobile Safari, צריך להשתמש ב-position: sticky כדי לוודא שמופץ אפקט הפרלקס.

אם אתם מחפשים את פתרון ההוספה, עברו למאגר GitHub של UI Element Samples וקנו את Parallax helper JS! תוכלו לראות הדגמה פעילה של פס ההזזה של פרלקס במאגר של GitHub.

מפרקים של בעיות

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

לא טוב: שימוש באירועי גלילה

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

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

לא טוב: מתבצע עדכון של background-position

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

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

CSS בתלת-ממד

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

  • מגדירים רכיב מכיל כדי לגלול עם overflow-y: scroll (וכנראה overflow-x: hidden).
  • על אותו הרכיב צריך להחיל את הערך perspective, ו-perspective-origin שמוגדר ל-top left או ל-0 0.
  • בשביל הצאצאים של הרכיב הזה, מחילים תרגום ב-Z ונותנים גיבוי לגביהם כדי לספק תנועת פרלקס בלי להשפיע על הגודל שלהם על המסך.

שירות ה-CSS בגישה הזו נראה כך:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

ההנחה היא בקטע HTML כזה:

<div class="container">
    <div class="parallax-child"></div>
</div>

כוונון קנה המידה של פרספקטיבה

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

במקרה של הקוד שלמעלה, הפרספקטיבה היא 1px והמרחק של ה-Z ב-parallax-child הוא -2px. המשמעות היא שצריך להגדיל את הרכיב לפי 3x, כפי שאפשר לראות הוא הערך שמחובר לקוד: scale(3).

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

איך הגישה הזו עובדת

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

עם זאת, החלת ערך פרספקטיבה על רכיב הגלילה מבלבלת את התהליך הזה; היא משנה את המטריצות שעומדות בבסיס השינוי בגלילה. עכשיו גלילה של 300 פיקסלים עשויה להזיז את הילדים ב-150 פיקסלים בלבד, בהתאם לערכים perspective ו-translateZ שבחרתם. אם לרכיב יש ערך translateZ 0, הוא ייגלל ביחס 1:1 (כפי שהיה בעבר), אבל צאצא שיידחף ב-Z ממקור הפרספקטיבה ייגלל בקצב שונה! התוצאה נטו: תנועת פרלקס. והדבר חשוב מאוד, הטיפול הזה מתבצע באופן אוטומטי כחלק ממכונות הגלילה הפנימיות של הדפדפן, כלומר אין צורך להקשיב לאירועי scroll או לשנות את background-position.

זבוב במשחה: Mobile Safari

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

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

ב-HTML שלמעלה, ה-.parallax-container חדש והוא יטשטש את הערך perspective בצורה יעילה ונאבד את אפקט הפרלקס. ברוב המקרים הפתרון פשוט למדי: מוסיפים את transform-style: preserve-3d לאלמנט ומפזרים אפקטים תלת-ממדיים (כמו ערך הפרספקטיבה שלנו) שהוחלו במעלה העץ.

.parallax-container {
  transform-style: preserve-3d;
}

במקרה של Safari בנייד, הדברים קצת יותר מורכבים. מבחינה טכנית אפשר להחיל את overflow-y: scroll על רכיב הקונטיינר, אבל זה כרוך בעלות היכולת להטות את רכיב הגלילה. הפתרון הוא להוסיף את הפקודה -webkit-overflow-scrolling: touch, אבל היא גם תשטח את ה-perspective ולא תתקבל פרלקסיה.

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

position: sticky כדי להציל!

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

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

באמצעות החלת position: -webkit-sticky על האלמנט המפרך, אפשר למעשה "להפוך" את אפקט ההשטחה של -webkit-overflow-scrolling: touch. כך ניתן לוודא שהרכיב שמרכיב את החזרת מפנה אל ישות האב הקרובה ביותר באמצעות תיבת גלילה, שבמקרה הזה היא .container. לאחר מכן, בדומה למה שהיה קודם, .parallax-container מחיל ערך perspective, שמשנה את היסט הגלילה המחושב ויוצר אפקט פרלקס.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

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

אזהרות לגבי מיקום במיקום קבוע

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

  • כשהרכיב position: sticky קרוב יותר ל-z=0, הוא נמוך יותר.
  • בלי position: sticky, ככל שהרכיב קרוב יותר ל-z=0 כך יותר הוא זז.

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

צילום מסך של נקודת המבט של Parallax

הדגמה של רוברט פלאק, שמראה איך position: sticky משפיע על גלילה פרלקסית.

באגים שונים ופתרונות עקיפים

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

  • תמיכה לא עקבית. התמיכה עדיין מיושמת ב-Chrome, אין ב-Edge תמיכה מלאה וב-Firefox יש באגים בציור של 'הקשה ביד אחת' בשילוב עם טרנספורמציה של נקודת המבט. במקרים כאלה, כדאי להוסיף מעט קוד כדי להוסיף רק את position: sticky (הגרסה עם הקידומת -webkit-) כשצריך, והיא מיועדת רק ל-Mobile Safari.
  • האפקט לא "רק עובד" ב-Edge. Edge מנסה לטפל בגלילה ברמת מערכת ההפעלה, וזה דבר טוב בדרך כלל, אבל במקרה הזה הוא מונע ממנו לזהות את השינויים בנקודת המבט בזמן הגלילה. כדי לפתור את הבעיה, תוכלו להוסיף רכיב מיקום קבוע, כי נראה שהפעולה הזו מעבירה את Edge ל שיטת גלילה שאינה של מערכת ההפעלה, ומבטיחה שהיא תיקח בחשבון שינויים בנקודת המבט.
  • "התוכן של הדף הפך לעצום!" דפדפנים רבים לוקחים בחשבון את ההיקף הזה כשמחליטים מה גודל התוכן בדף, אבל לצערנו ב-Chrome וב-Safari לא נלקחים בחשבון פרספקטיבה. כך אם יש סולם של פי 3 על רכיב, ייתכן שתראו סרגלי גלילה וכדומה, גם אם הרכיב נמצא בגודל של פי 1 לאחר החלת ה-perspective. אפשר לעקוף את הבעיה על ידי שינוי אלמנטים מהפינה הימנית התחתונה (עם transform-origin: bottom right), פעולה שגורמת לאלמנטים גדולים להפוך ל'אזור שלילי' (בדרך כלל בפינה השמאלית העליונה) של האזור הניתן לגלילה. אזורים שבהם ניתן לגלול אף פעם לא מאפשרים לראות תוכן באזור השלילי או לגלול לתוכן אחר.

סיכום

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

שחקו וספרו לנו איך אתם מתקדמים.