RequestAnimationFrame无法停止日志输出的视频播放器进度条问题
视频暂停后requestAnimationFrame仍持续执行的问题及解决方法
问题描述
我在实现视频播放器进度条动画时遇到了异常:调用视频pause方法并执行cancelAnimationFrame后,requestAnimationFrame的回调函数仍在持续执行,控制台日志不停输出。
相关代码如下:
let stopId; const playVideo = () =>{ if(isPlaying){ videoRef.current.pause() window.cancelAnimationFrame(stopId) }else{ videoRef.current.play(); window.requestAnimationFrame(calcTime) } setIsPlaying(!isPlaying) } const calcTime = () =>{ stopId = window.requestAnimationFrame(calcTime) console.log('hey') }
我做了额外测试,修改calcTime加入延时暂停逻辑:
const calcTime = () =>{ stopId = window.requestAnimationFrame(calcTime) console.log('hey') setTimeout(() => { videoRef.current.pause() window.cancelAnimationFrame(stopId) console.log('pause') }, 5000); }
测试结果是先输出约300次hey才停止,之后又输出约300次pause。我想知道:视频暂停后requestAnimationFrame是不是真的还在运行?会不会引发性能问题?
问题原因
核心问题是每次调用requestAnimationFrame(calcTime)都会生成新的请求ID,但你只保存了最后一次的ID,之前的请求并未被取消:
- 第一个代码中,
calcTime递归调用自己时,每次都会把新的请求ID覆盖stopId。调用cancelAnimationFrame(stopId)只能取消最后一次的请求,而之前已经被调度的calcTime回调仍会执行,执行时又会发起新的请求,导致循环无法终止。 - 第二个测试里,5秒内(按60fps计算约300次)
requestAnimationFrame已经调度了几百次calcTime,每个calcTime都创建了一个setTimeout。即使取消了最后一次动画请求,已经排队的calcTime还是会执行,进而触发对应的setTimeout,所以会输出300次pause。
解决方案
要彻底终止动画循环,需要通过状态变量控制是否继续发起新的requestAnimationFrame请求,同时取消当前请求:
let stopId; let shouldAnimate = false; // 新增状态变量控制动画循环 const playVideo = () =>{ if(isPlaying){ videoRef.current.pause() shouldAnimate = false; // 标记停止动画 window.cancelAnimationFrame(stopId) }else{ videoRef.current.play(); shouldAnimate = true; // 标记开始动画 window.requestAnimationFrame(calcTime) } setIsPlaying(!isPlaying) } const calcTime = () =>{ if (!shouldAnimate) return; // 状态为停止时,不再发起新请求 stopId = window.requestAnimationFrame(calcTime) console.log('hey') // 这里编写进度条更新逻辑 }
性能影响说明
如果放任未被取消的requestAnimationFrame持续运行,确实会造成不必要的性能消耗——浏览器会不断执行回调函数,占用主线程资源,可能导致页面卡顿。视频暂停后动画已无运行必要,这种无效执行完全可以避免。
内容的提问来源于stack exchange,提问作者john pear
相关产品推荐
相关产品推荐

