You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 12:12:03