Canvas滚动序列帧动画图片分段延迟预加载优化求助
滚动触发Canvas序列帧动画的图片分段加载优化方案
针对884张序列帧图片加载过长的问题,以下是分段加载的优化实现,核心思路是初始加载前100张,之后每5秒加载下一批100张,同时加入图片缓存避免重复请求,优化滚动体验。
优化后的JavaScript代码
const html = document.documentElement; const canvas = document.getElementById("hero-lightpass"); const context = canvas.getContext("2d"); const frameCount = 884; const batchSize = 100; // 每批加载图片数量 const imageCache = []; // 缓存已加载的图片,索引对应帧号(从1开始) let currentBatch = 0; // 当前加载批次序号 // 生成指定帧的图片路径 const currentFrame = index => `compressed/${index.toString().padStart(9, 'web_0000')}.webp`; // 加载指定范围的图片批次 const loadBatch = (start, end) => { for (let i = start; i <= end; i++) { if (i > frameCount) break; const img = new Image(); img.src = currentFrame(i); img.onload = () => { imageCache[i] = img; // 第一帧加载完成后绘制初始画面 if (i === 1) scaleToFill(img); }; } }; // 初始化加载第一批图片 const initPreload = () => { const end = Math.min(batchSize, frameCount); loadBatch(1, end); currentBatch = 1; // 启动后续批次的定时加载 startBatchLoading(); }; // 定时加载后续批次图片 const startBatchLoading = () => { const interval = setInterval(() => { const start = currentBatch * batchSize + 1; const end = Math.min((currentBatch + 1) * batchSize, frameCount); if (start > frameCount) { clearInterval(interval); return; } loadBatch(start, end); currentBatch++; }, 5000); // 每5秒加载一批 }; // 将图片缩放填充至整个画布 function scaleToFill(img) { const scale = Math.max(canvas.width / img.width, canvas.height / img.height); const x = (canvas.width / 2) - (img.width / 2) * scale; const y = (canvas.height / 2) - (img.height / 2) * scale; context.drawImage(img, x, y, img.width * scale, img.height * scale); } // 更新画布显示的帧 const updateImage = index => { const targetImg = imageCache[index]; if (targetImg) { scaleToFill(targetImg); } else { // 目标帧未加载时,显示最近已加载的帧避免空白 const nearestLoaded = imageCache.slice(1).reverse().find(img => img); if (nearestLoaded) scaleToFill(nearestLoaded); } }; // 滚动事件监听,触发帧更新 window.addEventListener('scroll', () => { const scrollTop = html.scrollTop; const maxScrollTop = html.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; const frameIndex = Math.min( frameCount - 1, Math.ceil(scrollFraction * frameCount) ); requestAnimationFrame(() => updateImage(frameIndex + 1)); }); // 初始化画布尺寸 canvas.width = window.innerWidth; canvas.height = window.innerHeight; // 窗口大小变化时重新调整画布并绘制当前帧 window.addEventListener('resize', () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; const scrollTop = html.scrollTop; const maxScrollTop = html.scrollHeight - window.innerHeight; const scrollFraction = scrollTop / maxScrollTop; const frameIndex = Math.min( frameCount - 1, Math.ceil(scrollFraction * frameCount) ); const currentImg = imageCache[frameIndex + 1] || imageCache[1]; if (currentImg) scaleToFill(currentImg); }); // 启动加载流程 initPreload();
HTML代码(无改动)
<canvas id="hero-lightpass"></canvas>
关键优化点说明
- 分段加载逻辑:拆分图片加载任务,初始加载前100张,后续每5秒自动加载下一批,大幅降低页面初始加载压力
- 图片缓存:用数组缓存已加载的图片,滚动时直接从缓存取图,避免重复发起网络请求
- 降级处理:当滚动到未加载的帧时,自动显示最近已加载的帧,避免画布空白
- 窗口适配:新增窗口 resize 监听,自动调整画布尺寸并重绘当前帧,保证不同屏幕尺寸下的显示效果
内容的提问来源于stack exchange,提问作者Aslan
相关产品推荐
相关产品推荐

