JavaScript 是否提供高分辨率计时器?

IT技术 javascript animation
2021-02-21 16:13:50

JavaScript 是否提供高分辨率计时器?

我从头开始编写了一些游戏引擎,有些是用 C 语言编写的,有些是用 Java 编写的,有些是用 Flash 编写的。在动画和交互式图形方面,我始终遵循相同的基本模型。使用以下设计创建一个基本类/结构:

void init() { /* Called once, preload essential resources here. */ }
void update(double time) { /* Updates game/animation state using high resolution time. */ }
void render(double time) { /* Updates screen graphics using high resolution time. */ }

void run()
{
    double time;
    init();
    while (!done)
    {
        time = queryTime();
        update(time);
        render(time);
    }
}

时间对于平滑动画和游戏状态计算非常重要。在本机代码 Windows 中,我使用QueryPerformanceCounter()QueryPerformanceFrequency()来执行queryTime()每个游戏循环中的角色,并通过时间来更新/渲染。在 Java 中,我使用System.nanoTime().

JavaScript 中的等价物是什么?也就是说,一些函数像queryTime()它以高精度(亚毫秒)返回时间值。据我所知,您在 JavaScript 中可以获得的最佳准确度约为 15 毫秒……这对于动画来说太可怕了。

6个回答

几乎所有现代浏览器都提供高分辨率计时器。这是“高分辨率时间”W3C 标准:http : //www.w3.org/TR/hr-time/#sec-DOMHighResTimeStamp

它允许您通过调用window.performance.now()获得亚毫秒级的准确时间戳这将返回一个以毫秒为单位的时间戳,但它是一个浮点数,因此您仍然可以获得亚毫秒级的分辨率。

非常旧的浏览器可能会实现该标准的“带前缀”版本,例如用于实现window.performance.webkitNow() 的基于 WebKit 的浏览器

下面是一些代码,您可以使用它在可用时获取准确的时间戳,否则回退到标准精度:

if (window.performance.now) {
    console.log("Using high performance timer");
    getTimestamp = function() { return window.performance.now(); };
} else {
    if (window.performance.webkitNow) {
        console.log("Using webkit high performance timer");
        getTimestamp = function() { return window.performance.webkitNow(); };
    } else {
        console.log("Using low performance timer");
        getTimestamp = function() { return new Date().getTime(); };
    }
}

getTimestamp();

请注意,此getTimestamp()函数不会返回表示当前日期/时间的值。通过减去两个不同的时间戳,返回值只能用于测量时间段。例如

var t1 = getTimestamp();
//... some other code
var t2 = getTimestamp();
console.log("Time delta: " + (t2 - t1));
'看起来 Chromeperformance.now()现在具有微秒分辨率。
2021-04-16 16:13:50
请注意,由于 Spectre 和其他威胁,该值实际上并不是高分辨率的。例如,Firefox 舍入到毫秒。.
2021-04-23 16:13:50
截至 2014 年 3 月,Chromewindow.performance.now()仍仅提供毫秒级分辨率。见奥列格的回答。
2021-05-11 16:13:50

请参阅@h3r3 的答案——这是正确的答案。

毫秒是 JavaScript 中最好的。而且,就像你说的,它不是很准确。请参阅 Stack Overflow 问题JavaScript 中的微秒计时

timer.js声称提供高达微秒的分辨率,但它仅适用于Google Chrome

更新:timer.js并没有支持微秒分辨率。它只是将毫秒计数乘以 1000。

抱歉,没有更好的消息了!

很高兴知道 timer.js 至少做了这么多,乘以 1000 是非常困难的。
2021-04-23 16:13:50
据我所知,Flash 也没有亚毫秒级计时。我认为没有人期望与本机应用程序具有同等的多媒体性能,而是朝着正确方向迈出的一步。
2021-04-24 16:13:50
好的,跟进。如果浏览器不支持精确计时,推广 html+javascript+canvas 的人们如何期望开发人员使用 html+javascript+canvas 为浏览器创建漂亮的动画或实时交互图形?动画应该基于时间而不是帧数。基于帧数的动画会导致它们在不同的硬件/系统配置上以不同的速度运行。它也没有那么顺利。随着时间的推移,您可以插入增量。
2021-05-06 16:13:50
看起来我们在 2017 年越来越近了:developer.mozilla.org/en-US/docs/Web/API/Performance/now ...授予高分辨率时间 API 为一些复杂的缓存攻击打开了可能性,所以它是排序的福与祸:cs.columbia.edu/~simha/spyjs.ccs15.pdf
2021-05-15 16:13:50

使用递归代替while (true)/ 它将比基于超时的动画运行更流畅。如果您需要动画以较慢的路径运行,它会提供时间戳setIntervalrequestAnimationFrame

好的,谢谢你的信息。我的代码是伪代码,用于解释我通常如何设计游戏的结构。这足以获得您的好评。谢谢。
2021-04-27 16:13:50

截至目前(2013 年 2 月 25 日),Chrome 24 中高性能时代的质量非常糟糕。

var old = 0; 

for (var i=0; i<10; i++) {
    var t = window.performance.now(); 
    var d = t - old; 
    old = t; 
    console.log(t + ' += ' +d); 
}

输出是这样的:

609689.000000013 += 609689.000000013
609689.9999999441 += 0.9999999310821295
609689.9999999441 += 0
609689.9999999441 += 0
609689.9999999441 += 0
609690.9999999916 += 1.0000000474974513
609690.9999999916 += 0
609690.9999999916 += 0
609691.9999999227 += 0.9999999310821295
609691.9999999227 += 0

这表明

1) 抽样很少发生

2)精度还在1ms左右,不在微秒范围内。

解决了吗?2014 年 10 月 21 日,OS X 上的 Chrome 38 对我来说工作正常(平均打印 += 0.2)。
2021-04-26 16:13:50
2013 年 12 月 16 日,Chrome 31 - 仍然是同样的情况,performance.now()只是返回\d{6}.(999999999|000000000)\d{2}
2021-04-28 16:13:50
Chrome 的错误报告在这里:code.google.com/p/chromium/issues/detail?id=158234
2021-04-30 16:13:50

这是您的代码在 JavaScript 中的样子,因此它不会阻塞用户界面,也不会使用不能跨浏览器工作的window.requestAnimationFrame

/* Called once, preload essential resources here. */
function init() {}

/* Updates game/animation state */
function update(time) {}

/* Updates screen graphics  */
function render(time) {}

window.onload = function()
{
    var time;
    var done = false;

    init();
    // Using setTimeout passing zero makes the animate function run
    // as soon as possible, but yielding first.
    setTimeout(animate, 0);

    function animate () {
        time = new Date();
        update(time);
        render(time);
        if (!done) {
            setTimeout(animate, 0);
        }
    }
}

这种方法的一个问题是,animate()可能会比屏幕更新更频繁地调用 s(在 60 Hz 下,它不会比每 16 毫秒更新一次 abot 更频繁),从而导致额外的渲染永远不会出现在屏幕上。这就是为什么您应该尽可能坚持使用 requestAnimationFrame 的原因。

顺便说一句,当您传递0to的延迟时setTimeout,实际上不是零延迟。例如,在 Firefox 5 中是 4 毫秒。dom.min_timeout_valueabout:config设置中查看
2021-04-17 16:13:50
@Domi 任何接触 DOM 的代码都会阻塞 UI。OP 建议使用繁忙循环,它会完全阻塞 UI,并且永远不会将控制权交还给更新屏幕的 UI 线程。每次调用 时setTimeout,都会给 UI 线程一个更新屏幕的机会,这是动画的重点。Web Workers 无权访问 DOM,因此您必须使用消息来更新 DOM。
2021-04-17 16:13:50
警告:setTimeout仍会阻止您的 UI。JS 上下文是单线程的,除非您明确启动web worker
2021-04-23 16:13:50
@katspaugh:我在回答中很明显地表明零意味着尽快。关于 about:config 设置的好信息。
2021-05-07 16:13:50