
本文解决使用 gsap scrolltrigger 实现画布帧动画时“首屏无图、仅滚动才显示、回滚即消失”的问题,核心是确保动画资源就绪后立即执行首次渲染,并脱离滚动依赖实现初始可见。
本文解决使用 gsap scrolltrigger 实现画布帧动画时“首屏无图、仅滚动才显示、回滚即消失”的问题,核心是确保动画资源就绪后立即执行首次渲染,并脱离滚动依赖实现初始可见。
在基于 Canvas 的逐帧动画中,若仅将 render() 绑定到 GSAP 的 onUpdate 回调(如 ScrollTrigger 的滚动驱动逻辑),会导致动画完全受制于滚动状态:页面加载完成但未触发滚动时,render() 从不执行,Canvas 保持空白;而当用户回滚超出触发区域,ScrollTrigger 暂停或重置动画,画面随即消失。
根本原因在于:渲染行为未与资源加载和页面生命周期解耦。当前代码中,render() 仅在 imagesLoaded === frameCount 时被调用一次(作为 onload 的收尾),之后全权交由 ScrollTrigger 控制——但 ScrollTrigger 默认不会在初始化时主动触发 onUpdate,尤其当起始位置未满足 start 条件时。
✅ 正确做法是:显式、主动、时机可控地触发首次渲染,并确保其独立于滚动事件。推荐方案如下:
-
封装初始化逻辑为独立函数
将画布尺寸设置与首帧绘制合并为setup()函数,既响应窗口变化,也响应页面加载:
function setup() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 关键:强制渲染当前帧(默认为第 0 帧)
render();
}
// 页面加载完成时立即执行
window.addEventListener('load', setup);
// 同时保留响应式能力(注意:需防抖优化)
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(setup, 150); // 防抖 150ms
});
let resizeTimer;
确保
imageSeq.frame初始值有效且可渲染
当前代码中imageSeq.frame初始化为0,但render()中使用imageSeq.frame | 0取整,逻辑正确。需确认images[0]已成功加载——因此onload计数后调用render()是必要前提,但不能替代setup()的主动调用。增强健壮性:添加加载失败兜底
建议补充错误处理,避免单张图片加载失败导致整个动画卡死:
img.onerror = () => {
console.warn(`Failed to load frame ${i}:`, files(i));
imagesLoaded++;
if (imagesLoaded === frameCount) setup(); // 仍尝试初始化
};
-
CSS 补充建议
当前.hero canvas的margin-top: -189px属于硬编码偏移,易因布局变动失效。推荐改用现代布局控制,例如:
.hero {
position: relative;
overflow: hidden;
}
#home canvas {
display: block;
width: 100%;
height: 100vh;
object-fit: cover;
}
最后提醒:ScrollTrigger 的 scrub: 1 虽能实现平滑过渡,但高频重绘对性能敏感。若动画仅需“进入视口后播放一次”,可改用 once: true + onEnter 触发;若必须滚动同步,则务必配合 resize 防抖与 will-change: transform 等优化手段。核心原则始终不变——渲染控制权必须掌握在开发者手中,而非完全委托给触发条件。










