显示的确切时间:requestAnimationFrame 用法和时间线
Exact time of display: requestAnimationFrame usage and timeline
我想实现的是检测屏幕上出现某种变化的精确时间(主要是Google Chrome)。例如,我使用 $("xelement").show();
显示一个项目或使用 $("#xelement").text("sth new");
更改它然后我想看看 performance.now() 究竟是什么时候更改出现在用户屏幕上屏幕重绘。所以我对任何解决方案都持开放态度——下面我主要指的是 requestAnimationFrame (rAF),因为它是应该帮助实现这一目标的功能,只是它似乎没有;见下文。
基本上,正如我想象的那样,rAF 应该在大约 0-17 毫秒内执行其中的所有内容(每当下一帧出现在我的标准 60 Hz 屏幕上时)。此外,时间戳参数应给出此执行时间的值(并且此值基于与 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);
我在Chrome中看到的是:rAF 中的函数总是在大约 0-3 毫秒内执行(从它之前的 performance.now() 开始计算),而且,最奇怪的是, rAF 时间戳与我在 rAF 中使用 performance.now() 得到的完全不同,通常比调用 before[= 的 performance.now() 早大约 0-17 毫秒55=] rAF(但有时之后大约 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 中,“对比 RAF 回调开始之前”大约为 1-3 毫秒或大约 16-17 毫秒。 “之前与 RAF 标记”始终为正,通常在 0-3 毫秒左右,但有时在 3-17 毫秒之间。在 IE 中,这两个差异几乎总是在 15-18 毫秒左右(正)。这些或多或少与不同的 PC 相同。然而,当我 运行 它出现在我的 phone 的 Chrome 上时,然后,并且只有那时,它似乎合理正确:“在 vs. RAF 邮票之前”在 0-17 左右随机,并且“RAF 回调开始”总是在几毫秒之后。
有关更多上下文:这是针对用户使用自己的 PC 的在线响应时间实验(但我通常将浏览器限制为 Chrome,因此这是对我来说真正重要的唯一浏览器)。我重复显示各种项目,并测量响应时间为“从元素显示的那一刻(当人看到它时)到他们按下一个键的那一刻”,并从记录的响应时间中计算特定的平均值项目,然后检查某些项目类型之间的差异。这也意味着如果记录的时间总是在一个方向上有点偏差(例如总是在元素实际出现之前 3 毫秒)并不重要,只要这种偏差对于每个显示都是一致的,因为只有差异真的很重要。 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 没有真正的区别。
您正在经历什么is a Chrome bug(甚至两个)。
基本上,当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);
}
};
现在,requestAnimationFrame 回调在 下一次绘制之前触发(实际上在同一个事件循环中),并且 TimeStamp 参数应该表示在执行当前帧的所有主要任务和微任务之后,开始其“更新渲染”子任务之前的时间(步骤 9 here)。
[edit]:然而,这并不是浏览器真正实现的,有关详细信息,请参阅 this Q/A。
所以这不是你能拥有的最精确的,你是对的,在这个回调中使用 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) ;
});
}
})();
<button id="btn">flash</button>
<div id="out"></div>
<pre id="report_el"></pre>
再一次,运行无限 rAF 循环将修复这个奇怪的错误。
所以您可能想要检查的一件事是 可能传入 requestPostAnimationFrame
method。
您可以在 Chrome、1 中访问它,然后在 [=17] 中启用“实验性 Web 平台功能” =].此方法如果被 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);
}
};
对于尚未实现该提案的浏览器,或者如果该提案从未通过规范实现,您可以尝试使用 MessageEvent 对其进行 polyfill,这应该是在下一个事件中首先触发的事情循环。
// polyfills requestPostAnimationFrame
// requestPostAnimationFrame polyfill
if (typeof requestPostAnimationFrame !== "function") {
(() => {
const channel = new MessageChannel();
const callbacks = [];
let timestamp = 0;
let called = false;
let scheduled = false; // to make it work from rAF
let inRAF = false; // to make it work from rAF
channel.port2.onmessage = e => {
called = false;
const toCall = callbacks.slice();
callbacks.length = 0;
toCall.forEach(fn => {
try {
fn(timestamp);
} catch (e) {}
});
}
// We need to overwrite rAF to let us know we are inside an rAF callback
// as to avoid scheduling yet an other rAF, which would be one painting frame late
// We could have hooked an infinite loop on rAF, but this means
// forcing the document to be animated all the time
// which is bad for perfs
const rAF = globalThis.requestAnimationFrame;
globalThis.requestAnimationFrame = function(...args) {
if (!scheduled) {
scheduled = true;
rAF.call(globalThis, (time) => inRAF = time);
globalThis.requestPostAnimationFrame(() => {
scheduled = false;
inRAF = false;
});
}
rAF.apply(globalThis, args);
};
globalThis.requestPostAnimationFrame = function(callback) {
if (typeof callback !== "function") {
throw new TypeError("Argument 1 is not callable");
}
callbacks.push(callback);
if (!called) {
if (inRAF) {
timestamp = inRAF;
channel.port1.postMessage("");
} else {
requestAnimationFrame((time) => {
timestamp = time;
channel.port1.postMessage("");
});
}
called = true;
}
};
})();
}
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);
}
};
- 事实证明,此功能显然已从 Chrome 实验中删除。查看 the implementation issue 我找不到原因、时间,也不知道他们是否打算继续努力。
我想实现的是检测屏幕上出现某种变化的精确时间(主要是Google Chrome)。例如,我使用 $("xelement").show();
显示一个项目或使用 $("#xelement").text("sth new");
更改它然后我想看看 performance.now() 究竟是什么时候更改出现在用户屏幕上屏幕重绘。所以我对任何解决方案都持开放态度——下面我主要指的是 requestAnimationFrame (rAF),因为它是应该帮助实现这一目标的功能,只是它似乎没有;见下文。
基本上,正如我想象的那样,rAF 应该在大约 0-17 毫秒内执行其中的所有内容(每当下一帧出现在我的标准 60 Hz 屏幕上时)。此外,时间戳参数应给出此执行时间的值(并且此值基于与 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);
我在Chrome中看到的是:rAF 中的函数总是在大约 0-3 毫秒内执行(从它之前的 performance.now() 开始计算),而且,最奇怪的是, rAF 时间戳与我在 rAF 中使用 performance.now() 得到的完全不同,通常比调用 before[= 的 performance.now() 早大约 0-17 毫秒55=] rAF(但有时之后大约 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 中,“对比 RAF 回调开始之前”大约为 1-3 毫秒或大约 16-17 毫秒。 “之前与 RAF 标记”始终为正,通常在 0-3 毫秒左右,但有时在 3-17 毫秒之间。在 IE 中,这两个差异几乎总是在 15-18 毫秒左右(正)。这些或多或少与不同的 PC 相同。然而,当我 运行 它出现在我的 phone 的 Chrome 上时,然后,并且只有那时,它似乎合理正确:“在 vs. RAF 邮票之前”在 0-17 左右随机,并且“RAF 回调开始”总是在几毫秒之后。
有关更多上下文:这是针对用户使用自己的 PC 的在线响应时间实验(但我通常将浏览器限制为 Chrome,因此这是对我来说真正重要的唯一浏览器)。我重复显示各种项目,并测量响应时间为“从元素显示的那一刻(当人看到它时)到他们按下一个键的那一刻”,并从记录的响应时间中计算特定的平均值项目,然后检查某些项目类型之间的差异。这也意味着如果记录的时间总是在一个方向上有点偏差(例如总是在元素实际出现之前 3 毫秒)并不重要,只要这种偏差对于每个显示都是一致的,因为只有差异真的很重要。 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 没有真正的区别。
您正在经历什么is a Chrome bug(甚至两个)。
基本上,当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);
}
};
现在,requestAnimationFrame 回调在 下一次绘制之前触发(实际上在同一个事件循环中),并且 TimeStamp 参数应该表示在执行当前帧的所有主要任务和微任务之后,开始其“更新渲染”子任务之前的时间(步骤 9 here)。
[edit]:然而,这并不是浏览器真正实现的,有关详细信息,请参阅 this Q/A。
所以这不是你能拥有的最精确的,你是对的,在这个回调中使用 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) ;
});
}
})();
<button id="btn">flash</button>
<div id="out"></div>
<pre id="report_el"></pre>
再一次,运行无限 rAF 循环将修复这个奇怪的错误。
所以您可能想要检查的一件事是 可能传入 requestPostAnimationFrame
method。
您可以在 Chrome、1 中访问它,然后在 [=17] 中启用“实验性 Web 平台功能” =].此方法如果被 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);
}
};
对于尚未实现该提案的浏览器,或者如果该提案从未通过规范实现,您可以尝试使用 MessageEvent 对其进行 polyfill,这应该是在下一个事件中首先触发的事情循环。
// polyfills requestPostAnimationFrame
// requestPostAnimationFrame polyfill
if (typeof requestPostAnimationFrame !== "function") {
(() => {
const channel = new MessageChannel();
const callbacks = [];
let timestamp = 0;
let called = false;
let scheduled = false; // to make it work from rAF
let inRAF = false; // to make it work from rAF
channel.port2.onmessage = e => {
called = false;
const toCall = callbacks.slice();
callbacks.length = 0;
toCall.forEach(fn => {
try {
fn(timestamp);
} catch (e) {}
});
}
// We need to overwrite rAF to let us know we are inside an rAF callback
// as to avoid scheduling yet an other rAF, which would be one painting frame late
// We could have hooked an infinite loop on rAF, but this means
// forcing the document to be animated all the time
// which is bad for perfs
const rAF = globalThis.requestAnimationFrame;
globalThis.requestAnimationFrame = function(...args) {
if (!scheduled) {
scheduled = true;
rAF.call(globalThis, (time) => inRAF = time);
globalThis.requestPostAnimationFrame(() => {
scheduled = false;
inRAF = false;
});
}
rAF.apply(globalThis, args);
};
globalThis.requestPostAnimationFrame = function(callback) {
if (typeof callback !== "function") {
throw new TypeError("Argument 1 is not callable");
}
callbacks.push(callback);
if (!called) {
if (inRAF) {
timestamp = inRAF;
channel.port1.postMessage("");
} else {
requestAnimationFrame((time) => {
timestamp = time;
channel.port1.postMessage("");
});
}
called = true;
}
};
})();
}
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);
}
};
- 事实证明,此功能显然已从 Chrome 实验中删除。查看 the implementation issue 我找不到原因、时间,也不知道他们是否打算继续努力。