requestAnimationFrame API - עכשיו עם דיוק של תת-אלפית שנייה

Ilmari Heikkinen

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

עם זאת, בקרוב יתבצע שינוי בחלק מ-API. חותמת הזמן שמועברת לפונקציית ה-callback משתנה מחותמת זמן רגילה כמו Date.now() למדידה ברזולוציה גבוהה של אלפיות השנייה בנקודת צפה מאז פתיחת הדף. אם משתמשים בערך הזה, צריך לעדכן את הקוד בהתאם להסבר שבהמשך.

רק כדי להבהיר, זה מה שאני מתכוון אליו:

// assuming requestAnimationFrame method has been normalized for all vendor prefixes..
requestAnimationFrame(function(timestamp){
    // the value of timestamp is changing
});

אם אתם משתמשים ב-requestAnimFrame shim הש ניתן כאן, סימן שאתם לא משתמשים בערך חותמת הזמן. אין לך מה לדאוג. :)

סיבה

למה? ובכן, rAF עוזר לכם לקבל את ה-60fps האולטימטיבי, ו-60fps מתורגמת ל-16.7ms לכל פריים. עם זאת, מדידה במילישניות שלמות מאפשרת לנו לקבל דיוק של 1/16 בכל מה שאנחנו רוצים לצפות בו ולטרגט.

השוואה בין תרשים של 16 אלפיות שנייה לבין תרשים של 16 אלפיות שנייה שלמים.

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

הטיימר ברזולוציה גבוהה פותר את הבעיה הזו על ידי מתן נתון מדויק הרבה יותר:

Date.now()         //  1337376068250
performance.now()  //  20303.427000007

הטיימר לרזולוציה גבוהה זמין כרגע ב-Chrome בתור window.performance.webkitNow(), והערך הזה בדרך כלל שווה לערך הארגומנט החדש שמועבר בקריאה החוזרת (callback) של rAF. ככל שהמפרט יתקדם בתהליך הסטנדרטיזציה, השיטה תפסיק לכלול את הקידומת ותהיה זמינה דרך performance.now().

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

בשימוש

הבעיה העיקרית היא ספריות אנימציה שמשתמשות בתבנית העיצוב הזו:

function MyAnimation(duration) {
    this.startTime = Date.now();
    this.duration = duration;
    requestAnimFrame(this.tick.bind(this));
}
MyAnimation.prototype.tick = function(time) {
    var now = Date.now();
    if (time > now) {
        this.dispatchEvent("ended");
        return;
    }
    ...
    requestAnimFrame(this.tick.bind(this));
}

אפשר לערוך בקלות את הבעיה הזו... אפשר להרחיב את startTime ואת now כדי להשתמש ב-window.performance.now().

this.startTime = window.performance.now ?
                    (performance.now() + performance.timing.navigationStart) :
                    Date.now();

זוהי הטמעה תמימה למדי, היא לא משתמשת בשיטה now() עם קידומת, וגם מתייחסת לתמיכה ב-Date.now(), שאינה קיימת ב-IE8.

זיהוי תכונות

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

requestAnimationFrame(function(timestamp){

    if (timestamp < 1e12){
        // .. high resolution timer
    } else {
        // integer milliseconds since unix epoch
    }

    // ...

בדיקת if (timestamp < 1e12) היא בדיקה מהירה כדי לראות עם איזה מספר גדול אנחנו מתמודדים. מבחינה טכנית, יכול להיות דיווח חיובי שגוי, אבל רק אם דף אינטרנט פתוח ברציפות במשך 30 שנה. אבל אנחנו לא יכולים לבדוק אם זהו מספר נקודה צפה (ולא מספר שלם שהוגדר כמספר שלם שלם). אם תבקשו מספיק שעונים ברזולוציה גבוהה, תקבלו ערכים שלמים בשלב מסוים.

אנחנו מתכננים להטמיע את השינוי הזה ב-Chrome 21, כך שאם אתם כבר משתמשים בפרמטר ה-callback הזה, חשוב לעדכן את הקוד שלכם.