הוספת אנימציה לטשטוש

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

אמ;לק

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

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

הבעיה

המעבד (CPU) הופך את תגי העיצוב לטקסטורות. הטקסטורות מועלות ל-GPU. ה-GPU מצייר את הטקסטורות האלה לתוך מאגר המסגרות באמצעות הצללות. הטשטוש מתבצע ב-shader.

נכון לעכשיו, אנחנו לא יכולים לגרום לאנימציה של טשטוש לפעול ביעילות. עם זאת, אנחנו יכולים למצוא פתרון שעובד מספיק טוב, אבל מבחינה טכנית הוא לא טשטוש מונפש. כדי להתחיל, נסביר קודם למה הטשטוש המונפש איטי. יש שתי שיטות לטשטוש אלמנטים באינטרנט: מאפיין ה-CSS‏ filter ומסנני SVG. בדרך כלל משתמשים במסנני CSS כי הם נתמכים יותר וקל יותר להשתמש בהם. לצערנו, אם אתם נדרשים לתמוך ב-Internet Explorer, אין לכם ברירה אלא להשתמש במסנני SVG, כי גרסאות IE 10 ו-11 תומכות בהם אבל לא במסנני CSS. החדשות הטובות הן שהפתרון שלנו להנפשת טשטוש פועל עם שתי הטכניקות. אז ננסה למצוא את צוואר הבקבוק באמצעות כלי הפיתוח.

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

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

כאן הבעיה מתחילה. אנחנו מריצים פעולת GPU יקרה למדי בכל פריים, מה שגורם לחריגה מהתקציב של 16ms לכל פריים, ולכן אנחנו מקבלים קצב פריימים נמוך בהרבה מ-60fps.

Down the rabbit hole

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

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

בניסויים שערכנו, הגדלה של רדיוס הטשטוש באופן אקספוננציאלי בכל שלב הניבה את התוצאות החזותיות הטובות ביותר. דוגמה: אם יש לנו ארבעה שלבי טשטוש, נחיל filter: blur(2^n) על כל שלב, כלומר שלב 0: 1px, שלב 1: 2px, שלב 2: 4px ושלב 3: 8px. אם נכפה על כל אחד מהעותקים המטושטשים האלה להיות בשכבה משלו (פעולה שנקראת 'קידום') באמצעות will-change: transform, שינוי השקיפות של הרכיבים האלה יהיה מהיר מאוד. באופן תיאורטי, זה יאפשר לנו להקדים את העבודה היקרה של הטשטוש. מסתבר שהלוגיקה פגומה. אם תפעילו את ההדגמה הזו, תראו שקצב הפריימים עדיין נמוך מ-60fps, והטשטוש גרוע יותר ממה שהיה קודם.

‫DevTools
  מציג מעקב שבו יחידת ה-GPU עסוקה במשך תקופות ארוכות.

בדיקה מהירה ב-DevTools מגלה שה-GPU עדיין עמוס מאוד ומותח כל פריים ל-90ms בערך. אבל למה? We are not changing the blur value anymore, only the opacity, so what's happening? הבעיה נובעת שוב מאופי אפקט הטשטוש: כמו שהוסבר קודם, אם הרכיב גם מועבר לקידום וגם מטשטש, האפקט מוחל על ידי ה-GPU. לכן, למרות שאנחנו כבר לא מנפישים את ערך הטשטוש, הטקסטורה עצמה עדיין לא מטשטשת וצריך לטשטש אותה מחדש בכל פריים על ידי ה-GPU. הסיבה לכך שקצב הפריימים גרוע עוד יותר מבעבר נובעת מהעובדה שבהשוואה להטמעה הפשוטה, למעשה יש ל-GPU יותר עבודה מבעבר, כי ברוב הזמן אפשר לראות שתי טקסטורות שצריך לטשטש בנפרד.

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

‫DevTools
  מציג מעקב שבו למעבד הגרפי יש הרבה זמן השבתה.

עכשיו יש לנו הרבה מרווח ב-GPU וקצב רענון חלק של 60fps. הצלחתם!

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

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

רוב האנשים חושבים על Shadow DOM כדרך לצרף אלמנטים 'פנימיים' ל-Custom Elements שלהם, אבל הוא גם פרימיטיב של בידוד וביצועים. אי אפשר להשתמש ב-JavaScript וב-CSS כדי לחדור לגבולות של Shadow DOM, ולכן אנחנו יכולים לשכפל תוכן בלי להפריע לסגנונות או ללוגיקה של האפליקציה של המפתח. כבר יש לנו רכיב <div> לכל עותק כדי לבצע רסטריזציה, ועכשיו אנחנו משתמשים ברכיבי <div> האלה כמארחי צל. אנחנו יוצרים ShadowRoot באמצעות attachShadow({mode: 'closed'}) ומצרפים עותק של התוכן לShadowRoot במקום ל<div> עצמו. כדי לוודא שהעותקים שלנו ייראו בדיוק כמו המקור, אנחנו צריכים להקפיד להעתיק את כל גיליונות הסגנון אל ShadowRoot.

דפדפנים מסוימים לא תומכים ב-Shadow DOM v1, ובמקרים כאלה אנחנו פשוט משכפלים את התוכן ומקווים שהכול יעבוד. יכולנו להשתמש ב-Shadow DOM polyfill עם ShadyCSS, אבל לא הטמענו את זה בספרייה שלנו.

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

סיכום

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