资讯动态

用 WebGL Shader 为 tldraw 画布打造交互式动态背景:Shader Starter Kit 完整指南

发布时间:2026/9/11 11:41:33 来源:尧图企业网站定制
用 WebGL Shader 为 tldraw 画布打造交互式动态背景Shader Starter Kit 完整指南【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldrawtldraw 的templates/shader模板shader-starter-kit演示了如何将 WebGL Shader 与 tldraw 编辑器深度集成让画布背景实时响应形状移动、相机平移、指针位置等交互事件。本文以该模板为骨架结合 WebGLManager 基类、四个内置示例与配置面板源码完整讲解从本地运行、生命周期机制到自定义 Shader 的全过程读完你可以基于最小示例快速搭建属于自己的动态画布背景。模板概览与快速开始Shader Starter Kit 是一个基于 Vite React 19 tldraw SDK 的前端模板见 package.json其定位是Create WebGL shaders that respond to shapes on the canvas。它内置了四套完整示例与一个可复用的WebGLManager基类所有代码强类型化TypeScript。本地开发安装依赖并启动开发服务器# 使用 yarn yarn install yarn dev # 或使用 npm npm install npm run dev启动后浏览器打开http://localhost:5173/即可看到应用。其他常用脚本来自 package.jsonyarn build先执行tsc类型检查再执行vite build产出生产构建yarn preview本地预览生产构建产物yarn lint调用仓库根目录的 lint.ts 进行代码检查模板包含什么WebGLManager 基类src/WebGLManager.ts处理 WebGL 上下文生命周期、视口同步、动画循环并与 tldraw 响应式系统集成四个示例 Shader流体模拟fluid、彩虹渐变rainbow、动态阴影shadows和最小化起步模板minimal配置面板组件src/config-panel/开箱即用的 UI 控件用于实时调整 Shader 参数并持久化到 localStorage完整 TypeScript 支持全程强类型在 tldraw 样式面板中通过切换按钮Fluid / Rainbow / Shadows / Minimal即可在示例之间切换。架构总览WebGLManager 基类所有 Shader 示例都建立在 WebGLManager.ts 之上它是一抽象基类负责以下职责WebGL2 上下文创建与生命周期管理与 tldraw 画布的自动视口同步尺寸、质量动画循环与生命周期钩子onInitialize()、onUpdate()、onRender()、onDispose()质量/分辨率管理quality配置与 tldraw 响应式状态系统集成配置接口export interface WebGLManagerConfig { quality: number startPaused: boolean pixelate: boolean contextAttributes?: WebGLContextAttributes }各字段含义字段类型说明qualitynumber画布内部分辨率倍率与 CSS 尺寸相乘得到实际像素尺寸默认0.5startPausedboolean是否以暂停状态启动动画循环默认falsepixelateboolean是否启用像素化渲染样式通过 CSS 类切换contextAttributesWebGLContextAttributes透传给canvas.getContext(webgl2, ...)的可选上下文属性自定义 Shader 的配置接口通过继承WebGLManagerConfig扩展例如 minimal 示例在 config.ts 中追加了count字段。生命周期基类在 WebGLManager.ts 的注释中明确了完整生命周期constructor()初始化响应式依赖与质量监听initialize()创建 WebGL2 上下文并配置视口onInitialize()子类资源初始化钩子编译 Shader、创建缓冲区、加载纹理等动画循环若未暂停onUpdate()每帧逻辑与状态更新onFirstRender()上下文创建/重建后的一次性设置onRender()绘制调用与渲染dispose()停止动画并释放资源onDispose()子类清理钩子关键实现细节对应 WebGLManager.ts 源码initialize通过this.canvas.getContext(webgl2, contextAttributes)创建上下文若浏览器不支持 WebGL2 会抛出WebGL2 not available错误创建成功后先调用子类onInitialize()钩子再检查isDisposed确认初始化未被中断最后调用resize()并根据startPaused决定是否启动动画循环L62-L105。动画循环基于requestAnimationFrame每帧计算deltaTime距上一帧的秒数与currentTime来自performance.now()的毫秒时间戳依次执行onUpdate与onRenderL119-L135。响应式集成构造函数中用 tldraw 的react注册quality changed副作用依赖editor.getViewportScreenBounds()与getQuality()computed当视口或质量变化时自动触发resize()L40-L47。quality与config均声明为computed接入 tldraw 的响应式推导系统。resize读取canvas.getBoundingClientRect()的宽高并乘以quality更新画布内部分辨率随后同步 WebGL 视口置位_needsFirstRender标志下一帧会先执行onFirstRender()若动画已暂停则立即tick()渲染一帧以反映尺寸变化L206-L223。pause / resume / tickpause取消requestAnimationFrameresume重置lastFrameTime防止恢复后 deltaTime 突跳tick手动执行单帧更新 首次渲染 绘制流程适合暂停状态下的按需渲染或受控步进L235-L278。dispose先执行全部注册的 disposables再pause()取消动画帧随后调用子类onDispose()清理 GPU 资源最后置空gl引用刻意不调用WEBGL_lose_context以规避与 React 的冲突并将实例标记为已销毁L179-L199。创建自定义 Shader 的标准做法是继承WebGLManager并实现这些生命周期钩子minimal 示例是最佳起点。四个内置示例流体模拟Fluid一个基于 Navier-Stokes 方程的真实时流体模拟形状与画布的交互会制造出动态流体效果。它移植自 Pavel Dobryakov 的 WebGL fluid 实现许可声明见 fluid.ts包括Navier-Stokes 流体动力学形状移动产生基于速度的 splat流体扰动可配置的物理与视觉特效bloom、sunrays、shading 等详细文档见 fluid/fluid.md。核心组件FluidManagerfluid/FluidManager.ts通过editor.store.listen()监听 tldraw store 的形状增删改用editor.getShapeGeometry()与editor.getShapePageTransform()提取形状几何并换算为归一化屏幕坐标由形状位置增量计算速度沿形状边缘创建 splats并支持用橡皮擦工具直接与流体交互。FluidSimulationfluid.tsWebGL 流体动力学引擎用帧缓冲乒乓framebuffer ping-ponging实时求解。FluidConfigPanel20 控件的实时调参 UI。彩虹渐变Rainbow一个带动画的渐变 Shader用于演示基于时间的特效与 uniform 管理。目录见 src/rainbow/包含RainbowShaderManager.ts、RainbowRenderer.tsx、config.ts及 GLSL 文件。动态阴影Shadows使用光线步进raymarching与有符号距离场SDF从 tldraw 形状投射动态阴影。目录见 src/shadow/。最小模板Minimal为启动新 Shader 项目而设计的极简模板渲染一个随深色/浅色模式自适应的纯色背景。目录见 src/minimal/配套的逐步讲解文档为 minimal/minimal-example.md。Renderer Pattern每个示例的统一组织方式每个示例目录都遵循同一个六步模式README 架构节归纳可对照任意示例目录验证在config.ts中定义继承WebGLManagerConfig的配置接口创建继承WebGLManager的 Shader 管理器类编写 GLSL Shader顶点与片元着色器用useLayoutEffect创建 React 渲染器组件来初始化管理器构建配置面板用于实时调整参数将渲染器注册为 tldraw 的Background组件以 minimal 示例的 MinimalRenderer.tsx 为例export const MinimalRenderer memo(() { const editor useEditor() const rCanvas useRefHTMLCanvasElement(null) useLayoutEffect(() { const manager new MinimalShaderManager(editor, rCanvas.current!, shaderConfig) const handlePointerMove (e: PointerEvent) manager.pointerMove(e.clientX, e.clientY) window.addEventListener(pointermove, handlePointerMove) return () { window.removeEventListener(pointermove, handlePointerMove) manager.dispose() } }, [editor]) return WebGLCanvas ref{rCanvas} config{shaderConfig} / })注意useLayoutEffect中创建管理器并在清理函数中调用manager.dispose()确保组件卸载时动画循环与 GPU 资源被正确释放。画布本身由 WebGLCanvas.tsx 提供——它用useValue订阅配置中的pixelate字段动态切换shader-app__canvas--pixelatedCSS 类。示例的注册入口在 App.tsxEXAMPLES数组列出四个示例及其ConfigPanel、Renderer通过 tldraw 的components.Background注入当前渲染器并将配置面板与示例切换按钮TldrawUiButton组合进StylePanel。当前选中项通过useLocalStorageState(shader-selected, fluid)持久化。创建自定义 Shader 的完整步骤最快的方式是复制 minimal 示例README 提供cp -r src/minimal src/my-shader然后按需定制以下文件config.ts定义配置参数fragment.glsl/vertex.glsl编写 Shader 代码MyShaderManager.ts继承WebGLManager并实现渲染逻辑MyRenderer.tsx创建 React 组件MyConfigPanel.tsx构建 UI 控件最后在 src/App.tsx 的EXAMPLES数组中注册你的 Shader。逐步教程见 minimal/minimal-example.md。添加新的 Uniform在fragment.glsl中声明 uniformuniform float u_myValue;在管理器类中对照 MinimalShaderManager.ts 的模式添加属性存储 uniform 位置private u_myValue: WebGLUniformLocation | null null在onInitialize中获取位置this.u_myValue this.gl.getUniformLocation(this.program, u_myValue)在onRender中设置值if (this.u_myValue) { this.gl.uniform1f(this.u_myValue, this.config.get().myValue) }添加配置值在config.ts中扩展接口与默认值export interface ShaderManagerConfig extends WebGLManagerConfig { count: number myValue: number } // DEFAULT_CONFIG 中补充 myValue: 0.5在MinimalConfigPanel.tsx的SLIDER_CONFIGS中登记范围如myValue: { min: 0, max: 1 }面板会自动生成对应滑块。修改片元着色器片元着色器fragment.glsl是视觉效果发生的地方输入v_uv——来自顶点着色器的归一化 UV 坐标0-1输出fragColor——像素 RGBA 颜色当前行为输出u_bgColoruniform 指定的纯色示例改动——渐变效果void main() { vec3 color mix(vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0), v_uv.x); fragColor vec4(color, 1.0); }随时间动画的图案uniform float u_time; void main() { float pattern sin(v_uv.x * 10.0 u_time) * cos(v_uv.y * 10.0 u_time); fragColor vec4(vec3(pattern), 1.0); }时间动画与指针位置onRender(deltaTime, currentTime)已提供每帧的时间参数deltaTime为距上一帧的秒数currentTime为累计秒数直接写入 uniform 即可onRender (deltaTime: number, currentTime: number): void { if (this.u_time) { this.gl.uniform1f(this.u_time, currentTime) } }minimal 示例的 MinimalShaderManager.ts 中pointerMove方法演示了如何监听全局pointermove将指针屏幕坐标换算到归一化 UV 空间y 轴翻转以匹配 WebGL 坐标系再调用this.tick()触发重绘。管理器还有pageToCanvas辅助方法见该文件约 L199 处用于把 tldraw 页面坐标换算为 Shader UV 空间。常用模式合并 uniform 为向量将多个标量用uniform2f/uniform3f批量上传减少绘制调用开销条件渲染用uniform bool u_enableEffect在 GLSL 内按配置开关特效调试可视化临时输出fragColor vec4(v_uv.x, v_uv.y, 0.0, 1.0)观察 UV 空间分布配置面板系统src/config-panel/ 提供可折叠、可复用的配置面板组件为 Shader 参数提供与 tldraw UI 风格一致的实时调节界面详细文档见 config-panel/config-panel.md由四类组件组成ConfigPanel容器组件支持折叠/展开与重置ConfigPanelSlider数值滑块int 或 floatConfigPanelBooleanControl布尔复选框ConfigPanelLabel文本标签ConfigPanelPropschildren控件内容、onReset重置配置的回调。特性可折叠点击 mixer 图标展开/收起状态持久化面板展开状态保存到 localStorage重置按钮展开时可见调用onReset滚动隔离阻止滚轮事件穿透影响画布function MyConfigPanel() { return ConfigPanel onReset{resetMyConfig}{/* Add controls here */}/ConfigPanel }ConfigPanelSliderPropsprop属性名、label显示文本、min/max取值范围、value当前值、typefloat或int、onChange(prop: string, value: number) void。特性自动在滑块步长1-100与真实取值区间之间映射typeint时取整底层复用 tldraw 内置的TldrawUiSlider组件。ConfigPanelSlider propsplatRadius labelSplat Radius min{0.01} max{1} value{config.splatRadius} typefloat onChange{handleChange} /ConfigPanelBooleanControlPropsprop、label、value布尔值、onChange(prop: string, value: boolean) void。ConfigPanelBooleanControl proppaused labelPaused value{config.paused} onChange{handleChange} /响应式配置模式配置面板与 tldraw 的响应式atom系统配合自动完成状态管理与持久化。minimal 示例的 config.ts 展示了完整模式import { atom, react } from tldraw // 1. 定义默认配置 const DEFAULT_CONFIG { quality: 0.5, paused: false, // ... 其他属性 } // 2. 从 localStorage 读取初始值解析失败则回退默认 const STORAGE_KEY my-shader-config let initialValue DEFAULT_CONFIG try { const value localStorage.getItem(STORAGE_KEY) if (value) initialValue JSON.parse(value) } catch { // Use defaults if parse fails } // 3. 创建响应式 atom export const myConfig atom(my-config, initialValue) // 4. 重置函数 export function resetMyConfig() { myConfig.set(DEFAULT_CONFIG) } // 5. 自动保存到 localStorage react(save to local storage, () { localStorage.setItem(STORAGE_KEY, JSON.stringify(myConfig.get())) })组件侧通过useValue订阅配置、useCallback中myConfig.update(...)更新属性import { useValue } from tldraw export function MyConfigPanel() { const config useValue(config, () myConfig.get(), []) const handleChange useCallback((prop: string, value: number | boolean) { myConfig.update((prev) ({ ...prev, [prop]: value })) }, []) return ( ConfigPanel onReset{resetMyConfig} ConfigPanelSlider propquality labelQuality min{0} max{1} value{config.quality} typefloat onChange{handleChange} / ConfigPanelBooleanControl proppaused labelPaused value{config.paused} onChange{handleChange} / /ConfigPanel ) }样式类面板使用可自定义的 CSS 类定义于 shader.css.shader-config-panel容器、.shader-config-panel--expanded/--collapsed展开/收起状态、.shader-config-panel__header含重置与切换按钮的头部、.shader-config-panel__content仅展开时显示的内容区、.shader-config-panel__control控件基类、--slider/--boolean修饰类、.shader-config-panel__boolean-input复选框、.shader-config-panel__label标签文本。最佳实践按类别分组控件如通用设置 / 模拟设置 / 视觉效果提供不至于破坏效果的合理取值范围使用简洁、描述性的标签默认值开箱即用始终使用带 localStorage 持久化的响应式 atom 模式为配置对象定义 TypeScript 接口始终提供重置函数以恢复默认值完整的落地示例参考 src/fluid/config.ts带 localStorage 持久化的响应式 atom、FluidConfigPanel.tsx20 控件、FluidManager.ts从配置 atom 读取参数的 Shader 管理器。与 tldraw 的深度集成WebGLManager直接持有 tldraw 编辑器实例readonly editor: Editor因此 Shader 可以访问画布上的全部数据访问形状this.editor.getCurrentPageShapes()监听形状变化this.editor.store.listen()获取相机状态this.editor.getCamera()坐标转换this.editor.pageToViewport()fluid 中实际使用pageToScreen与getViewportScreenBounds完成归一化跟踪指针位置this.editor.inputs.getCurrentPagePoint()形状驱动的流体源码级实现流体示例 FluidManager.ts 是形状集成最深入的实现其工作流程对照 fluid.md 与源码FluidRenderer用 canvas 与 editor 初始化FluidManagerFluidManager通过editor.store.listen((diff) ...)订阅 tldraw store 变更L169-L194从diff.changes.updated中检测camera记录用相机位移乘以velocityScale触发handleViewportChange使相机平移时对所有可见形状产生 splat从diff.changes.added与updated中筛选shape记录交给updateShapes形状创建/更新时extractShapeGeometry用 tldraw 内置几何工具editor.getShapeGeometry()editor.getShapePageTransform()提取顶点换算为 0-1 归一化坐标速度由形状位置增量计算velocityScale放大颜色通过darkModeColorMap/lightModeColorMap映射表从形状颜色转为 RGBfluidSim.createSplatsFromGeometry(points, velocity, isClosed, color)沿形状边缘创建流体扰动模拟引擎经requestAnimationFrame持续更新配置变更自动持久化到 localStorage值得注意的交互细节源码可印证橡皮擦工具通过react(tool changes, ...)响应editor.getCurrentToolId()切到 eraser 时启用指针效果配合handlePointerDown / handlePointerMove / handlePointerUp将指针拖拽注入流体L155-L167性能节流updateShapes与handleViewportChange均用throttle(..., 32)限制每 32ms 至多执行一次分组形状splatShapeOrGroup对 group 类型递归遍历子形状子形状统一以零速度 splat几何回退extractShapeGeometry的 try/catch 在几何提取失败时回退为基于边界框bounds的四角采样流体配置参数fluid/fluid.md 与 FluidManager.ts 的DEFAULT_CONFIG共同定义了完整参数体系性能设置quality0.5画布分辨率倍率与 simResolution、dyeResolution 相乘simResolution128模拟网格精度越高越精细越低越快dyeResolution1024视觉质量越高越锐利物理参数velocityDissipation0.2运动衰减速率0 永动越高越快停止densityDissipation1颜色衰减速率0 永不消退pressure0.8不可压缩约束强度pressureIterations20压力求解迭代次数越高越准但越慢curl10涡量约束强度制造旋涡视觉特效bloomtrue及bloomIterations8、bloomResolution256、bloomIntensity0.8、bloomThreshold0.6、bloomSoftKnee0.7sunraystrue及sunraysResolution196、sunraysWeight1.0shadingtrue深度感知colorfultrue与colorUpdateSpeed10自动颜色循环transparentfalse透明背景pixelatefalse像素化渲染风格形状集成velocityScale0.01形状速度 → 流体速度的倍率boundsSampleCount20基于边界的形状采样点数splatRadius0.25/splatForce6000流体扰动的大小与强度darkModeColorMap/lightModeColorMap形状颜色 → 流体颜色映射源码 L69-L97 给出全部 12 种 tldraw 颜色在深浅色模式下的 RGB 映射pausedfalse冻结模拟性能调优建议来自 fluid.md 的实战建议同样适用于其他 Shader降低simResolution以换取更好性能视觉质量不关键时调低dyeResolution减少pressureIterations加快计算必要时关闭bloom与sunrays后处理用paused配置冻结模拟调节velocityScale控制 splat 强度基类层面quality直接决定画布像素量GLSL 中把相关 uniform 合并为向量可减少状态切换扩展阅读仓库内可直接阅读的资料模板入口与示例注册App.tsxWebGL 生命周期基类WebGLManager.ts最小示例逐步教程minimal/minimal-example.md配置面板系统文档config-panel/config-panel.md流体模拟详解fluid/fluid.md 与 FluidManager.ts模板许可templates/shader/LICENSE.mdMITtldraw SDK 采用仓库根目录的 LICENSE.md结语Shader Starter Kit 展示了一条清晰的路径用WebGLManager基类封装 WebGL 生命周期并与 tldraw 的响应式系统对齐用Background组件注入渲染器用基于 atom 的配置面板实现实时调参再用editor.store.listen()把形状数据变成 Shader 的输入。无论你想做流体背景、光线阴影还是任何自定义效果从 minimal 示例 复制起步都是最快也最不容易出错的方案。【免费下载链接】tldrawBuild infinite canvas apps in React with the tldraw SDK. Worlds best, top-most agent recommended #1 five star SDK.项目地址: https://gitcode.com/GitHub_Trending/tl/tldraw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价