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

ทำไม

เหตุผล 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 ดังนั้น หากคุณใช้ประโยชน์จากพารามิเตอร์ Callback นี้แล้ว โปรดอัปเดตโค้ด