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

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

אמ;לק

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

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

הבעיה

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

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

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

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

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

הירידה למטה

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

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

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

DevTools שמציגים מעקב שבו ל-GPU יש תקופות ארוכות של זמן פעילות.

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

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

DevTools שמוצגת בו נתיב שבו למעבד הגרפי יש הרבה זמן חוסר פעילות.

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

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

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

רוב האנשים חושבים על Shadow DOM כדרך לצרף אלמנטים 'פנימיים' לרכיבים מותאמים אישית, אבל הוא גם רכיב בסיסי לבידוד ולשיפור הביצועים. 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.