我想实现的是检测屏幕上出现特定更改的精确时间(主要是使用Google Chrome)。例如,我使用$("xelement").show();
显示一个项目,或者使用$("#xelement").text("sth new");
更改它,然后我想看看当performance.now用户屏幕上出现给定屏幕重绘时,www.example.com()到底是什么。所以我对任何解决方案都持开放态度-下面我主要指的是requestAnimationFrame(rAF),因为这是应该帮助实现这一点的函数,只是它似乎没有;见下文。
基本上,正如我想象的那样,rAF应该在大约0-17毫秒内执行它内部的所有内容(每当下一帧出现在我的标准60 Hz屏幕上时)。此外,timestamp参数应该给予执行时间的值(该值基于与www.example.com()相同的DOMHighResTimeStamp度量performance.now)。
下面是我为此做的众多测试之一:https://jsfiddle.net/gasparl/k5nx7zvh/31/
function item_display() {
var before = performance.now();
requestAnimationFrame(function(timest){
var r_start = performance.now();
var r_ts = timest;
console.log("before:", before);
console.log("RAF callback start:", r_start);
console.log("RAF stamp:", r_ts);
console.log("before vs. RAF callback start:", r_start - before);
console.log("before vs. RAF stamp:", r_ts - before);
console.log("")
});
}
setInterval(item_display, Math.floor(Math.random() * (1000 - 500 + 1)) + 500);
我在Chrome中看到的是:rAF内部的函数总是在大约0-3毫秒内执行(从performance.now紧挨着它的www.example.com()开始计算),最奇怪的是,rAF时间戳与我在rAF内部使用performance.now()得到的时间戳完全不同,通常比performance.now()调用 before rAF早大约0-17毫秒(但有时大约晚0-1毫秒)。
下面是一个典型的例子:
before: 409265.00000001397
RAF callback start: 409266.30000001758
RAF stamp: 409260.832
before vs. RAF callback start: 1.30000000353902
before vs. RAF stamp: -4.168000013974961
在Firefox和IE中是不同的。在Firefox中,“before vs.在一些实施例中,“RAF回调开始”的时间大约为1-3 ms或大约为16-17 ms。“之前VS。RAF戳”总是正的,通常在0-3 ms左右,但有时在3-17 ms之间。在IE中,两个差异几乎总是在15-18 ms左右(正)。这些或多或少是不同的PC相同。然而,当我在手机的Chrome上运行它时,只有在那时,它似乎是正确的:“之前与RAF标记”随机地在0-17附近,并且“RAF回调开始”总是在几毫秒之后。
有关更多上下文:这是一个在线响应时间实验,用户使用自己的PC(但我通常限制浏览器为Chrome,所以这是唯一对我来说真正重要的浏览器)。我反复显示各种项目,并测量响应时间为“从元素显示的时刻(当人们看到它时)到他们按下键的时刻”,并从记录的响应时间中计算特定项目的平均值,然后检查某些项目类型之间的差异。这也意味着,如果记录的时间总是在一个方向上有点偏斜,那也没什么关系。总是在元素的实际出现之前3 ms),只要该偏斜对于每个显示器是一致的,因为只有差异才真正重要。1-2毫秒的精度将是理想的,但任何减轻随机“刷新率噪声”(0-17毫秒)将是很好的。
我也尝试了jQuery.show()
回调,但它没有考虑刷新率:https://jsfiddle.net/gasparl/k5nx7zvh/67/
var r_start;
function shown() {
r_start = performance.now();
}
function item_display() {
var before = performance.now();
$("#stim_id").show(complete = shown())
var after = performance.now();
var text = "before: " + before + "<br>callback RT: " + r_start + "<br>after: " + after + "<br>before vs. callback: " + (r_start - before) + "<br>before vs. after: " + (after - r_start)
console.log("")
console.log(text)
$("p").html(text);
setTimeout(function(){ $("#stim_id").hide(); }, 500);
}
setInterval(item_display, Math.floor(Math.random() * (1000 - 500 + 1)) + 800);
使用HTML:
<p><br><br><br><br><br></p>
<span id="stim_id">STIMULUS</span>
解决方案(基于Kaiido的答案)沿着工作显示示例:
function monkeyPatchRequestPostAnimationFrame() {
const channel = new MessageChannel();
const callbacks = [];
let timestamp = 0;
let called = false;
channel.port2.onmessage = e => {
called = false;
const toCall = callbacks.slice();
callbacks.length = 0;
toCall.forEach(fn => {
try {
fn(timestamp);
} catch (e) {}
});
};
window.requestPostAnimationFrame = function(callback) {
if (typeof callback !== 'function') {
throw new TypeError('Argument 1 is not callable');
}
callbacks.push(callback);
if (!called) {
requestAnimationFrame((time) => {
timestamp = time;
channel.port1.postMessage('');
});
called = true;
}
};
}
if (typeof requestPostAnimationFrame !== 'function') {
monkeyPatchRequestPostAnimationFrame();
}
function chromeWorkaroundLoop() {
if (needed) {
requestAnimationFrame(chromeWorkaroundLoop);
}
}
// here is how I display items
// includes a 100 ms "warm-up"
function item_display() {
window.needed = true;
chromeWorkaroundLoop();
setTimeout(function() {
var before = performance.now();
$("#stim_id").text("Random new text: " + Math.round(Math.random()*1000) + ".");
$("#stim_id").show();
// I ask for display above, and get display time below
requestPostAnimationFrame(function() {
var rPAF_now = performance.now();
console.log("before vs. rPAF now:", rPAF_now - before);
console.log("");
needed = false;
});
}, 100);
}
// below is just running example instances of displaying stuff
function example_loop(count) {
$("#stim_id").hide();
setTimeout(function() {
item_display();
if (count > 1) {
example_loop(--count);
}
}, Math.floor(Math.random() * (1000 - 500 + 1)) + 500);
}
example_loop(10);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<div id="stim_id">Any text</div>
**编辑:**因此,基于经验测量,在所有这些中,turns out所有重要的是rAF循环。rPAF没有真实的的区别。
1条答案
按热度按时间xwbd5t1u1#
你正在经历的是一个Chrome bug(甚至是两个)。
基本上,当 requestAnimationFrame 回调池为空时,它们将在当前事件循环结束时直接调用它,而无需等待规范要求的实际绘制帧。
要解决这个bug,你可以保持一个不断循环的 requestAnimationFrame,但要注意这会将你的文档标记为“动画”,并会在页面上触发一系列副作用(比如每次屏幕刷新时强制重绘)。所以我不确定你在做什么,但通常这样做不是一个好主意,我宁愿邀请你只在需要的时候运行这个动画循环。
现在,requestAnimationFrame 回调在下一次绘制之前**触发(实际上在同一个事件循环中),并且TimeStamp参数应该表示当前帧的所有主任务和微任务执行之后的时间,然后才开始其“更新渲染”子任务(这里的步骤9)。
所以它不是最精确的,你是对的,在这个回调函数中使用
performance.now()
应该可以让你更接近实际的绘制时间。此外,当Chrome在这里面临另一个bug时,可能与第一个bug有关,当他们将此rAF timeStamp设置为...我必须承认我不知道...也许是前一个画框的时间戳
再一次,运行无限rAF循环将修复这个奇怪的错误。
因此,您可能需要检查 maybe incoming
requestPostAnimationFrame
method。在
chrome:flags
中启用“Experimental Web Platform features”后,您可以在Chrome 1中访问它。这个方法如果被html标准接受,将允许我们在paint操作发生后立即触发回调。从那里,你应该在最接近的画。
对于那些还没有实现这个建议的浏览器,或者如果这个建议从来没有通过规范来实现,你可以尝试使用Task Scheduling API或MessageEvent来填充它,这应该是下一个事件循环中第一个触发的东西。
1.事实证明,这个功能显然已经从Chrome实验中删除了。看看the implementation issue,我不知道为什么,什么时候,也不知道他们是否计划继续工作。