Chromium中多同域iframe的requestAnimationFrame回调频率异常升高求助
同域多iframe下requestAnimationFrame帧率异常问题
问题现象
我用TypeScript+WebGL开发视觉艺术作品,部分作品以iframe形式嵌入页面。发现当页面中嵌入多个同域作品iframe时,每个iframe的动画速度都会变快——经排查,每个独立iframe的requestAnimationFrame回调频率会随同级iframe数量增加而升高,并非整体帧率总和上升,而是单个iframe的运行速度异常加快。
复现情况:
- 1个iframe时,帧率维持正常的约60Hz
- 2个iframe时,每个iframe的FPS明显超过60(截图显示单iframe FPS突破100)
- iframe数量越多,单iframe帧率上升越显著
额外差异:
- 本地Vite开发服务器(
npm run dev)嵌入作品时,帧率始终正常 - 生产服务器同域作品嵌入时出现异常
- 仅在Arch Linux系统的Chromium 126.0.6478.182中出现该问题,Firefox无异常
相关代码示例
嵌入多iframe的页面HTML
<body> <div id="stack-container"> <div class="wrapper" style="opacity: 0.51; z-index: 0;"><iframe src="https://gfx.aimparency.org/controllable/fps/"></iframe></div> <div class="wrapper" style="opacity: 0; z-index: 1;"><iframe src="https://gfx.aimparency.org/controllable/fps/"></iframe></div> </div> <script type="module" src="/src/main.ts?t=1722986133016"></script> ... </body>
iframe内的最小作品代码(TypeScript)
import './style.css' import { Clock } from 'time-n-controls'; class Loop { clock = new Clock() fpsElement = document.getElementById('fps') as HTMLParagraphElement start() { this.loop() } private loop() { this.clock.tick() this.fpsElement.innerText = this.clock.getTickSmoothFPS().toFixed(2) requestAnimationFrame(this.loop.bind(this)) } } window.onload = () => { const loop = new Loop() loop.start() }
Clock类核心代码
// tick shall be called once per frame, especially for tickDelta private smoothFPS = 0 private previousTick = Date.now() tick() { const now = Date.now() this.tickDeltaMS = now - this.previousTick this.tickDeltaS = this.tickDeltaMS * 0.001 this.previousTick = now this.smoothFPS = this.smoothFPS * 0.9 + 0.1 / (this.tickDeltaS + 0.001) this.update() this.notifyQueues() this.updatedDynamicTimes(this.tickDeltaS) } getTickSmoothFPS() { return this.smoothFPS }
原因猜测
怀疑Chromium对同域iframe的requestAnimationFrame调度存在异常:当多个同域iframe同时请求动画帧时,浏览器会为每个iframe触发所有iframe的回调,导致单个iframe的回调被高频重复调用。本地开发环境与生产环境的差异可能源于浏览器对本地服务器、生产服务器的同域策略或调度逻辑不同。
解决方案
1. 替换时间计算方式
Date.now()依赖系统时间,可能受同域iframe的时钟同步或浏览器节流影响,改用**高精度、单调递增的performance.now()**计算帧间隔,避免时间误差:
// 修改Clock类的tick方法 private smoothFPS = 0 private previousTick = performance.now() tick() { const now = performance.now() this.tickDeltaMS = now - this.previousTick this.tickDeltaS = this.tickDeltaMS * 0.001 this.previousTick = now this.smoothFPS = this.smoothFPS * 0.9 + 0.1 / (this.tickDeltaS + 0.001) this.update() this.notifyQueues() this.updatedDynamicTimes(this.tickDeltaS) }
2. 强制限制帧间隔
手动设置每帧的最小间隔(60Hz对应约16.67ms),避免回调触发过于频繁:
// 修改Loop类的loop方法 private lastFrameTime = performance.now() private loop() { const currentTime = performance.now() const delta = currentTime - this.lastFrameTime // 确保至少间隔16.67ms才处理下一帧 if (delta >= 16.67) { this.clock.tick() this.fpsElement.innerText = this.clock.getTickSmoothFPS().toFixed(2) this.lastFrameTime = currentTime } requestAnimationFrame(this.loop.bind(this)) }
3. 父页面统一调度动画帧(同域场景)
由父页面创建唯一的requestAnimationFrame循环,通过消息通知所有iframe执行动画,避免重复触发:
- 父页面代码:
const iframes = document.querySelectorAll('iframe') function mainLoop() { iframes.forEach(iframe => { iframe.contentWindow.postMessage('tick', '*') }) requestAnimationFrame(mainLoop) } requestAnimationFrame(mainLoop)
- iframe内修改逻辑:
class Loop { clock = new Clock() fpsElement = document.getElementById('fps') as HTMLParagraphElement constructor() { window.addEventListener('message', (e) => { if (e.data === 'tick') { this.updateFrame() } }) } private updateFrame() { this.clock.tick() this.fpsElement.innerText = this.clock.getTickSmoothFPS().toFixed(2) } } window.onload = () => { const loop = new Loop() }
内容的提问来源于stack exchange,提问作者Felix Niemeyer
相关产品推荐
相关产品推荐

