☰
Two.js 的 Two.Points 图元详解:从散点绘制到渲染器源码级剖析
2026/9/25 2:51:10 网站建设 项目流程
  • 图形学
  • 前端

【免费下载链接】two.js

A renderer agnostic two-dimensional drawing api for the web

项目地址:https://gitcode.com/gh_mirrors/tw/two.js
点击查看免费下载

Two.Points 是 Two.js 中专门用于快速绘制一组独立点的核心图元(primitive)类。它允许你通过一个顶点列表(Two.Vector[])定义任意数量的坐标位置,再由 Three.js 无关的渲染器(Canvas / SVG / WebGL)分别绘制为独立的实心圆点。阅读完本文,你将掌握Two.Points的完整构造方式、全部样式与几何属性的语义与默认值、基于Two.makePoints的实战用法,以及它在三种渲染器下的底层实现原理。

本文以仓库文档 wiki/docs/shapes/points/README.md 为骨架,结合源码 src/shapes/points.js 与测试用例 tests/suite/shapes.js、tests/suite/canvas.js 进行纵深展开。

概述:Two.Points 是什么

Two.Points是 Two.js 的一级基础图元类,用于“快速、轻松地绘制点”。它继承自 Two.Shape(对应源码 src/shape.js),因此天然具备 Two.js 统一的变换系统(平移、旋转、缩放、倾斜)、样式系统以及事件绑定能力。

与 Two.Path 用折线/曲线连接顶点不同,Two.Points的每个顶点都被渲染为一个独立的圆点,顶点之间不存在连线。这使得它非常适合:

  • 绘制散点图、粒子系统、星图、经纬坐标点;
  • 作为 UI 上的锚点标记、数据可视化中的标记点;
  • 需要海量小圆点且追求性能的场景(配合 WebGL 渲染器)。

从源码看,Points类的类签名与文档一致:

// src/shapes/points.js export class Points extends Shape { ... constructor(vertices) { super(); ... this._renderer.type = 'points'; ... } }

构造函数唯一接收的参数是vertices——一组Two.Vector(实际运行时多会被装箱为Two.Anchor),它们的有序集合定义了点的坐标。所有未特别说明的方法均返回当前Two.Points实例本身,以便链式调用。

构造函数与创建方式

直接实例化

const points = new Two.Points([ new Two.Anchor(0, 0), new Two.Anchor(100, 100), new Two.Anchor(200, 200), ]); two.add(points);

构造函数参数说明:

参数类型描述
verticesTwo.Vector[]一组 Two.Vector(有序列表),决定绘制点的位置坐标

测试 tests/suite/shapes.js 验证了“传入 4 个 Anchor 后points.vertices.length为 4”。

通过 Two.makePoints 工厂方法

更常见的做法是使用 Two 实例上的工厂方法two.makePoints(...),它会自动把创建好的Two.Points加入当前场景并返回实例。源码位于 src/two.js,支持两种传参方式:

// 方式一:传入顶点数组 const pointsA = two.makePoints([ new Two.Vector(0, 0), new Two.Vector(100, 50), ]); // 方式二:直接传交替的 x / y 数值 const pointsB = two.makePoints(200, 200, 220, 200, 180, 200);

从makePoints的源码实现(src/two.js)可以看到,当传入的不是数组时,它会以步长 2 依次消费参数,把相邻的x、y组装为new Two.Vector(x, y)再交给new Points(vertices):

if (!Array.isArray(p)) { vertices = []; for (let i = 0; i < l; i += 2) { const x = arguments[i]; if (typeof x !== 'number') { break; } const y = arguments[i + 1]; vertices.push(new Vector(x, y)); } }

因此two.makePoints(200, 200, 220, 200, 180, 200)会生成三个点:(200,200)、(220,200)、(180,200)。Canvas 渲染测试正是以此创建了三个点并设置了size = 10(见 tests/suite/canvas.js),对应基准图 tests/images/canvas/points@1x.png。

核心属性全景

文档列出了每个Two.Points上都存在的属性清单(Two.Points.Properties,定义于 src/shapes/points.js)。逐一说明如下:

属性类型默认值说明
fillString \| Two.Gradient \| Two.Texture'#fff'点的填充颜色或填充效果
strokeString \| Two.Gradient \| Two.Texture'#000'点的描边颜色或描边效果
linewidthNumber1描边的像素粗细
opacityNumber1整体不透明度
visibleBooleantrue是否显示
sizeNumber1每个点的直径(单位像素)
sizeAttenuationBooleanfalse点的大小是否随父级矩阵缩放
beginningNumber0从顶点列表的哪个百分比开始渲染
endingNumber1到顶点列表的哪个百分比结束渲染
dashesNumber[][]应用于描边的虚线数组
strokeAttenuationBooleantrue描边宽度是否随变换缩放

其中strokeAttenuation属性同样包含在静态Properties列表中,且与其余属性一样会被copy、clone、toObject处理(源码见 src/shapes/points.js)。

几何与位置相关属性

size:每个点的直径

size是Number类型,描述每个点应具有的直径(diameter),单位为像素。默认值为1。设置它会同步影响三种渲染器中的圆点半径:Canvas 渲染器中radius = size * 0.5(src/renderers/canvas.js);SVG 渲染器通过svg.pointsToString生成带半径r = size * 0.5的弧命令(src/renderers/svg.js);WebGL 渲染器则把它作为离屏纹理的尺寸(src/renderers/webgl.js)。

points.size = 10; // 直径为 10 像素的圆点

sizeAttenuation:大小是否随父级缩放

sizeAttenuation是布尔值,决定 Two.js 是否根据其矩阵层级(matrix hierarchy)缩放点的大小:

  • 设为true:点的大小相对于其父级(Group / Scene)的 scale 值缩放,即父级放大时点也跟着变大;
  • 设为false:忽略父级缩放,点始终保持在屏幕上的像素尺寸。默认值是false。

从实现看,Canvas 渲染器在sizeAttenuation为false时会用worldMatrix反算出最大缩放系数并做补偿(src/renderers/canvas.js),SVG 渲染器也有同样的补偿逻辑(src/renderers/svg.js)。注意默认sizeAttenuation = false是文档与源码一致的默认值(src/shapes/points.js),即默认点大小不随父级缩放。

beginning 与 ending:分段渲染控制

  • beginning:介于 0 与 1 之间的数值,表示渲染器应从路径(顶点序列)的哪个百分比位置开始绘制点,默认0;
  • ending:介于 0 与 1 之间的数值,表示渲染器应绘制到顶点序列的哪个百分比位置停止,默认1。

在_update的实现中(src/shapes/points.js),Two.js 会先计算总长度_length,再通过getIdByLength把beginning * length与ending * length换算成顶点区间,只有落在该区间内的顶点才会进入_renderer.collection被渲染。测试用例中将beginning与ending同时设为0.5也不会报错(tests/suite/shapes.js)。

vertices:有序顶点列表

vertices是一组有序的Two.Vector对象,决定在哪些坐标绘制点。文档特别提示:该数组在实际操作时其实是一个Two.Collection(src/collection.js)。

源码 setter(src/shapes/points.js)说明了这一点:当赋值非 Collection 的数组时,Two.js 会把它包装成new Collection(vertices),并自动绑定insert/remove事件——这意味着你向vertices集合push或删除顶点时,_update会被自动触发,无需手动刷新。

points.vertices.push(new Two.Anchor(400, 400)); // 动态新增一个点

length:总长度

length是只读属性,表示所有顶点之间距离的总和(The sum of distances between all Two.Points#vertices)。它由_updateLength计算(复用 src/path.js 的实现,见 src/shapes/points.js),供beginning/ending的分段渲染计算使用。由于是惰性求值(getter 中带_flagLength判断),只在需要时才重算(src/shapes/points.js)。

样式属性

fill:填充

fill决定点的填充内容。默认'#fff'。可以是:

  • CSS 颜色字符串,例如'red'、'#00AEFF'(参考 CSS color_value 规范);
  • Two.Gradient(LinearGradient / RadialGradient);
  • Two.Texture(wiki/docs/effects/texture/README.md)。

从 setter 实现(src/shapes/points.js)可以看到,当 fill 是 Gradient / Texture 等效果对象时,Two.js 会为它绑定change事件,使效果对象的任何修改都能自动触发该Two.Points的重绘。

stroke:描边

stroke决定点的描边内容,默认'#000'。取值类型与fill相同(CSS 颜色字符串或 Gradient / Texture)。其 setter 同样会对效果对象做事件绑定(src/shapes/points.js)。

linewidth:描边粗细

linewidth是描边的像素厚度,默认1。它参与三种渲染器的线宽计算:Canvas 中ctx.lineWidth、SVG 中stroke-width属性、WebGL 离屏纹理的绘制(src/renderers/webgl.js)。

opacity:不透明度

opacity表示整个点的透明程度,默认1。文档提示:可以与带 alpha 值的 CSS 颜色配合使用,实现双重透明叠加效果。Canvas 渲染器中opacity会与父级的渲染 opacity 相乘(src/renderers/canvas.js),体现 Two.js 的分层合成语义。

className:CSS 类名

className是应用于元素以便与 CSS 样式兼容的类名,默认''。文档强调:仅在 SVG 渲染器中会写入 DOM。这意味着如果你在 WebGL 或 Canvas 模式下使用className,它不会被反映到渲染结果中。

visible:显隐控制

visible控制是否显示这些点,默认true。文档特别说明了一个性能要点:对于 Two.CanvasRenderer 和 Two.WebGLRenderer,当visible设为false时,所有更新都会被禁用,当场景中有大量对象时可大幅提升性能。对应的源码行为在 Canvas 渲染器中被显式处理(src/renderers/canvas.js)。

dashes:虚线描边

dashes是数字数组,表示应用到描边上的重复“线段长度”与“间隔长度”:

  • 奇数索引代表 dash length(线段长度);
  • 偶数索引代表 dash space(间隔长度)。

例如points.dashes = [2, 2]表示 2 像素线段 + 2 像素间隔的虚线。测试 tests/suite/shapes.js 验证了该赋值与内部_dashes的一致性。语义上对应 SVG 的stroke-dasharray属性。

points.dashes = [10, 5, 2, 5]; // 10 实、5 空、2 实、5 空,循环往复

dashes.offset:虚线偏移

dashes.offset是以像素为单位的数值,用于偏移dashes的显示位置,默认0。Canvas 渲染器会把该值写入ctx.lineDashOffset(src/renderers/canvas.js),SVG 渲染器则对应stroke-dashoffset属性。常用于制作描边虚线流动动画。

strokeAttenuation:描边衰减

strokeAttenuation控制描边宽度与变换的关系:

  • true(默认):描边宽度随变换缩放(例如父级 Group 放大时描边变粗);
  • false:描边宽度保持屏幕空间的恒定视觉厚度,即使缩放、缩放视口(zoom)也不会变粗。

当为false时,Two.js 会自动根据对象的 world transform scale 补偿线宽(src/shapes/points.js),这对数据可视化中需要恒定粗细的标记点非常有用。

常用方法

fromObject(静态)

Two.Points.fromObject(obj)从一个Two.Points的对象表示(object notation)创建新实例,返回Two.Points。文档提示它与toObject配合使用。

源码实现(src/shapes/points.js)的要点:当obj.fill是字符串时直接沿用,否则通过getEffectFromObject重建 Gradient / Texture 效果对象;若obj携带id则会一并恢复。因此它可以安全地完成“序列化 → 反序列化”的往返。

toObject(实例方法)

toObject()返回一个 JSON 兼容的纯对象,表示当前 points 对象。源码(src/shapes/points.js)会把renderer.type标记为'points'、把vertices映射为各顶点的toObject(),并把Points.Properties中已定义的全部属性写入结果。

测试 tests/suite/shapes.js 验证了完整的往返流程:toObject生成对象 →fromObject(obj)恢复出坐标一致的Two.Points,同时copy也能产生完全一致的副本。

const obj = points.toObject(); const restored = Two.Points.fromObject(obj);

copy 与 clone

  • copy(points):把一个Two.Points的属性复制到另一个之上。它会深拷贝顶点(Anchor.clone()或new Anchor().copy(v)),再遍历Points.Properties复制样式属性(src/shapes/points.js);
  • clone(parent?):创建一个具有当前 points 相同属性的新Two.Points,可选传入父级 Group 直接加入(src/shapes/points.js)。如果提供了parent,clone 会被自动parent.add()到该父级。

dispose

dispose()释放渲染器资源并解除全部事件。按源码注释(src/shapes/points.js),它会依次:调用父类dispose保留渲染器类型;解绑 vertices 集合事件与每个顶点的独立事件;对 fill / stroke 中的效果对象调用dispose()(Gradient / Texture 的彻底清理)或退化为unbind();同时保留renderer.type以便将来重新挂载到新渲染器。对应测试见 tests/suite/dispose.js。

便捷方法:noFill / noStroke / corner / center / getBoundingClientRect / subdivide

这些方法均复用Path.prototype的实现(src/shapes/points.js):

  • noFill():快捷地把fill设为'none';
  • noStroke():快捷地把stroke设为'none';
  • corner():把形状顶点对齐到 points 对象的左上角;
  • center():把形状顶点对齐到 points 对象的中心;
  • getBoundingClientRect(shallow?):返回包含top、left、right、bottom、width、height的对象;shallow为true时基于局部矩阵计算,否则基于世界矩阵;
  • subdivide(limit):在vertices的每一项之间插入中点Two.Vector,limit控制递归细分次数(src/shapes/points.js)。由于Two.Points的顶点之间不连线,该操作主要用于配合beginning/ending做更平滑的分段动画。

实战示例:散点图的完整用法

结合 Two.makePoints 与测试中的渲染配置(tests/suite/canvas.js),下面是一个可以直接运行的完整示例:

<!DOCTYPE html> <html> <body> <div id="container"></div> <script src="https://cdn.jsdelivr.net/npm/two.js@latest"></script> <script> const two = new Two({ type: Two.Types.canvas, // 或 Two.Types.svg / Two.Types.webgl width: 400, height: 400, }).appendTo(document.getElementById('container')); // 方式一:工厂方法 + 交替坐标参数 const points = two.makePoints(200, 200, 220, 200, 180, 200); // 方式二:工厂方法 + 顶点数组 // const points = two.makePoints([ // new Two.Vector(200, 200), // new Two.Vector(220, 200), // new Two.Vector(180, 200), // ]); points.size = 10; // 每个点直径 10px points.fill = '#00AEFF'; // 亮蓝实心圆点 points.noStroke(); // 取消描边 points.sizeAttenuation = false; // 点大小不随父级缩放(默认) two.update(); // 触发一次渲染 </script> </body> </html>

运行结果对应基准图 tests/images/canvas/points@1x.png:白色画布上 3 个等间距排列的亮蓝色圆点。

进阶用法:利用beginning/ending让点集合“逐点出现”,做出入场动画;利用dashes+dashes.offset制作描边流动;利用clone()批量复制点群;利用visible = false配合大量对象隐藏时的性能优化。

三种渲染器下的实现细节

Canvas 渲染器

Canvas 渲染器对points类型的渲染逻辑位于 src/renderers/canvas.js。核心步骤:

  1. 取出_renderer.collection作为“命令集”(即被beginning/ending筛选后的顶点子集);
  2. 应用矩阵变换(ctx.save()+ctx.transform,仅当矩阵非默认时);
  3. 依次设置fillStyle/strokeStyle/globalAlpha/lineDashOffset;
  4. 对每个顶点执行ctx.moveTo(x + radius, y)与ctx.arc(x, y, radius, 0, TWO_PI),最后统一fill()/stroke()。

也就是说,Canvas 模式下每个点都是一段独立的圆弧路径,一次fill()/stroke()批量绘制。

SVG 渲染器

SVG 渲染器对points类型的实现位于 src/renderers/svg.js,配套的工具函数svg.pointsToString见 src/renderers/svg.js。它把每个点编码成 SVG 的path元素d属性:

M x (y - r) a r r 0 1 0 0.001 0 Z

即先moveTo到点顶部,再用一个小圆弧绘制出整个圆。当sizeAttenuation为false时,同样会用worldMatrix反算缩放系数来补偿半径(src/renderers/svg.js)。此外,className只会在此渲染器中被写入 DOM(对应文档提示),fill / stroke 为 Gradient 或 Texture 时会被序列化为url(#effectId)引用。

WebGL 渲染器

WebGL 渲染器采用“单顶点纹理”策略:先在离屏 canvas 上把“一个点”的样式(fill、stroke、linewidth、opacity、dashes、size)绘制成纹理,再为每个顶点实例化一个四边形进行批量贴图渲染。核心逻辑见 src/renderers/webgl.js:

const canvas = this.canvas; const ctx = this.ctx; const ratio = gl.renderer.ratio; ... const size = elem._size * ratio; let dimension = size; if (!webgl.isHidden.test(stroke)) { dimension += linewidth; } canvas.width = getPoT(dimension); canvas.height = canvas.width;

其中getPoT把纹理尺寸对齐到 2 的幂次(power of two),以适配 WebGL 纹理约束。这种“一个点一份纹理 + 实例化渲染”的设计,让大量点集的场景在 WebGL 下拥有良好的吞吐性能。

对象往返与持久化

Two.Points支持完整的 JSON 化往返流程:

const point = new Two.Points(new Two.Anchor(50, 50)); point.id = 'my-point'; const obj = point.toObject(); // -> { id, vertices:[...], fill, stroke, ... } const newPoint = Two.Points.fromObject(obj); // 还原出坐标一致的新实例

测试 tests/suite/shapes.js 用 7 个断言覆盖了这一过程:toObject返回对象、保留顶点 x/y、保留id、fromObject还原坐标、copy生成完全一致的副本。这意味着你可以把散点数据序列化保存,之后无缝重建。

延伸阅读

  • Two.Shape 基类:Two.Points的继承基类;
  • Two.Vector:顶点坐标对象;
  • Two.Collection:vertices的实际容器;
  • Two.Path:复用其noFill、corner、center、subdivide等实现;
  • Canvas 渲染器、SVG 渲染器、WebGL 渲染器:三种渲染模式;
  • 源码:src/shapes/points.js;
  • 测试:tests/suite/shapes.js、tests/suite/canvas.js、tests/suite/dispose.js。
  • 图形学
  • 前端

【免费下载链接】two.js

A renderer agnostic two-dimensional drawing api for the web

项目地址:https://gitcode.com/gh_mirrors/tw/two.js
点击查看免费下载
上一篇:如何让老款Mac焕发新生:OpenCore Legacy Patcher完整使用指南
下一篇:BuildingBlocks主题切换系统:动态主题与夜间模式的完整实现方案

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询