setInterval中预加载图片出现同步阻塞,如何实现循环与加载并行?
解决方案
问题根源
你遇到的setInterval阻塞问题本质是以下几个原因共同导致的:
async关键字只有配合await使用才会产生异步中断效果,你当前的preload方法没有任何等待逻辑,所有DOM操作、资源请求触发逻辑都是同步执行的,会占用主线程- 构造函数是同步执行的,你在构造函数内直接调用
preload,会阻塞后续动画逻辑直到preload内的同步代码全部执行完成 - 存在冗余DOM操作:同一个img元素被连续两次append到同一个容器,触发不必要的页面重绘,额外消耗主线程资源
- 给预加载图片设置了
loading="lazy"属性,反而延迟了预加载请求的触发时机,不符合预加载需求
优化后的代码实现
修改后的Preloader类
class Preloader { constructor(gen, attach_id) { this.gen = gen this.attach_id = attach_id this.loaded = false // 标记图片是否加载完成 } preload() { // 将预加载逻辑放入宏任务队列,不阻塞当前动画主线程 setTimeout(() => { const container = document.getElementById("generation_animation"); const preload_img = document.createElement("img"); preload_img.style.display = "none"; preload_img.style.width = "0px"; preload_img.id = "preload_generation"; container.appendChild(preload_img); // 监听加载状态 preload_img.onload = () => { this.loaded = true console.log("预加载完成", this.gen) } preload_img.onerror = () => { console.error("预加载失败", this.gen) } // 触发资源请求 preload_img.src = "res/img/Generations/" + this.gen.toString() + ".png"; console.log("触发预加载", this.gen); }, 0) } add() { // 未加载完成则延后执行,避免出现空白/碎图 if (!this.loaded) { setTimeout(() => this.add(), 100) return } const previous = document.getElementById("generation") const current = document.getElementById("preload_generation"); current.style.width = "50%"; current.style.display = ""; current.classList.add("generations_class"); current.id = "generation"; previous.remove(); } }
修改后的动画逻辑(推荐用requestAnimationFrame替代setInterval)
const roman_numeral = document.getElementById('roman_numeral'); let i = 1 let generation = 1 let preload; function animate() { roman_numeral.innerHTML = romanize(i); if(i == 1){ preload = new Preloader(generation+1, "generation") // 异步触发预加载,不阻塞当前动画执行 preload.preload() } i++ if(i == 1000){ i = 1; generation ++; preload.add() } if(generation == 1035){ i = 1; generation = 1 } // 对齐浏览器渲染频率循环执行 requestAnimationFrame(animate) } // 启动动画 requestAnimationFrame(animate)
额外说明
如果一定要保留原有的setInterval写法,只需将preload的执行逻辑改为异步触发即可,不需要修改其他逻辑。10ms的setInterval执行频率高于浏览器默认的60fps刷新频率(单帧间隔约16.6ms),会产生大量无效执行,换成requestAnimationFrame可以大幅降低主线程占用,动画也会更流畅。
内容的提问来源于stack exchange,提问作者Elias Knudsen
相关产品推荐
相关产品推荐

