第七集:把 Canvas 塞进 React/Vue,前端框架不打架
113
0
0
“我把 Canvas 写进 useEffect,结果热更新一次,画布被重构得比我还惨。”
—— 某位被 React 刷新机制按在地上摩擦的前端小可爱
大家好,欢迎来到《Canvas 翻车指南》第七集!
前面我们让十万粒子在 WebGL 里蹦迪,但今天上“生产环境”——React & Vue。
- Hook + Ref:生命周期不对?画布瞬间闪崩
- 自定义指令 / Hook:封装“粒子组件”,props 即配置
- Vite 热更新:保存状态不丢 WebGL 上下文,开发体验拉满
- 双端兼容:React 的 Concurrent、Vue 的 KeepAlive,统统拿捏
准备好枸杞泡水,咱们把 Canvas 组件化,让框架乖乖当小弟!
一、React 篇:useRef 与 useEffect 的黄金搭档
1. 初始化只跑一次
const CanvasParticles: React.FC = () => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const glRef = useRef<WebGLRenderingContext | null>(null);
const rafRef = useRef<number>(0);
useEffect(() => {
const canvas = canvasRef.current!;
const gl = canvas.getContext('webgl')!;
glRef.current = gl;
initWebGL(gl); // 编译着色器、上传缓冲区
const loop = () => {
render(gl);
rafRef.current = requestAnimationFrame(loop);
};
loop();
return () => {
cancelAnimationFrame(rafRef.current);
// 清理 WebGL 资源,防止内存泄漏
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, []); // 空依赖 = 挂载时初始化
return <canvas ref={canvasRef} width={800} height={600} />;
};口诀:
“Ref 存对象,Effect 管生死,依赖数组别乱写,否则重载两行泪。”
2. props 响应式更新 —— useImperativeHandle + forwardRef
想让父组件动态改粒子重力?
export const ParticleCanvas = forwardRef<ParticleHandle, Props>(
({ gravity }, ref) => {
useImperativeHandle(ref, () => ({
setGravity(g: number) {
worker.postMessage({ type: 'setGravity', payload: g });
},
}));
...
}
);父组件调用:
const ctrlRef = useRef<ParticleHandle>(null);
<ParticleCanvas ref={ctrlRef} gravity={0.2} />
<button onClick={() => ctrlRef.current?.setGravity(0.5)}>加重力</button>不强制重新挂载,WebGL 上下文稳稳当当。
二、Vue 篇:自定义指令 + composable 双剑合璧
1. 自定义指令 v-particle
// main.ts
app.directive('particle', {
mounted(el, binding) {
const { count = 1e4, gravity = 0.1 } = binding.value;
const gl = el.getContext('webgl');
initParticleSystem(gl, { count, gravity });
},
unmounted(el) {
cleanup(el);
}
});使用:
<canvas v-particle="{ count: 15000, gravity: 0.2 }" width="800" height="600" />模板即配置,像用 v-model 一样简单。
2. Composable 封装逻辑
// useParticle.ts
export function useParticle(canvasRef: Ref<HTMLCanvasElement | null>) {
let raf = 0;
onMounted(() => {
const gl = canvasRef.value!.getContext('webgl');
startLoop(gl);
});
onUnmounted(() => cancelAnimationFrame(raf));
return { pause: () => ..., resume: () => ... };
}组件内:
<template>
<canvas ref="canvasEl" width="800" height="600" />
</template>
<script setup lang="ts">
import { useParticle } from '@/composables/useParticle';
const canvasEl = ref<HTMLCanvasElement>();
const { pause, resume } = useParticle(canvasEl);
</script>响应式暂停 / 继续,和 Vue 生命周期同步。
三、热更新守护:Vite + WebGL 上下文不丢失
痛点:
- 修改一行颜色,整页刷新,十万粒子回到原点
- WebGL 上下文重建,显存炸出双峰
解决:
- 把粒子状态放 Worker 内存,与主线程分离
- 使用
import.meta.hot手动 accept
if (import.meta.hot) {
import.meta.hot.accept(() => {
// 只重编译着色器,不重建 Worker
worker.postMessage({ type: 'hotShader', shaderSource: newSource });
});
}- Vue 3 的
<KeepAlive>+include缓存画布组件 - React 用
key = stableId,避免父级重排导致卸载
口诀:
“热更新像换内裤,别让 WebGL 一起裸奔。”
四、Concurrent & KeepAlive:双框架兼容坑
React 18 Concurrent:
- 组件会被中断 / 恢复,WebGL 渲染要幂等
- 用
useDeferredValue把高耗任务拆帧:
const deferredCount = useDeferredValue(particleCount);
useEffect(() => {
worker.postMessage({ type: 'setCount', payload: deferredCount });
}, [deferredCount]);Vue 3 KeepAlive:
- 切走缓存时
deactivated触发,记得暂停 RAF - 回来
activated再重启,防止后台偷跑 GPU
onActivated(() => raf = requestAnimationFrame(loop));
onDeactivated(() => cancelAnimationFrame(raf));五、体积优化:Tree-Shaking 与异步 Worker
- Worker 代码放
new URL('./worker.ts', import.meta.url),Vite 自动拆包 - WebGL 着色器写
*.glsl文件,?raw导入,享受语法高亮 - 粒子库默认导出工厂函数,未用到的渲染器被 Tree-Shake 掉,打包体积 < 60 KB (gzip)
六、TypeScript 类型小贴士
export interface ParticleProps {
count?: number;
gravity?: number;
onFrame?: (fps: number) => void;
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$particle: ParticlePlugin;
}
}组件库即插即用,IDE 提示拉满,同事再也不敢乱传字符串。
七、本集知识点一口闷
| 技能 | 口诀 | 翻车点 |
|---|---|---|
| useRef + [] | 只 mount 一次 | 依赖写错反复重建 |
| imperativeHandle | 父调子方法 | ref 没 forward 全白搭 |
| v-particle | 指令即配置 | unmounted 忘清理内存 |
| hot.accept | 热更着色器 | 状态放 Worker 否则全丢 |
| KeepAlive | 切页缓存 | 后台 RAF 不停风扇起飞 |
八、课后作业(翻车 field)
封装 NPM 包:
- 名
@your/canvas-particle - 支持 React/Vue 双入口,Tree-Shaking 着色器
- 名
加控制台面板:
- 滑条调
count / gravity / size,实时生效
- 滑条调
- Bonus:Storybook + HMR,修改 props 画布不闪,录屏晒 NPM 下载量
九、大结局预告(第八集·终章)
第八集《实战:画一只猫,结果画成猪?——项目复盘》
带你完整跑一遍:
- 需求 → 原型 → 技术选型 → 踩坑 → 性能优化 → 上线
- 把前面七集技能串成通关连招,交付给老板一只“会扭屁股”的 3D 猪猫
- 开源《Canvas 翻车指南》仓库,Star 破千直播“用 Canvas 画 KPI 曲线,画不好扣工资”!
“人生就像把 Canvas 塞进框架,
你不给 Ref,
就只能被生命周期反复重构,连狗都嫌。”
—— 我说的。
第七集结束,点赞、转发、晒 NPM 包,
终章一起画只会扭屁股的猪猫,回见!
0
快来点个赞吧
发表评论
评论列表