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

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

אמ;לק

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

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

הבעיה

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

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

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

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

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

במורד חור הארנב

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

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

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

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

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

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

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

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

הפקה

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

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

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

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

סיכום

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