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

אילמרי הייקינן

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

עם זאת, עומד להיות שינוי בחלק מה-API. חותמת הזמן שמועברת לפונקציית הקריאה החוזרת משתנה מחותמת זמן אופיינית דמוית 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.7 אלפיות השנייה לכל פריים. אבל מדידה עם אלפיות שנייה במספרים שלמות פירושה שיש לנו דיוק של 1/16 לכל מה שאנחנו רוצים לתעד ולמקד.

השוואת תרשים של 16 אלפיות השנייה לעומת 16 אלפיות השנייה של מספר שלם.

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

טיימר ברזולוציה גבוהה פותר את הבעיה בכך שהוא מספק דמות מדויקת הרבה יותר:

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

הטיימר ברזולוציה הגבוהה זמין כרגע ב-Chrome בתור window.performance.webkitNow(), והערך הזה בדרך כלל שווה לערך הארגומנט החדש שהועבר לקריאה החוזרת של ה-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.

זיהוי תכונות

אם אתם לא משתמשים בדפוס שלמעלה ואתם רוצים לזהות את סוג הערך של הקריאה החוזרת (callback), תוכלו להשתמש בשיטה הזו:

requestAnimationFrame(function(timestamp){

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

    // ...

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

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