关于 gsap-canvas
gsap-canvas 是面向前端开发者的 GSAP Canvas 动画技能,说明如何用普通 JavaScript 对象承载动画状态,再通过 Canvas 2D API 绘制。涵盖粒子动画、控制、缩放处理和性能技巧。
主要功能
- 01GSAP 对象动画与 Canvas 绘制
- 02粒子系统制作方法
- 03播放与暂停控制
- 04画布缩放处理
- 05渲染性能建议
适用场景
- 01制作 Canvas 粒子动画
- 02将 GSAP 动画绘制到画布
- 03为画布动画添加播放控制
- 04优化逐帧绘制流程
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ iotron/ gsap-cookbook --skill gsap-canvas安装后在 Claude Code 中输入/gsap-canvas确认已加载。
附带文件references/canvas-patterns.md
详细介绍
GSAP Canvas — 渲染模式
流程:gsap-setup → gsap-canvas → gsap-optimise → gsap-test
配套内容:如需查阅 GSAP 核心 API 参考,请调用 gsap-core。如需了解时间轴排序,请调用 gsap-timeline。本技能仅涵盖 canvas 渲染方案。需要:
greensock/gsap-skills
1. 模式
使用 GSAP 执行 Canvas 动画与 DOM 动画有本质区别。GSAP 不会直接操作 DOM 元素,而是:
- 创建带有数值属性的普通 JS 对象(x、y、scale、rotation、alpha)。
- 使用 GSAP 时间轴或补间动画这些对象——GSAP 会对这些数值进行插值。
- 为时间轴附加
onUpdate回调,读取对象的当前值,并使用 Canvas 2D API 绘制到 canvas 上。
Plain objects → GSAP tweens numbers → onUpdate draws to canvas
↑ ↓
{ x, y, scale } ctx.drawImage(img, x, y, w*scale, h*scale)
这意味着 GSAP 负责缓动、交错、重复、往返和时间轴排序,而整个渲染流程完全由你掌控。
// Minimal example: animate a circle across canvas
const dot = { x: 0, y: 250, radius: 20 }
gsap.to(dot, {
x: 800, duration: 2, ease: 'power2.inOut',
onUpdate() {
ctx.clearRect(0, 0, cw, ch)
ctx.beginPath()
ctx.arc(dot.x, dot.y, dot.radius, 0, Math.PI * 2)
ctx.fill()
},
})
2. 粒子系统方案
让一组带有精灵图的粒子向内绕行。GSAP 负责所有运动;onUpdate 在每一帧重新绘制。
设置粒子数组
const particles = Array.from({ length: 99 }, (_, i) => ({
x: 0, y: 0, scale: 0, rotate: 0,
img: Object.assign(new Image(), {
src: `https://assets.codepen.io/16327/flair-${2 + (i % 21)}.png`,
}),
}))
构建时间轴
const radius = Math.max(cw, ch)
const tl = gsap.timeline({ onUpdate: draw })
.fromTo(particles, {
x: (i) => {
const angle = (i / particles.length) * Math.PI * 2 - Math.PI / 2
return Math.cos(angle * 10) * radius
},
y: (i) => {
const angle = (i / particles.length) * Math.PI * 2 - Math.PI / 2
return Math.sin(angle * 10) * radius
},
scale: 1.1,
rotate: 0,
}, {
duration: 5, ease: 'sine',
x: 0, y: 0, scale: 0, rotate: -3,
stagger: { each: -0.05, repeat: -1 },
}, 0)
.seek(99) // jump ahead so repeating stagger is mid-flow
绘制函数
function draw() {
particles.sort((a, b) => a.scale - b.scale) // z-sort by scale
ctx.clearRect(0, 0, cw, ch)
particles.forEach((p) => {
ctx.translate(cw / 2, ch / 2)
ctx.rotate(p.rotate)
ctx.drawImage(p.img, p.x, p.y, p.img.width * p.scale, p.img.height * p.scale)
ctx.resetTransform()
})
}
3. 控制与调整大小
通过 timeScale 切换播放/暂停
canvas.addEventListener('pointerup', () => {
gsap.to(tl, {
timeScale: tl.isActive() ? 0 : 1, // smooth ease to pause/play
})
})
使用 invalidate 的尺寸调整处理程序
window.addEventListener('resize', () => {
cw = canvas.width = innerWidth
ch = canvas.height = innerHeight
radius = Math.max(cw, ch)
tl.invalidate() // recalculates functional from-values on next render
})
invalidate() 会强制 GSAP 使用更新后的尺寸重新计算基于函数的值(即三角函数计算)。
4. 性能提示
| 提示 | 原因 |
|---|---|
使用 gsap.ticker.add(fn) 执行渲染循环 | 与 GSAP 内部的 rAF 同步——共用一个帧预算,避免重复绘制 |
| 尽量减少状态更改 | 批量调用 translate/rotate;每个粒子只调用一次 resetTransform() |
| 谨慎排序 | 每帧调用 particles.sort() 的复杂度为 O(n log n)——如果 z 顺序固定,就跳过 |
在 <canvas> 上使用 will-change: transform | 提升至 GPU 图层,降低合成开销 |
优先使用 ctx.resetTransform(),而不是 save/restore | 速度更快——避免栈推入/弹出的开销 |
| 预渲染到离屏 canvas | 对于静态精灵图,只需绘制一次到 OffscreenCanvas,之后从中调用 drawImage |
参考资料
references/canvas-patterns.md— 完整实现:粒子绕行系统、canvas 形变(将 MorphSVG 渲染到 canvas)
评论
支持 Markdown