TL;DR: requestIdleCallback
支持已在 Firefox Nightly 中实现,计划在 Firefox 52 中发布。
构建交互式网站最混乱的地方在于:主线程与 UI 线程是同一个。渲染页面和响应用户操作与计算、网络活动以及 DOM 操作存在竞争关系。其中一些操作可以通过 使用 Worker 安全地转移到其他线程,并且相对容易,但只有主线程可以更改 DOM 和许多其他 Web 平台功能。历史上,脚本没有办法与用户交互和页面渲染“和谐共处”,导致帧速率下降和输入滞后。
显然,如果情况仍然如此,我不会写这篇文章!
如果您必须在主线程上执行任务(更改 DOM 或与仅在主线程上运行的 Web API 交互),您现在可以请求浏览器为您提供一个可以安全执行此操作的时间窗口!以前,开发人员使用 setTimeout
让浏览器在周期性操作之间喘口气。这最多只会将任务推迟到事件循环的下一轮。并且它仍然会导致卡顿。
requestIdleCallback
应运而生。从表面上看,它的基本用法类似于 setTimeout
,甚至更类似于 setImmediate
requestIdleCallback(sporadicScriptAction);
但是,浏览器有更多自由来满足您的请求。而不是等待特定的毫秒数或直到事件循环的下一个立即传递,requestIdleCallback
允许浏览器等待直到它识别到一个 空闲期。空闲期可能是两个帧之间绘制时仅有的几毫秒。
请记住,流畅的 60fps 动画在两帧之间只有 16 毫秒,其中大部分可能需要浏览器。所以我们说的是只有几毫秒!另外,如果没有任何动画或其他视觉变化正在发生,浏览器可以选择允许最多 50 毫秒。如果用户与页面交互,视觉反馈在 100 毫秒内完成会让人感觉“瞬间”。有了最多 50 毫秒的空闲时间,浏览器有充足的时间来优雅地处理任何传入的用户事件。
因此,您的回调可能会得到一个 1ms-10ms 的时间窗口来执行操作,也可能得到一个轻松悠闲的 50ms!您的代码如何知道?这就是 requestIdleCallback
与其祖先不同的部分。当您的回调被调用时,它会收到有关它可以执行操作的时间的信息。
// we know this action can take longer than 16ms,
// so let's be safe and only do it when we have the time.
function sporadicScriptAction (timing) {
if (timing.timeRemaining() > 20) {
// update DOM or what have you
} else {
// request another idle period or simply do nothing
}
}
您的回调将传递一个 IdleDeadline
对象,该对象包含一个 timeRemaining
方法。timeRemaining()
将提供一个关于空闲期剩余时间的实时估计,允许脚本确定需要完成多少工作,或者是否需要完成工作。
如果我 *必须* 做事情怎么办?
协作式多任务处理是各方之间的谈判,为了成功,双方(浏览器和脚本)都需要做出让步。如果您的主线程操作必须在特定时间范围内完成(UI 更新或其他时间敏感的操作),脚本可以请求一个 timeout
,在此之后回调 *必须* 运行。
// Call me when you have time, but wait no longer than 1000ms
requestIdleCallback(neededUIUpdate, { timeout: 1000 });
如果在请求的超时之前出现了空闲期,操作将如之前一样进行。但是,如果请求达到提供的超时时间,并且没有可用的空闲期,回调将无论如何运行。可以通过检查 IdleDeadline
对象的 didTimeout
属性来检测这一点。
function sporadicScriptAction (timing) {
// will be 0, because we reached the timeout condition
if (timing.timeRemaining() > 20) {
} else if (timing.didTimeout) { // will be true
}
}
不用了(取消调用)
与所有已安排的回调机制(setTimeout
、setImmediate
、setInterval
)和 requestAnimationFrame
一样,requestIdleCallback
返回一个句柄,可用于取消已安排的函数调用。
var candyTime = requestIdleCallback(goTrickOrTreating);
// it's November.
cancelIdleCallback(candyTime);
更协调的 Web
就像 requestAnimationFrame
为我们提供了与浏览器绘制协调的工具一样,requestIdleCallback
提供了一种与浏览器整体工作计划进行合作的方法。如果操作正确,用户甚至不会注意到您正在执行工作,他们只会感受到更流畅、更响应的网站。我们无法回到过去,在 JS 中将 UI 线程与主线程分离,但有了合适的工具、关注点分离和计划,我们仍然可以构建出色的交互式体验。
3 条评论