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

如何用原生JS消除canvas滚动重绘异常 实现AirPods同款360滚动展示效果

问题根因

  • 预加载逻辑仅触发了图片下载,没有将加载完成的图片对象存入缓存数组,滚动切换帧时仍然需要重新等待图片加载,drawImage执行时图片未就绪导致画面空白、闪烁
  • 滚动事件未做节流,短时间内会触发数十次帧更新请求,渲染逻辑执行混乱出现跳帧
  • 帧切换逻辑直接修改img.src后立刻执行绘制,没有等待当前帧图片加载完成就渲染,必然出现绘制异常

修复后完整代码

JS代码

const html = document.documentElement;
const canvas = document.getElementById("hero-lightpass");
const context = canvas.getContext("2d");

const frameCount = 148;
// 图片缓存数组
const imageCache = [];
let currentFrameIndex = 1;
// 节流标记,避免重复触发渲染
let isUpdating = false;

const currentFrame = index => (`https://connectplus.s3.ap-southeast-2.amazonaws.com/connect-plus/${index.toString().padStart(4, '0')}.png`)

// 预加载逻辑修改:加载完成后存入缓存数组
const preloadImages = async () => {
    const loadImage = (index) => {
        return new Promise((resolve) => {
            const img = new Image();
            img.src = currentFrame(index);
            img.onload = () => {
                imageCache[index] = img;
                resolve();
            };
        });
    };
    // 批量加载所有帧
    const loadTasks = [];
    for(let i = 1; i <= frameCount; i++){
        loadTasks.push(loadImage(i));
    }
    await Promise.all(loadTasks);
    // 所有帧加载完成后再绑定滚动事件,避免加载过程中滚动出现异常
    initScrollEvent();
};

// 初始化画布参数
canvas.width = 1158;
canvas.height = 770;
const renderFrame = (index) => {
    if(!imageCache[index] || isUpdating) return;
    isUpdating = true;
    // 清空画布后绘制新帧
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.drawImage(imageCache[index], 0, 0);
    currentFrameIndex = index;
    isUpdating = false;
}

const initScrollEvent = () => {
    window.addEventListener('scroll', () => {
        const scrollTop = html.scrollTop;
        const maxScrollTop = html.scrollHeight - window.innerHeight;
        const scrollFraction = scrollTop / maxScrollTop;
        const frameIndex = Math.min(
            frameCount,
            Math.max(1, Math.ceil(scrollFraction * frameCount))
        );
        // 只有帧索引变化时才触发重绘,避免无效渲染
        if(frameIndex === currentFrameIndex) return;
        requestAnimationFrame(() => renderFrame(frameIndex))
    });
}

// 预加载完成后渲染第一帧
preloadImages().then(() => {
    renderFrame(1);
});

CSS代码(可选优化,解决canvas画面模糊问题)

html {
  height: 100vh;
}

body {
    margin: 0;
    padding: 0;
    width: auto;
    height: auto;
}

.test {
    height: 500vh;
    background-color: #ffffff;
}

canvas {
    /* 关闭canvas平滑插值,避免帧画面模糊 */
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-crisp-edges;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

HTML代码无修改,保持原有逻辑即可

内容的提问来源于stack exchange,提问作者tengkuzulfadli

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 16:06:06