API requestAnimationFrame – nay có độ chính xác chưa đến mili giây

Ilmari Heikkinen

Nếu từng sử dụng requestAnimationFrame, bạn sẽ thấy thú vị khi thấy màu vẽ được đồng bộ hoá với tốc độ làm mới màn hình, nhờ đó mang lại các ảnh động có độ trung thực cao nhất có thể. Ngoài ra, bạn còn giúp người dùng tiết kiệm được tiếng ồn của quạt CPU và tiết kiệm pin khi họ chuyển sang một thẻ khác.

Tuy nhiên, sắp có một thay đổi đối với một phần của API. Dấu thời gian được truyền vào hàm gọi lại sẽ thay đổi từ dấu thời gian giống như Date.now() thông thường thành phép đo có độ phân giải cao mili giây dấu phẩy động kể từ khi trang được mở. Nếu sử dụng giá trị này, bạn sẽ cần phải cập nhật mã dựa trên nội dung giải thích bên dưới.

Xin nói rõ, đây là những gì tôi muốn nói đến:

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

Nếu bạn đang dùng vòng đệm requestAnimFrame phổ biến được cung cấp tại đây, thì tức là bạn không sử dụng giá trị dấu thời gian. Bạn không cần làm gì cả. :)

Tại sao nên

Tại sao? rAF tốt giúp bạn có được 60 khung hình/giây tối ưu, lý tưởng và 60 khung hình/giây chuyển thành 16,7 mili giây mỗi khung hình. Tuy nhiên, việc đo lường bằng số nguyên mili giây có nghĩa là chúng ta có độ chính xác là 1/16 cho mọi thứ mà chúng ta muốn quan sát và nhắm mục tiêu.

So sánh đồ thị 16 mili giây so với 16 mili giây số nguyên.

Như bạn có thể thấy ở trên, thanh màu xanh dương biểu thị khoảng thời gian tối đa bạn có để thực hiện tất cả công việc trước khi vẽ một khung mới (ở tốc độ 60 khung hình/giây). Có thể bạn sẽ làm hơn 16 việc, nhưng với số nguyên mili giây, bạn chỉ có thể lên lịch và đo lường theo những lần tăng rất nhỏ đó. Như vậy chưa đủ.

Bộ tính giờ có độ phân giải cao giải quyết vấn đề này bằng cách đưa ra một con số chính xác hơn:

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

Bộ tính giờ có độ phân giải cao hiện được cung cấp trong Chrome dưới dạng window.performance.webkitNow() và giá trị này thường bằng với giá trị đối số mới được truyền vào lệnh gọi lại rAF. Sau khi quy cách tiếp tục qua các tiêu chuẩn, phương thức này sẽ bỏ tiền tố và có thể sử dụng thông qua performance.now().

Bạn cũng sẽ nhận thấy hai giá trị trên là nhiều thứ tự khác nhau về độ lớn. performance.now() là số đo mili giây dấu phẩy động kể từ khi trang cụ thể đó bắt đầu tải (performance.navigationStart cụ thể).

Đang sử dụng

Vấn đề chính bị cắt là thư viện ảnh động sử dụng mẫu thiết kế này:

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));
}

Việc chỉnh sửa để khắc phục vấn đề này khá dễ dàng... tăng cường startTimenow để sử dụng window.performance.now().

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

Đây là một cách triển khai khá đơn thuần, không sử dụng phương thức now() có tiền tố và cũng giả định khả năng hỗ trợ Date.now() không có trong IE8.

Phát hiện tính năng

Nếu bạn không sử dụng mẫu ở trên và chỉ muốn xác định loại giá trị gọi lại mà bạn đang nhận được, bạn có thể sử dụng kỹ thuật này:

requestAnimationFrame(function(timestamp){

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

    // ...

Việc kiểm tra if (timestamp < 1e12) là một kiểm thử nhanh để xem số lượng mà chúng ta đang xử lý. Về mặt kỹ thuật, mã đó có thể bị nhầm lẫn nhưng chỉ khi một trang web mở liên tục trong 30 năm. Tuy nhiên, chúng tôi không thể kiểm tra xem đó có phải là số thực hay không (thay vì dùng số sàn thành số nguyên). Hãy yêu cầu cung cấp đủ đồng hồ hẹn giờ có độ phân giải cao và bạn sẽ nhận được giá trị số nguyên vào lúc nào đó.

Chúng tôi dự định áp dụng thay đổi này trong Chrome 21. Vì vậy, nếu bạn đã tận dụng thông số gọi lại này, hãy nhớ cập nhật mã của bạn!