gas*_*par 5 javascript animation timing requestanimationframe display
我要实现的是检测屏幕上出现某些更改的准确时间(主要是使用Google Chrome浏览器)。例如,我使用显示项目$("xelement").show();或使用进行更改$("#xelement").text("sth new");,然后我想查看Performance.now()到底是什么,当更改以给定的屏幕重新绘制出现在用户的屏幕上时。因此,我完全可以接受任何解决方案-在下文中,我主要指的是requestAnimationFrame(rAF),因为这是应该帮助实现此功能的函数,只是似乎没有实现。见下文。
基本上,正如我所想象的,rAF应该在0-17毫秒内执行其中的所有操作(每当下一帧出现在我的标准60 Hz屏幕上)。此外,timestamp参数应提供此执行时间的值(并且该值基于与performance.now()相同的DOMHighResTimeStamp度量)。
现在,这是我为此进行的众多测试之一: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);
Run Code Online (Sandbox Code Playgroud)
我在Chrome中看到的是:rAF内的函数总是在大约0到3毫秒内执行(从紧接其前的performance.now()开始),最奇怪的是,rAF时间戳与我得到的完全不同rAF内的performance.now()通常比在rAF 之前调用的performance.now()早大约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
Run Code Online (Sandbox Code Playgroud)
在Firefox和IE中,情况有所不同。在Firefox中,“在开始之前vs. RAF回调开始”大约是1-3毫秒或大约16-17毫秒。“对RAF戳之前”始终为正,通常在0-3毫秒左右,但有时在3-17毫秒之间。在IE中,两种差异几乎总是在15-18毫秒左右(正值)。这些或多或少与不同的PC相同。但是,当我在手机的Chrome上运行它时,直到那时,这似乎是正确的:“之前对RAF戳”随机在0-17左右,而“ RAF回调开始”总是在几毫秒后。
有关更多信息:这是一个在线响应时间实验,用户使用自己的PC(但是我通常将浏览器限制为Chrome浏览器,因此这是对我真正重要的唯一浏览器)。反复显示各种项目,将响应时间测量为“从显示元素(当人们看到它的那一刻)到按下键的那一刻”,并从记录的响应时间中计算出平均值项目,然后检查差异在某些项目类型之间。这也意味着,只要记录的时间始终沿某个方向偏斜(例如,始终在元素实际出现之前3毫秒)就没关系,只要该偏斜对于每个显示都是一致的即可,因为只有差异真的很重要 1-2 ms的精度将是理想的选择,但是任何可以缓解随机“刷新率噪声”(0-17 ms)的方法都可以。
我还尝试了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);
Run Code Online (Sandbox Code Playgroud)
使用HTML:
<p><br><br><br><br><br></p>
<span id="stim_id">STIMULUS</span>
Run Code Online (Sandbox Code Playgroud)
解决方案(基于Kaiido的答案)以及有效的显示示例:
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);
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
您遇到的是 Chrome 错误(甚至两个)。
基本上,当requestAnimationFrame回调池为空时,它们会在当前事件循环结束时直接调用它,而无需按照规范的要求等待实际绘制帧。
要解决此错误,您可以保持一个持续不断的requestAnimationFrame循环,但要注意这会将您的文档标记为“动画”并在您的页面上触发一系列副作用(例如在每次屏幕刷新时强制重绘)。所以我不确定你在做什么,但这样做通常不是一个好主意,我宁愿邀请你只在需要时运行这个动画循环。
let needed = true; // set to false when you don't need the rAF loop anymore
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("")
setTimeout(item_display, Math.floor(Math.random() * (1000 - 500 + 1)) + 500);
});
}
chromeWorkaroundLoop();
item_display();
function chromeWorkaroundLoop() {
if (needed) {
requestAnimationFrame(chromeWorkaroundLoop);
}
};Run Code Online (Sandbox Code Playgroud)
现在,requestAnimationFrame回调在下一次绘制之前触发(实际上在同一个事件循环中),并且 TimeStamp 参数应该代表当前帧的所有主要任务和微任务执行后的时间,在它开始它的“更新渲染”子之前任务(此处为第 9 步)。
[编辑]:然而,这并不是浏览器真正实现的,有关更多详细信息,请参阅此问答。
所以它不是你能拥有的最精确的,你是对的,performance.now()在这个回调中使用应该让你更接近实际的绘画时间。
此外,当 Chrome 在这里面临另一个错误时,可能与第一个错误有关,当他们将此 rAF 时间戳设置为...我必须承认我不知道什么...也许是前一个绘画帧的时间戳。
(function() {
let raf_id,
eventLoopReport = {
id: 0,
timeStamp: 0,
now: 0
},
report = {
nb_of_loops_between_call_and_start: -1,
mouseClick_timeStamp: 0,
calling_task: {
eventLoop: null,
now: 0
},
rAF_task: {
eventLoop: null,
now: 0,
timeStamp: 0
}
};
startEventLoopCounter();
btn.onclick = triggerSingleFrame;
// increments eventLoop_id at every event loop
// (or at least every time our postMessage loop fires)
function startEventLoopCounter() {
const channel = new MessageChannel()
channel.port2.onmessage = e => {
eventLoopReport.id ++;
eventLoopReport.timeStamp = e.timeStamp;
eventLoopReport.now = performance.now();
channel.port1.postMessage('*');
};
channel.port1.postMessage('*');
}
function triggerSingleFrame(e) {
// mouseClick Event should be generated at least the previous event loop, so its timeStamp should be in the past
report.mouseClick_timeStamp = e.timeStamp;
const report_calling = report.calling_task;
report_calling.now = performance.now();
report_calling.eventLoop = Object.assign({}, eventLoopReport);
cancelAnimationFrame(raf_id);
raf_id = requestAnimationFrame((raf_ts) => {
const report_rAF = report.rAF_task;
report_rAF.now = performance.now();
report_rAF.timeStamp = raf_ts;
report_rAF.eventLoop = Object.assign({}, eventLoopReport);
report.nb_of_loops_between_call_and_start = report_rAF.eventLoop.id - report_calling.eventLoop.id;
// this should always be positive
report_el.textContent = "rAF.timeStamp - mouse_click.timeStamp: " +
(report.rAF_task.timeStamp - report.mouseClick_timeStamp) + '\n\n' +
// verbose
JSON.stringify(report, null, 2) ;
});
}
})();Run Code Online (Sandbox Code Playgroud)
<button id="btn">flash</button>
<div id="out"></div>
<pre id="report_el"></pre>Run Code Online (Sandbox Code Playgroud)
再一次,运行无限 rAF 循环将修复这个奇怪的错误。
因此,您可能想要检查的一件事是可能传入的 requestPostAnimationFrame方法。
您可以在 Chrome 中访问它,在chrome:flags. 如果 html 标准接受此方法,我们将允许我们在绘制操作发生后立即触发回调。
从那里,您应该离这幅画最近。
var needed = true;
function item_display() {
var before = performance.now();
requestAnimationFrame(function() {
requestPostAnimationFrame(function() {
var rPAF_now = performance.now();
console.log("before vs. rPAF now:", rPAF_now - before);
console.log("");
setTimeout(item_display, Math.floor(Math.random() * (1000 - 500 + 1)) + 500);
});
});
}
if (typeof requestPostAnimationFrame === 'function') {
chromeWorkaroundLoop();
item_display();
} else {
console.error("Your browser doesn't support 'requestPostAnimationFrame' method, be sure you enabled 'Experimental Web Platform features' in chrome:flags");
}
function chromeWorkaroundLoop() {
if (needed) {
requestAnimationFrame(chromeWorkaroundLoop);
}
};Run Code Online (Sandbox Code Playgroud)
对于还没有实现这个提议的浏览器,或者如果这个提议从来没有通过规范实现,你可以尝试使用 MessageEvent 对它进行monkeyPatch,这应该是在下一个事件循环中触发的第一件事。但是,由于我们无法知道我们是否已经在 rAF 回调中,除非通过运行无限 rAF 循环,因此无法从 rAF 回调中调用此猴子补丁。
// monkey-patches requestPostAnimationFrame
//!\ Can not be called from inside a requestAnimationFrame callback
function monkeyPatchRequestPostAnimationFrame() {
console.warn('using a MessageEvent workaround');
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();
}
var needed = true;
function item_display() {
var before = performance.now();
requestPostAnimationFrame(function() {
var rPAF_now = performance.now();
console.log("before vs. rPAF now:", rPAF_now - before);
console.log("");
setTimeout(item_display, Math.floor(Math.random() * (1000 - 500 + 1)) + 500);
});
}
chromeWorkaroundLoop();
item_display();
function chromeWorkaroundLoop() {
if (needed) {
requestAnimationFrame(chromeWorkaroundLoop);
}
};Run Code Online (Sandbox Code Playgroud)
但是,如果您不关心电池电量消耗并且需要从 rAF 内部调用它,则它是:
// monkey-patches requestPostAnimationFrame
// runs an infinite rAF loop for it to be callable inside rAF
function monkeyPatchRequestPostAnimationFrame() {
console.warn('using a MessageEvent workaround');
const channel = new MessageChannel();
const callbacks = [];
let timestamp = 0;
let called = false;
channel.port2.onmessage = e => {
const toCall = callbacks.slice();
callbacks.length = 0;
toCall.forEach(fn => {
try {
fn(timestamp);
} catch (e) {}
});
// loop in here so that we fire after other rAF loops
requestAnimationFrame(loop);
};
function loop(time) {
timestamp = time;
channel.port1.postMessage('');
}
window.requestPostAnimationFrame = function(callback) {
if (typeof callback !== 'function') {
throw new TypeError('Argument 1 is not callable');
}
callbacks.push(callback);
};
loop();
}
if (typeof requestPostAnimationFrame !== 'function') {
monkeyPatchRequestPostAnimationFrame();
}
var needed = true;
function item_display() {
var before = performance.now();
requestPostAnimationFrame(function() {
var rPAF_now = performance.now();
console.log("before vs. rPAF now:", rPAF_now - before);
console.log("");
setTimeout(item_display, Math.floor(Math.random() * (1000 - 500 + 1)) + 500);
});
}
item_display();Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
362 次 |
| 最近记录: |