אפשר לשפר את האנימציות של אפליקציית האינטרנט
אמ;לק: worklet של אנימציה מאפשר לכתוב אנימציות חיוניות שרצות בקצב הפריימים המקורי של המכשיר, כדי ליהנות מחוויית החלקה (jank) נטולת חמאה עוד יותר, כדי שהאנימציות יהיו עמידות יותר מפני בעיות בממשק הראשי של ה-thread הראשי, כך שניתן יהיה לקשר אותן כדי לגלול במקום בזמן. worklet של האנימציה נמצא ב-Chrome Canary (מאחורי הקלעים "תכונות ניסיוניות של פלטפורמת האינטרנט" ) ואנחנו מתכננים גרסת מקור לניסיון ל-Chrome 71. אפשר להתחיל להשתמש בו בתור לבצע שיפור הדרגתי עוד היום.
עוד ממשק API לאנימציה?
למעשה לא, זו הרחבה של מה שכבר יש לנו, ויש סיבה טובה לעשות זאת! נתחיל מההתחלה. אם ברצונך להוסיף אנימציה לרכיב DOM כלשהו באינטרנט היום יש לכם 2 אפשרויות 1⁄2: מעברי CSS עבור מעברים פשוטים מ-A ל-B, אנימציות CSS אנימציות מבוססות זמן שעשויות להיות מחזוריות ומורכבות יותר ו-Web Animations API (WAAPI) לאנימציות מורכבות באופן כמעט שרירותי. מטריצת התמיכה של WAAPI נראית די קשה, אבל הוא בדרך למעלה. עד אז, יש polyfill.
המשותף לכל השיטות האלה הוא שהן חסרות שמירת מצב מבוסס-זמן. אבל חלק מהאפקטים שמפתחים מנסים מבוסס-זמן או ללא שמירת מצב. לדוגמה, תג הגלילה הנודע לשמצה של פרלקס, מרמז, מבוסס-גלילה. באופן מפתיע, קשה להטמיע היום גליל פרלקס בעל ביצועים טובים באינטרנט.
ומה לגבי חוסר שמירת מצב? סרגל הכתובות של Chrome ב-Android, למשל לדוגמה. אם גוללים למטה, הוא נגלל מחוץ לתצוגה. אבל ברגע שגוללים למעלה, הוא חוזר, גם אם עברתם חצי מהדרך למטה בדף הזה. האנימציה תלויה לא רק במיקום הגלילה, אלא גם כיוון הגלילה הקודם שלך. היא מלאה.
בעיה נוספת היא עיצוב של פסי גלילה. חשוב לציין שהם לא סגנוניים – או שהסגנון לא מספיק מסוגנן. מה אם אני רוצה חתול ניאן בתור סרגל גלילה? לא משנה באיזו שיטה בוחרים, יצירה של סרגל גלילה מותאם אישית אינה ביצועים טובים או קלים.
הנקודה היא שכל הדברים האלה הם מביכים וקשה עד בלתי אפשרי
להטמיע אותם ביעילות. רובם מסתמכים על אירועים ו/או
requestAnimationFrame
, שעשוי לשמור על מהירות של 60fps, גם כשהמסך
יכול לפעול במהירות של 90fps, 120fps או יותר, תוך שימוש בחלק
תקציב יקר של מסגרת ה-thread הראשי.
worklet של האנימציה מרחיב את היכולות של מחסנית האנימציות באינטרנט של אפקטים כאלה. לפני שנתחיל, כדאי לוודא שאנחנו להתעדכן לגבי היסודות של אנימציות.
מבוא לאנימציות ולוחות זמנים
WAAPI ו-אנימציה Worklet עושים שימוש נרחב בצירי זמן כדי לאפשר לכם תזמרו את האנימציות והאפקטים בדרך שאתם רוצים. בקטע הזה רענון מהיר או מבוא לצירי זמן והאופן שבו הם פועלים עם אנימציות.
בכל מסמך יש document.timeline
. מתחיל ב-0 כשהמסמך
נוצר וסופר את אלפיות השנייה שחלפו מאז שהמסמך התחיל לפעול. כל החישובים האלה
האנימציות של מסמך פועלות ביחס לציר הזמן הזה.
כדי להבין קצת יותר, בואו נבחן את קטע הקוד של WAAPI
const animation = new Animation(
new KeyframeEffect(
document.querySelector('#a'),
[
{
transform: 'translateX(0)',
},
{
transform: 'translateX(500px)',
},
{
transform: 'translateY(500px)',
},
],
{
delay: 3000,
duration: 2000,
iterations: 3,
}
),
document.timeline
);
animation.play();
כשקוראים לפונקציה animation.play()
, האנימציה משתמשת ב-currentTime
של ציר הזמן
כשעת ההתחלה. באנימציה שלנו יש עיכוב של 3,000 אלפיות השנייה,
האנימציה תתחיל (או תהפוך ל'פעילה') כשציר הזמן יגיע ל-'startTime'
- 3000
. After that time, the animation engine will animate the given element from the first keyframe (
TranslateX(0)), through all intermediate keyframes (
TranslateX(500px)) all the way to the last keyframe (
TranslateY(500px)) in exactly 2000ms, as prescribed by the
משך זמןoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline's
זמן נוכחיis
startTime + 3000 + 1000and the last keyframe at
startTime + 3000 + 2000`. הנקודה היא כדי לקבוע איפה אנחנו נמצאים באנימציה שלנו!
כשהאנימציה תגיע לתמונת המפתח האחרונה, היא תחזור לתמונה הראשונה
תמונת מפתח ולהתחיל את האיטרציה הבאה של האנימציה. התהליך הזה חוזר על עצמו
3 פעמים בסך הכול מאז שהגדרנו את iterations: 3
. אם רצינו שהאנימציה
אף פעם לא נפסיק, היינו כותבים iterations: Number.POSITIVE_INFINITY
. הנה
תוצאה של הקוד
למעלה.
WAAPI הוא מאוד חזק ויש עוד הרבה תכונות נוספות כמו התאמה לצפייה, האזנות התחלה, משקלים של תמונות מפתח והתנהגות מילוי שעלולים לפוצץ את היקף המאמר הזה. רוצה לקבל מידע נוסף? כדאי לקרוא את המאמר הזה בנושא אנימציות של CSS על טריקים ב-CSS.
כתיבת worklet של אנימציה
עכשיו, אחרי שהגדרנו את לוחות הזמנים, אפשר להתחיל worklet של אנימציה ואיך הוא מאפשר להתעסק עם צירי זמן! האנימציה API של worklet לא מבוסס רק על WAAPI, אלא הוא – במובן של האינטרנט הניתן להרחבה – רכיב ברמה נמוכה יותר מסביר את אופן הפעולה של WAAPI. מבחינת התחביר, הם דומים להפליא:
worklet של אנימציה | WAAPI |
---|---|
new WorkletAnimation( 'passthrough', new KeyframeEffect( document.querySelector('#a'), [ { transform: 'translateX(0)' }, { transform: 'translateX(500px)' } ], { duration: 2000, iterations: Number.POSITIVE_INFINITY } ), document.timeline ).play(); |
new Animation( new KeyframeEffect( document.querySelector('#a'), [ { transform: 'translateX(0)' }, { transform: 'translateX(500px)' } ], { duration: 2000, iterations: Number.POSITIVE_INFINITY } ), document.timeline ).play(); |
ההבדל הוא בפרמטר הראשון, שהוא שם ה-worklet שמפעיל את האנימציה הזו.
זיהוי תכונות
Chrome הוא הדפדפן הראשון ששולח את התכונה הזו, לכן עליך לוודא
קוד לא מצפה שAnimationWorklet
יהיה שם. לכן לפני שטוענים את
worklet, אנחנו צריכים לזהות אם הדפדפן של המשתמש תומך
AnimationWorklet
באמצעות בדיקה פשוטה:
if ('animationWorklet' in CSS) {
// AnimationWorklet is supported!
}
טעינת worklet
worklets הם קונספט חדש שהוציא צוות המשימה Houdini כדי להפוך קל יותר לפתח את ממשקי ה-API החדשים ולהתאים אותם לעומס. נסביר על הפרטים של סביבת העבודה בהמשך, אבל לשם פשטות, אפשר לחשוב עליהם כעל זול בינתיים, שרשורים פשוטים (כמו עובדים).
אנחנו צריכים לוודא שטענו worklet בשם 'passthrough', לפני שמצהירים על האנימציה:
// index.html
await CSS.animationWorklet.addModule('passthrough-aw.js');
// ... WorkletAnimation initialization from above ...
// passthrough-aw.js
registerAnimator(
'passthrough',
class {
animate(currentTime, effect) {
effect.localTime = currentTime;
}
}
);
מה קורה פה? אנחנו רושמים שיעור כאנימטורים באמצעות
הקריאה registerAnimator()
של AnimationWorklet, נותנת לה את השם "passthrough".
זה אותו השם שבו השתמשנו ב-constructor של WorkletAnimation()
שלמעלה. אחרי ש
ההרשמה הושלמה, ההבטחה שהוחזרה על ידי addModule()
תטופל ו
אנחנו יכולים להתחיל ליצור אנימציות באמצעות ה-worklet הזה.
ה-method animate()
של המכונה שלנו תופעל לכל פריים
הדפדפן רוצה לבצע רינדור, להעביר את currentTime
בציר הזמן של האנימציה
וגם את האפקט שנמצא עכשיו בתהליך עיבוד. יש לנו רק
KeyframeEffect
ואנחנו משתמשים ב-currentTime
כדי להגדיר את האפקט
localTime
, ולכן האנימטור הזה נקרא 'החלפה'. עם הקוד הזה כדי
ה-worklet, ה-WAAPI וה-AnimationWorklet שלמעלה פועלים בדיוק
כמו שאפשר לראות
demo.
שעה
הפרמטר currentTime
של ה-method animate()
הוא currentTime
של
ציר הזמן שהעברנו ל-constructor של WorkletAnimation()
. במשפט הקודם
פשוט העברנו את הזמן הזה למצב פעיל. אבל מכיוון שזו
הקוד JavaScript, ואנחנו יכולים לעוות את הזמן 💫
function remap(minIn, maxIn, minOut, maxOut, v) {
return ((v - minIn) / (maxIn - minIn)) * (maxOut - minOut) + minOut;
}
registerAnimator(
'sin',
class {
animate(currentTime, effect) {
effect.localTime = remap(
-1,
1,
0,
2000,
Math.sin((currentTime * 2 * Math.PI) / 2000)
);
}
}
);
אנחנו לוקחים את Math.sin()
של currentTime
, וממפים מחדש את הערך הזה
הטווח [0; 2000], שהוא טווח הזמן שעבורו מוגדרת ההשפעה שלנו. היום
האנימציה נראית שונה מאוד, בלי
שינתה את תמונות המפתח או את אפשרויות האנימציה. קוד ה-worklet יכול להיות
הם מורכבים באופן שרירותי, ומאפשרים להגדיר באופן פרוגרמטי אילו אפקטים
ובאיזה סדר ובאיזה סדר.
אפשרויות מעל 'אפשרויות'
כדאי להשתמש שוב ב-worklet ולשנות את המספרים שלו. לכן constructor של workletAnimationcom מאפשר להעביר אובייקט אפשרויות ל-worklet:
registerAnimator(
'factor',
class {
constructor(options = {}) {
this.factor = options.factor || 1;
}
animate(currentTime, effect) {
effect.localTime = currentTime * this.factor;
}
}
);
new WorkletAnimation(
'factor',
new KeyframeEffect(
document.querySelector('#b'),
[
/* ... same keyframes as before ... */
],
{
duration: 2000,
iterations: Number.POSITIVE_INFINITY,
}
),
document.timeline,
{factor: 0.5}
).play();
בדוגמה הזאת, שתי האנימציות מונעות באמצעות אותו קוד, אבל עם אפשרויות שונות.
אני רוצה לקבל את המדינה המקומית שלך!
כפי שרמזתי קודם, אחת הבעיות העיקריות באנימציה שמטרתה לפתור היא
ואנימציות. ניתן לשמור על המצבים של רכיבי אנימציה לעבודה. אבל, אחת
אחת מהתכונות העיקריות של worklets היא שאפשר להעביר אותם
או אפילו יושמד כדי לחסוך משאבים, דבר שגם ישמיד את
. כדי למנוע אובדן מצב, worklet של האנימציה מציע הוק (hook)
נקראת לפני השמדת worklet, שאפשר להשתמש בו כדי להחזיר מצב
לאובייקט. האובייקט הזה יועבר ל-constructor כשה-worklet
נוצרה מחדש. ביצירה הראשונית, הפרמטר יהיה undefined
.
registerAnimator(
'randomspin',
class {
constructor(options = {}, state = {}) {
this.direction = state.direction || (Math.random() > 0.5 ? 1 : -1);
}
animate(currentTime, effect) {
// Some math to make sure that `localTime` is always > 0.
effect.localTime = 2000 + this.direction * (currentTime % 2000);
}
destroy() {
return {
direction: this.direction,
};
}
}
);
בכל פעם שמרעננים את ההדגמה הזו, מקבלים 50/50
יש סיכוי באיזה כיוון הריבוע יסתובב. אם הדפדפן היה מתנתק
את ה-worklet ולהעביר אותו לשרשור אחר,
קריאה אחת (Math.random()
) ליצירה, שעלולה לגרום לשינוי פתאומי של
לכיוון מסוים. כדי לוודא שזה לא יקרה, אנחנו מחזירים את האנימציות.
נבחר באופן אקראי כ-state ולהשתמש בו ב-constructor, אם צוין.
התנתקות אל רצף הזמן-מרחב: ScrollTimeline
כפי שראינו בקטע הקודם, AnimationWorklet מאפשר לנו
להגדיר באופן פרוגרמטי איך ההתקדמות בציר הזמן משפיעה על ההשפעות של
אנימציה. אבל עד עכשיו, ציר הזמן שלנו היה document.timeline
,
עוקב אחר הזמן.
ScrollTimeline
פותח אפשרויות חדשות ומאפשר לך ליצור אנימציות
באמצעות גלילה במקום זמן. נשתמש שוב במודל
'מעבר בין מכשירים' worklet של זה
demo:
new WorkletAnimation(
'passthrough',
new KeyframeEffect(
document.querySelector('#a'),
[
{
transform: 'translateX(0)',
},
{
transform: 'translateX(500px)',
},
],
{
duration: 2000,
fill: 'both',
}
),
new ScrollTimeline({
scrollSource: document.querySelector('main'),
orientation: 'vertical', // "horizontal" or "vertical".
timeRange: 2000,
})
).play();
במקום להעביר document.timeline
, אנחנו יוצרים ScrollTimeline
חדש.
בטח ניחשת, ScrollTimeline
לא משתמש בזמן אבל
מיקום הגלילה של scrollSource
כדי להגדיר את currentTime
ב-worklet. להיות
אם גוללים עד הסוף עד הקצה (או שמאלה), המשמעות היא currentTime = 0
, ולעומת זאת
אם המשתמש גולל כל הדרך לתחתית המסך (או ימינה), currentTime
מוגדר
timeRange
. אם גוללים את התיבה
demo, אפשר
קובעים את מיקום התיבה האדומה.
אם יוצרים ScrollTimeline
עם רכיב שלא נגלל,
currentTime
בציר הזמן יהיה NaN
. במיוחד עם עיצוב רספונסיבי,
חשוב לזכור, עליך להיות מוכן תמיד לNaN
בתור currentTime
. לעיתים קרובות
הגיוני להגדיר ערך ברירת מחדל של 0.
קישור אנימציות באמצעות מיקום גלילה הוא משהו שחיו כבר זמן רב, אבל מעולם לא הושג ברמת דיוק כזו (מלבד לעקוף את הבעיה באמצעות CSS3D). worklet של האנימציה מאפשר לאפקטים האלה מיושמת בצורה פשוטה ויש לה ביצועים גבוהים. מוצרים לדוגמה: אפקט של גלילה כמו פרלקס demo מראה עכשיו נדרשות רק כמה שורות כדי להגדיר אנימציה שמבוססת על גלילה.
מה צריך לעשות
worklet
זמני עבודה (worklet) הם הקשרים של JavaScript עם היקף מבודד ו-API קטן מאוד פלטפורמה. פלטפורמת ה-API הקטנה מאפשרת אופטימיזציה אגרסיבית יותר במיוחד במכשירים פשוטים. בנוסף, worklets לא קשורים אירוע ספציפי בלופ, אבל אפשר לעבור בין שרשורים לפי הצורך. הדבר שחשוב במיוחד ל-AnimationWorklet.
מחבר NSync
אתם עשויים לדעת שמאפייני CSS מסוימים מאפשרים אנימציה מהירה, בעוד שמאפייני CSS אחרים לא. בנכסים מסוימים נדרשת רק עבודה על ה-GPU כדי להפעיל אנימציה, ויש נכסים אחרים לאלץ את הדפדפן לפריסה מחדש של המסמך כולו.
ב-Chrome (כמו בדפדפנים אחרים רבים אחרים) יש לנו תהליך שנקרא 'המחבר', שמדובר בתפקיד של תפקידו — וכאן הרבה יותר פשוט לפשט אותו — לארגן שכבות ולאחר מכן להשתמש ב-GPU כדי לעדכן את המסך באופן קבוע ככל האפשר, מהירות אידיאלית לעדכון המסך (בדרך כלל 60Hz). בהתאם לסוג מופיעה אנימציה של מאפייני CSS, יכול להיות שבדפדפן צריך רק שאחר כך פועל, ואילו נכסים אחרים צריכים להריץ פריסה, פעולה שרק ה-thread הראשי יכול לבצע. בהתאם לנכסים מתכננים להוסיף אנימציה, worklet של האנימציה יהיה מקושר או להריץ בשרשור נפרד בסנכרון עם המחבר.
חבטה על פרק כף היד
בדרך כלל יש רק תהליך קומפוזיציה אחד שניתן לשתף בין כמה כרטיסיות, כי ה-GPU הוא משאב עמוס מאוד. אם המחבר מקבל נחסם בדרך כלשהי, הדפדפן כולו מתנתק ומפסיק להגיב קלט של משתמשים. יש להימנע מכך בכל מחיר. אז מה קורה אם worklet לא יכול לספק את הנתונים שהמחבר צריך בזמן כדי שהמסגרת מעובד?
במקרה כזה, ה-worklet מותר - לפי המפרט - "slip". הוא מאחור המחבר, ורכיב הקומפוזיציה מורשה להשתמש שוב בנתוני הפריים האחרון כדי לשמור על קצב פריימים גבוה. מבחינה חזותית, זה ייראה כמו ג'נק, אבל ההבדל הוא שהדפדפן עדיין מגיב לקלט של משתמשים.
סיכום
ל-AnimationWorklet יש היבטים רבים והיתרונות שהיא מספקת באינטרנט. היתרונות הבולטים הם יותר שליטה באנימציות ודרכים חדשות בנהיגה אנימציות כדי להוסיף לאינטרנט רמה חדשה של איכות חזותית. אבל ממשקי ה-API מאפשר גם להפוך את האפליקציה לעמידה יותר בפני בעיות ליהנות מכל היתרונות החדשים בו-זמנית.
worklet של האנימציה נמצא ב-Canary, ואנחנו שואפים ליצור גרסת מקור לניסיון עם Chrome 71. אנחנו מחכים בקוצר רוח לחוויות השימוש החדשות שלך באינטרנט ולהאזנה לגבי מה שאנחנו יכולים לשפר. יש גם polyfill שנותן לכם את אותו API, אבל לא מספק מידע מבודד על הביצועים.
חשוב לזכור שמעברי CSS ואנימציות של CSS עדיין בתוקף והן יכולות להיות הרבה יותר פשוטות לאנימציות בסיסיות. אבל אם צריך ללכת יפה מאוד, ל-AnimationWorklet יש את מאבטח!