news 2026/9/16 17:29:57

tsParticles 自定义 Shape 插件开发指南:模板 Shape 的加载、注册与绘制原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
tsParticles 自定义 Shape 插件开发指南:模板 Shape 的加载、注册与绘制原理

tsParticles 自定义 Shape 插件开发指南:模板 Shape 的加载、注册与绘制原理

【免费下载链接】tsparticlestsParticles - Easily create highly customizable JavaScript particles effects, confetti explosions and fireworks animations and use them as animated backgrounds for your website. Ready to use components available for React.js, Vue.js (2.x and 3.x), Angular, Svelte, jQuery, Preact, Inferno, Solid, Riot and Web Components.项目地址: https://gitcode.com/GitHub_Trending/ts/tsparticles

导读

tsParticles 允许开发者通过插件机制注册任意自定义粒子形状(Shape),从而在particles.shape.type中直接使用自己的图形。本文以 cli/commands/create/files/create-shape/README.md(tsparticles-shape-template模板 Shape 文档)为骨架,完整讲解 CDN、ESM、CommonJS 三种接入方式,并深入到引擎源码,剖析IShapeDrawer接口、ShapeDrawer绘制约定、渲染管线与addShape注册机制,最后以 heart 形状插件的真实实现为参照,给出从模板到正式插件的完整开发路径。读完本文,你将掌握编写、加载并调试一个 tsParticles 自定义形状插件的完整技术方案。

模板 Shape 是什么:一份可直接复用的自定义形状脚手架

tsparticles-shape-template是 tsParticles 提供的"模板形状"包,它的作用不是提供一个具体的图形,而是给开发者一份最小可运行的自定义 Shape 项目骨架:把其中的#template#占位符替换成你的形状名,在draw方法里写下绘制代码,即可得到一个完整的形状插件。

模板项目的源码结构非常精简,共 4 个 TypeScript 文件(见 cli/commands/create/files/create-shape/src):

文件职责
ShapeDrawer.ts实现引擎的IShapeDrawer接口,定义形状类型名与draw绘制逻辑
index.ts导出异步加载函数loadTemplateShape(engine),将绘制器注册进引擎
index.lazy.ts懒加载(动态import)版本的加载函数,适合按需拆包
browser.ts浏览器全局版入口,把loadTemplateShape挂到globalThis

在 index.ts 中,加载函数的实现只有三行核心逻辑:

export async function loadTemplateShape(engine: Engine): Promise<void> { await engine.addShape(new ShapeDrawer()); }

这段代码是脚手架的最简示意:它说明了一个形状插件的最小注册动作。需要留意的是,引擎真正的addShapeAPI 签名是addShape(shapes: string[], drawer: ShapeInitializer)(见下文"注册机制"一节),正式插件需要把形状名称数组与初始化器一并传入——heart 插件的写法就是标准范式。

方式一:CDN / Vanilla JS / jQuery 快速接入

如果项目通过<script>标签引入脚本(Vanilla JS 或 jQuery 场景),只需要一个文件:tsparticles.shape.template.min.js。该文件加载完成后会导出一个全局函数:

loadTemplateShape

其全局挂载逻辑见 browser.ts:文件将loadTemplateShape写入globalThis(并顺带初始化了__tsParticlesInternals内部命名空间),同时通过export *保留模块化导出能力。

页面引入并调用加载函数后,即可初始化 tsParticles 实例并使用该形状:

(async () => { await loadTemplateShape(tsParticles); await tsParticles.load({ id: "tsparticles", options: { /* options */ /* here you can use particles.shape.type: "template" */ }, }); })();

代码要点:

  • loadTemplateShape(tsParticles)必须在tsParticles.load(...)之前await完成,因为形状需要先注册进引擎,后续创建的实例才能解析shape.type
  • id: "tsparticles"对应页面上承载动画的容器元素 id,与 Vue、React 等框架包装器最终渲染出的 canvas 容器命名规则一致;
  • options.particles.shape.type填写模板中声明的形状类型名(模板占位为#template#,README 注释中以template示意,实际应替换为你自己的形状标识,见下文"validTypes 与 shape.type 的对应关系")。

方式二:ESM / CommonJS 模块化接入

模板包同时兼容 ES Module 与 CommonJS。首先安装依赖:

$ npm install tsparticles-shape-template

或使用 Yarn:

$ yarn add tsparticles-shape-template

CommonJS

const { tsParticles } = require("@tsparticles/engine"); const { loadTemplateShape } = require("tsparticles-shape-template"); (async () => { await loadTemplateShape(tsParticles); })();

ES Module

import { tsParticles } from "@tsparticles/engine"; import { loadTemplateShape } from "tsparticles-shape-template"; (async () => { await loadTemplateShape(tsParticles); })();

两条路线都从@tsparticles/engine取出单例tsParticles引擎,再把模板包导出的加载函数注入其中。加载函数内部会实例化ShapeDrawer并完成注册(见 index.ts)。如果你的打包体积敏感,还可以改用 index.lazy.ts 的懒加载版本——它通过await import("./ShapeDrawer.js")动态引入绘制器,让形状代码进入独立 chunk、按需加载。

validTypes 与 shape.type 的对应关系

模板 ShapeDrawer.ts 中通过validTypes声明了形状的类型标识:

readonly validTypes = ["#template#"] as const;

引擎解析粒子配置时,会用particles.shape.type的值去匹配各绘制器的validTypes,命中即调用该绘制器的draw完成渲染。因此:

  • README 注释中的"template"属于示意写法,真正使用时,shape.type必须与validTypes中声明的字符串完全一致
  • 开发自定义形状时,第一步就是把#template#替换成你自己的类型名(如"heart""star"),并在配置里使用同名标识;
  • validTypes是一个字符串数组,意味着一个绘制器可以同时响应多个形状类型名,例如同时声明["hexagon", "hex"]让两个别名共享同一绘制逻辑。

源码剖析一:draw 方法的绘制约定

模板 ShapeDrawer.ts 的draw方法体留空,但注释给出了编写任何形状都必须遵守的四条核心约定

  1. context 已居中:canvas 上下文已经被平移(setTransform)到粒子中心,绘制时以(0, 0)为图形中心即可,无需手动计算粒子坐标;
  2. 绘制边界为-radiusradius:粒子的radius(缩放后的drawRadius)就是绘制坐标系中的半边长,超出边界的部分可能被裁剪;
  3. delta是帧间时间差(毫秒):实现动态形状(如旋转、呼吸缩放)时,用它乘上速度系数,动画速率就不会随帧率漂移;
  4. pixelRatio是实例的像素比:在高 DPI(Retina)屏幕上需要按它缩放线条宽度或绘制密度,避免图形发虚。

此外,模板刻意把用不到的形参加了下划线前缀(如_data),这是为了通过 lint 检查;正式开发时按需去下划线并解构使用。

源码剖析二:IShapeDrawer 的完整生命周期钩子

模板只实现了draw,但引擎为形状绘制器预留了完整的生命周期接口(见 engine/src/Core/Interfaces/IShapeDrawer.ts),按需实现即可增强形状能力:

钩子触发时机典型用途
draw(data)每帧绘制粒子时必选,绘制形状路径
afterDraw(data)形状路径绘制完成后追加描边、发光等后处理
beforeDraw(data)绘制形状路径之前设置渐变、阴影等前置状态
init(container)形状随容器初始化时异步加载图片/字体等资源
loadShape(particle)粒子使用该形状时解析形状专属的 particle 选项
particleInit(container, particle)粒子初始化时为粒子准备绘制缓存
particleDestroy(particle)粒子销毁时释放该粒子的资源
getSidesCount(particle)引擎需要边数时polygon类算法与角度计算使用
isInsideCanvas(data)边界检查时自定义出界判定,优化剔除
destroy(container)容器销毁时释放全局资源

源码剖析三:IShapeDrawData 的数据结构

draw收到的data是引擎统一构造的IShapeDrawData(见 engine/src/Core/Interfaces/IShapeDrawData.ts),常用字段如下:

  • contextOffscreenCanvasRenderingContext2D,实际绘制上下文;
  • radius/drawRadius:粒子原始半径与缩放后的半径,绘制时通常以drawRadius为基准;
  • drawPosition/position:缩放后的绘制坐标与粒子原始坐标;
  • delta{ value, factor }形式的帧间差,用于时间驱动的动画;
  • pixelRatio:设备像素比,用于高 DPI 适配;
  • fill/stroke:该粒子是否启用了填充/描边(由shape.fillparticles.shape.stroke等配置决定);
  • opacity:粒子当前透明度;
  • particle:当前粒子对象,可读取其属性、选项甚至变换数据;
  • transformData{ a, b, c, d }变换矩阵分量,分别表示水平缩放、水平斜切、垂直斜切、垂直缩放。

在 RenderManager.ts 中可以确认,引擎每帧为粒子组装出这样一份数据,然后交给绘制器链处理。

源码剖析四:引擎的渲染管线与注册机制

渲染管线

引擎在RenderManager#drawParticle中完成一次粒子绘制的完整编排(见 engine/src/Core/RenderManager.ts):

  1. 根据粒子的effectshape从绘制器表取出对应的 effect 绘制器与 shape 绘制器;
  2. context.setTransform(...)将坐标系原点平移到粒子中心并应用旋转/缩放;
  3. 依次调用#drawBeforeEffect#drawShapeBeforeDraw#drawShape#drawShapeAfterDraw#drawAfterEffect
  4. #drawShape(RenderManager.ts)内部的固定流程为:context.beginPath()→ 调用你的drawer.draw(data)→ 若particle.shapeCloseclosePath()→ 若fillcontext.fill()→ 若strokecontext.stroke()

这解释了为什么模板注释要求"只画路径、颜色已处理":填充色、描边色、透明度与变换全部由引擎统一完成,你的draw只需要用moveTolineToquadraticCurveToarc等 API 勾勒出路径。

addShape 注册机制

形状注册最终落在PluginManager.addShape(见 engine/src/Core/Utils/PluginManager.ts):它会以形状名为 key,把"初始化器"存入initializers.shapes映射。当某个容器首次需要绘制时,getShapeDrawers(PluginManager.ts)通过getItemMapFromInitializer(engine/src/Utils/Utils.ts)惰性地为每个容器实例化各自的绘制器表,并按容器缓存。也就是说:addShape是全局注册、影响所有后续实例,而绘制器实例是按容器懒创建的。

参考实现:heart 形状插件的真实写法

仓库 shapes/heart 是官方形状中结构最贴近模板的参考实现。它的加载函数展示了标准注册范式(shapes/heart/src/index.ts):

export async function loadHeartShape(engine: Engine): Promise<void> { engine.checkVersion(__VERSION__); await engine.pluginManager.register(e => { e.pluginManager.addShape(["heart"], () => Promise.resolve(new HeartDrawer())); }); }

对比可见:正式插件通过pluginManager.register注册回调,用addShape(["heart"], () => Promise.resolve(new HeartDrawer()))显式传入类型名数组与返回绘制器的初始化器,这正是引擎 API 的真实签名(模板中的一行式写法是其最简示意)。

绘制器本体(shapes/heart/src/HeartDrawer.ts)只实现了draw,真正的路径绘制被抽到工具函数drawHeart(shapes/heart/src/Utils.ts)里——它用 6 段quadraticCurveTo贝塞尔曲线,在-radiusradius的局部坐标系中画出心形路径,context已经居中,因此所有坐标都相对(0, 0)。把drawHeart的函数体替换成你自己的路径代码,就是一个可用的自定义形状。

从模板到正式插件:自定义形状的完整开发路径

基于上述源码依据,把模板改造成正式形状插件只需四步:

  1. 命名:把ShapeDrawer类名与validTypes中的#template#换成你的形状名(如StarDrawer/"star");
  2. 绘制:在draw(data)中解构出contextdrawRadius等字段,用 Canvas 路径 API 以(0,0)为中心、-drawRadius ~ drawRadius为边界绘制路径;需要动画就读取delta按时间驱动;
  3. 注册:按 heart 的标准写法pluginManager.register+addShape([类型名], () => Promise.resolve(new Drawer())),或用懒加载版index.lazy.ts拆分代码;
  4. 使用await loadYourShape(tsParticles)之后,在配置里把particles.shape.type设为你的类型名。

如果不想手工拷贝模板,仓库提供了 CLI 脚手架:在 cli/README.md 的 Create 一节可以看到shape子命令:

npx @tsparticles/cli-create shape <folder> # 或全局安装后 tsparticles-create shape <folder>

该命令的实现在 cli/commands/create-shape/src/shape.ts:通过交互式提问收集项目信息(名称、描述、仓库地址等),再用createProjectTemplate({ kind: "shape", ... })生成cli/commands/create/files/create-shape这套模板。CLI 的命令编排见 cli/commands/create/src/create.ts,shapeCreateCommand与 app、preset、plugin 等子命令一起挂载在tsparticles-create下。

脚手架生成的包遵循tsparticles-shape-<name>的命名规则,这在 CLI 的测试用例中有明确断言(见 cli/commands/create/tests/create-shape.test.ts 与 cli/commands/create/tests/create-shape.test.ts),例如tsparticles-shape-footsparticles-shape-bar

总结

模板 Shape 文档虽短,却覆盖了自定义形状从"加载"到"使用"的全部接入姿势:CDN 场景用tsparticles.shape.template.min.js暴露的全局loadTemplateShape,模块化场景用npm install+import/require注入@tsparticles/engine的单例,最终在particles.shape.type中启用形状。结合引擎源码可以看到,这一切背后是IShapeDrawer生命周期接口、以-radius ~ radius局部坐标系为核心的draw约定、RenderManager的固定渲染管线,以及PluginManager.addShape的全局注册 + 容器级懒实例化机制。以 shapes/heart 为范本,替换类型名、实现路径绘制、按标准范式注册,即可将模板快速升级为你的专属形状插件。

【免费下载链接】tsparticlestsParticles - Easily create highly customizable JavaScript particles effects, confetti explosions and fireworks animations and use them as animated backgrounds for your website. Ready to use components available for React.js, Vue.js (2.x and 3.x), Angular, Svelte, jQuery, Preact, Inferno, Solid, Riot and Web Components.项目地址: https://gitcode.com/GitHub_Trending/ts/tsparticles

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 17:29:48

北京屈光参差就诊指南 这些权威医生可以参考

屈光参差指双眼屈光度差异超过250度的特殊屈光状态&#xff0c;它带来的不只是"两只眼睛度数不一样"&#xff0c;更会直接破坏双眼影像融合机制&#xff0c;导致视物重影、深度感丧失、3D视觉无法建立&#xff0c;长期还可能诱发单眼抑制和弱视。普通眼镜店常规验光往…

作者头像 李华
网站建设 2026/9/16 17:27:54

LLM系统提示词泄露风险与六层防御实战指南

1. 这不是“泄露”&#xff0c;而是模型训练与部署中被长期忽视的提示词暴露风险最近在几个技术社区里&#xff0c;频繁看到有人发帖问&#xff1a;“为什么我微调后的模型一上线&#xff0c;别人就能猜出我的system prompt&#xff1f;”、“API返回里怎么带出了内部指令&…

作者头像 李华
网站建设 2026/9/16 17:26:17

用Vue构建心理咨询系统:路由、状态管理与部署实战

简介&#xff1a;这是一份基于Vue的大学生心理咨询系统毕业设计项目&#xff0c;面向高校软件技术、计算机等专业学生&#xff0c;适用于毕业设计、课程设计或前端综合实训。压缩包共一百六十九个文件&#xff0c;以四十二个Vue页面组件和七十一个JavaScript逻辑文件为主&#…

作者头像 李华
网站建设 2026/9/16 17:25:55

五分钟从八大网盘拿到直链:网盘直链下载助手使用指南

五分钟从八大网盘拿到直链&#xff1a;网盘直链下载助手使用指南 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云…

作者头像 李华