requestAnimationFrame API - มีความแม่นยำระดับวินาทีย่อย

Ilmari Heikkinen

หากใช้ 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 ทั่วไปที่ให้ไว้ที่นี่ แสดงว่าคุณไม่ได้ใช้ค่าการประทับเวลา คุณมาถูกทางแล้ว :)

ทำไมจึงควร

เหตุผล อัตรา rAF จะช่วยให้คุณได้ 60 fps ที่ดีที่สุด และ 60 fps แปลเป็น 16.7 มิลลิวินาทีต่อเฟรม แต่การวัดด้วยจำนวนเต็มมิลลิวินาทีหมายความว่าเรามีความแม่นยำเป็น 1/16 สำหรับทุกสิ่งที่เราต้องการสังเกตการณ์และกำหนดเป้าหมาย

การเปรียบเทียบกราฟจำนวนเต็ม 16 มิลลิวินาทีกับจำนวนเต็ม 16 มิลลิวินาที

จากที่เห็นด้านบน แถบสีน้ำเงินคือระยะเวลาสูงสุดที่คุณต้องทำงานทั้งหมดก่อนที่จะวาดเฟรมใหม่ (ที่ 60 FPS) คุณอาจกำลังทำสิ่งต่างๆ มากกว่า 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

การตรวจหาฟีเจอร์

หากคุณไม่ได้ใช้รูปแบบข้างต้นและต้องการระบุเฉพาะค่าโค้ดเรียกกลับที่คุณได้รับ คุณสามารถใช้เทคนิคนี้

requestAnimationFrame(function(timestamp){

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

    // ...

การตรวจสอบ if (timestamp < 1e12) เป็นการทดสอบสั้นๆ เพื่อดูว่าเรากำลังรับมือกับตัวเลขจำนวนมากแค่ไหน ในทางเทคนิค อาจเกิดข้อผิดพลาดขึ้นเมื่อหน้าเว็บเปิดต่อเนื่องเป็นเวลา 30 ปีเท่านั้น แต่เราไม่สามารถทดสอบได้ว่าตัวเลขดังกล่าวเป็นเลขทศนิยมหรือไม่ (แทนที่จะกำหนดเป็นจำนวนเต็ม) ขอให้ส่งตัวจับเวลาที่มีความละเอียดสูงเพียงพอ จากนั้นคุณจะได้ค่าจำนวนเต็มในบางเวลา

เราวางแผนที่จะนำการเปลี่ยนแปลงนี้ใน Chrome 21 ไปใช้ ดังนั้นหากคุณใช้ประโยชน์จากพารามิเตอร์โค้ดเรียกกลับนี้อยู่แล้ว โปรดอัปเดตโค้ดของคุณ