前端计时器与布局测量:用 requestAnimationFrame 和 ResizeObserver
本文目录
定时器只是请求浏览器稍后调度回调,不提供精准时钟。后台标签页可能被节流,主线程忙碌也会让回调延迟。需要随屏幕刷新更新视觉时用 requestAnimationFrame;需要周期性业务任务时选计时器并管理漂移、重入和生命周期。
计时器的清理和时钟语义
setInterval 不应被当作精确倒计时。若回调执行时间长于周期,工作可能重叠;后台节流也会使计数落后。倒计时基于起止时间戳计算剩余值,而不是每次回调简单减一。网络心跳、重试和业务期限还应由服务端时间与状态兜底。
组件挂载时启动的 timeout、interval、animation frame、observer 和事件监听,都要在卸载时取消。Effect 重跑时先清理上一轮;React Strict Mode 的开发重复挂载正好能暴露不完整清理。Vue 可在对应生命周期解除订阅。减少“设置全局布尔变量以防重复”的临时补丁。
动画和用户偏好
动画使用 requestAnimationFrame 让更新与绘制同步,回调拿到的 timestamp 适合计算经过时间;浏览器切到后台时动画帧可能暂停,所以不要用帧数衡量业务时长。改变 transform/opacity 通常比频繁改变布局尺寸更易控制,但需用性能面板验证。
let frameId = 0;
let startedAt = 0;
const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)');
function renderFrame(timestamp) {
if (!startedAt) startedAt = timestamp;
const progress = Math.min((timestamp - startedAt) / 600, 1);
element.style.transform = 'translateX(' + (120 * progress) + 'px)';
if (progress < 1 && !reducedMotion.matches) {
frameId = requestAnimationFrame(renderFrame);
}
}
function start() {
cancelAnimationFrame(frameId);
startedAt = 0;
if (reducedMotion.matches) element.style.transform = 'translateX(120px)';
else frameId = requestAnimationFrame(renderFrame);
}
还应提供暂停或减少动态效果的路径。不要把“同意条款”“领取权益”设计成必须等待的倒计时,更不能在时间结束前禁用拒绝或撤回选择;这类强迫式设计会损害信任和可访问性。动画应服务信息表达,而不是制造误触压力。
何时使用 ResizeObserver
窗口 resize 事件只描述视口变化;组件宽度可能因侧栏、字体加载、容器网格或内容改变而变化。ResizeObserver 观察元素 content box 或 border box 尺寸,适合响应容器布局,而不是轮询尺寸。
观察回调里直接改被观察元素的尺寸可能形成循环通知。优先让 CSS 容器查询承担纯布局响应;确需 JavaScript 时,将读写拆开、避免同步强制布局,并在卸载时 disconnect()。如果用 requestAnimationFrame 批量写入,也要确保尺寸不会再次触发无休止更新。
offsetWidth 包含边框且通常是整数;clientWidth 包含 padding 但不含边框和滚动条;scrollWidth 描述内容所需的滚动宽度。getBoundingClientRect() 返回相对视口的几何矩形,包含变换后的视觉尺寸。测量前明确想知道的是布局框、内容框还是视口位置,避免误把它们互换。
验收清单
- 隐藏标签页、低帧率和慢设备下,动画是否仍按真实经过时间推进?
- 离开页面或卸载组件后是否取消回调、观察器和事件订阅?
- 用户偏好减少动态效果时是否取消非必要动画?
- ResizeObserver 回调是否可能改回观察尺寸并产生循环?
- 计时器是否用于真实时钟任务,而非假装有固定精确调度?
参考:MDN requestAnimationFrame、MDN Resize Observer API、MDN ResizeObserver、MDN setInterval、MDN prefers-reduced-motion、W3C WCAG 2.2。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。