- 图形学
- 前端
【免费下载链接】two.js
A renderer agnostic two-dimensional drawing api for the web
Two.SVGRenderer是 Two.js 渲染器家族中面向矢量 DOM 的实现,也是new Two(...)在不显式指定渲染类型时默认启用的渲染后端。它负责把 Two.js 的场景图(scenegraph)——由Group、Path、Text、渐变与纹理等对象组成的层级结构——翻译成浏览器原生的<svg />元素树,适用于图标、图表、可缩放插画以及任何需要高保真矢量输出的 Web 场景。读完本文,你将掌握Two.SVGRenderer的构造参数、核心成员与渲染方法,并深入理解 pathd属性生成、渐变/纹理<defs>管理、遮罩与裁剪等底层机制,能够在此基础上自行阅读源码、调试渲染结果甚至定制渲染行为。
Two.SVGRenderer 是什么:Two.js 的默认渲染后端
Two.js 是一个"渲染器无关"(renderer agnostic)的二维绘图库,同一套场景图可以输出到 SVG、Canvas 与 WebGL 三种后端。Two.SVGRenderer就是其中之一,官方文档对其定位的说明是:
This class is used by Two when constructing with
typeofTwo.Types.svg(the default type). It takes Two.js' scenegraph and renders it to a<svg />.
类继承关系为Two.SVGRenderer extends Two.Events(事件体系参见 wiki/docs/events/README.md),因此它天然支持addEventListener/trigger等事件能力,例如尺寸变化时会派发Two.Events.resize事件。
渲染类型在 src/constants.js 中定义:
Types: { webgl: 'WebGLRenderer', svg: 'SVGRenderer', canvas: 'CanvasRenderer', },在 src/two.js 的构造逻辑中,type默认值就是Two.Types.svg。这意味着下面的写法就足以得到一个由Two.SVGRenderer驱动的实例:
const two = new Two({ width: 640, height: 480, }); // 未指定 type,默认使用 SVG 渲染器构造参数
Two.SVGRenderer的构造函数在 src/renderers/svg.js 实现,其参数说明如下:
| Argument | Description |
|---|---|
parameters | 该对象在构造新的 Two 实例时被继承。 |
parameters.domElement | 要绘制的<svg />元素。如果不提供,渲染器会自动构造一个新的。 |
从源码可以看到默认行为的细节:构造函数先通过svg.createElement('svg')创建 SVG 根节点(并自动带上 SVG 1.1 的version属性,见 src/renderers/svg.js),随后创建<defs />子节点并把它挂到根 SVG 下,同时把根节点样式设为overflow: hidden:
this.domElement = params.domElement || svg.createElement('svg'); this.scene = new Group(); this.scene.parent = this; this.defs = svg.createElement('defs'); this.defs._flagUpdate = false; this.domElement.appendChild(this.defs); this.domElement.defs = this.defs; this.domElement.style.overflow = 'hidden';需要注意的是,Two主类在构造时会根据传入的domElement标签名自动校正渲染器类型(src/two.js):如果传入的元素与当前type不匹配,例如给了一个<svg>元素却声明type: Two.Types.canvas,Two.js 会把类型覆盖为Two.Types.svg,因为该元素只支持SVGRenderer-svg组合。
核心实例成员:domElement、scene 与 defs
Two.SVGRenderer暴露了三个贯穿渲染全流程的实例属性,理解它们就抓住了渲染器的骨架:
| 成员 | 含义 |
|---|---|
domElement | 与 Two.js 场景关联的<svg />元素,是整个渲染的输出目标。 |
scene | 场景图的根Group,所有添加到 Two 实例中的对象都挂在这棵树下。 |
defs | 用于承载渐变、图案(pattern)与位图素材的<defs />元素。 |
scene的声明在 src/renderers/svg.js,它是一个独立的Two.Group实例。实际上Two主类在构造完成之后会把this.scene = this.renderer.scene(src/two.js),所以你在two实例上通过two.add(...)添加的任何对象,最终都会成为渲染器scene的子节点。
defs的职责可以从渲染器对渐变、纹理的处理中看出:linearGradient、radialGradient、pattern等元素都会被追加到<defs>下(见后文"渐变与纹理"一节),而路径元素则通过fill="url(#two-xx)"/stroke="url(#two-xx)"这样的引用方式关联它们。Two.js 每个对象都会获得形如two-0、two-1的自增 id(前缀two-定义于 src/constants.js),这个 id 正是 SVG 中跨元素引用的纽带。
核心方法:setSize 与 render
setSize(width, height):变更渲染器尺寸
setSize用于改变渲染器的大小,签名与行为如下(src/renderers/svg.js):
| Argument | Description |
|---|---|
width | 渲染器的新宽度。 |
height | 渲染器的新高度。 |
setSize(width, height) { this.width = width; this.height = height; svg.setAttributes(this.domElement, { width: width, height: height, }); return this.trigger(Events.Types.resize, width, height); }方法内部会把width/height直接写成根<svg>的 DOM 属性,并触发Two.Events.resize事件(文档中以 nota-bene 的形式特别标注了这一行为)。Two主类在构造时(src/two.js)绑定resize事件来同步维护实例的width/height字段,所以当你想在运行时改画布大小时,除了调用two.renderer.setSize(w, h),也可以通过two.width = w; two.height = h; two.update()这类上层 API 间接生效。
render():把当前场景绘制到<svg />
render()是渲染的入口,其实现非常精炼(src/renderers/svg.js):
render() { svg.group.render.call(this.scene, this.domElement); svg.defs.update(this.domElement); return this; }它把根scene作为一个"组"来渲染,随后调用svg.defs.update清理<defs>中已无引用的渐变/图案(通过选择器[fill="url(#id)"],[stroke="url(#id)"],[clip-path="url(#id)"]判断引用是否仍存在,见 src/renderers/svg.js)。渲染完成后返回this,便于链式调用。在 Two.js 的正常工作流中,你一般不会直接调用render,而是使用two.update()或two.play()驱动刷新;单元测试(如 tests/suite/svg.js)正是通过two.update()之后查询two.renderer.domElement.querySelector('#' + id)来断言渲染结果的。
静态工具集 Two.SVGRenderer.Utils
Two.SVGRenderer.Utils(源码位置 src/renderers/svg.js)是官方文档中重点介绍的一个大型工具对象——它集中了把 Two.js 对象渲染到<svg />所需的全部工具函数与属性,源码实现即 src/renderers/svg.js 中的svg对象。
DOM 工具:createElement / setAttributes / removeAttributes
createElement(name, attrs):通过document.createElementNS(svg.ns, name)创建命名空间下的 SVG 元素;创建<svg>时自动补上version: 1.1。setAttributes(elem, attrs):批量写入属性,其中键名匹配/href/的属性(如xlink:href)会走setAttributeNS(svg.xlink, ...),以兼容旧版 SVG 命名空间写法。removeAttributes(elem, attrs):批量移除属性。
命名空间常量定义在 src/renderers/svg.js:ns为http://www.w3.org/2000/svg,xlink为http://www.w3.org/1999/xlink。另外这里还预置了两组映射表,用于把 Two.js 的语义值翻译成 SVG 标准值:
alignments: { left: 'start', center: 'middle', right: 'end' }, baselines: { top: 'hanging', middle: 'middle', bottom: 'ideographic', baseline: 'alphabetic' },toString(points, closed):路径d属性生成器
这是渲染管线中最关键的函数之一(src/renderers/svg.js),它的职责是把一组顶点(Anchor)转换为<path>的d属性字符串。源码注释特别强调:字符串拼接必须尽可能快,因为这个调用在一秒内会发生多次(动画场景)。
实现逻辑按顶点的command字段分发(命令常量定义于 src/utils/path-commands.js):
Commands.move(M):记录"last move"点,输出M x y;Commands.close(Z):直接输出闭合命令;Commands.arc(A):输出完整的 SVG 椭圆弧参数A rx ry x-axis-rotation large-arc-flag sweep-flag x y;Commands.curve(C):读取前后顶点的贝塞尔控制点(controls.left/controls.right),并兼容relative相对坐标模式,输出三次贝塞尔曲线段C ...;若处于闭合曲线的收尾处,还会补上回到最近move点的C段与Z。
toFixed会把坐标规整到 6 位小数(src/utils/math.js),这也是测试断言中d属性数值精确到55.228474这类值的原因。
pointsToString(points, size):点集合渲染
Points形状(散点)在 SVG 中同样以<path>输出,但每个点渲染成一个小圆。pointsToString(src/renderers/svg.js)为每个点生成一段M x y+a r r 0 1 0 0.001 0 Z的微小圆弧路径。测试中Two.makePoints的输出即为该格式(见 tests/suite/svg.js)。
getRendererType / getClip
getRendererType(type)(src/renderers/svg.js):把对象标记的渲染类型映射到svg工具对象上的对应条目,未知类型一律回退到'path'。getClip(shape, domElement)(src/renderers/svg.js):惰性创建并复用clipPath元素,追加到<defs>,用于实现clip裁剪。
各类对象的渲染管线
Utils中最核心的是按渲染类型组织的render函数:group、path、points、text、linear-gradient、radial-gradient与texture。它们遵循同一套"脏标记(flag)"机制:Two.js 对象在属性变化时置位对应的_flagXxx,渲染函数只把置位了的属性写进changed字典,从而做到最小化 DOM 写入。
group:场景树的容器
group.render(src/renderers/svg.js)是递归遍历的起点,负责:
- 隐藏对象的短路优化:
!this._visible && !this._flagVisible或opacity === 0时直接返回,且不重置标记,这样对象再次可见时变更会被一次性应用; - 矩阵变换:以
matrix(a b c d e f)字符串写入transform属性(Matrix.toString()生成); - 逐属性同步:
id、opacity、display(由visible映射为inline/none)、class; - 增量增删:
_flagAdditions/_flagSubtractions时调用appendChild/removeChild(梯度与裁剪对象会被跳过),_flagOrder时按顺序重排; - 遮罩:当
_mask存在时先渲染遮罩对象,再给当前元素写clip-path: url(#maskId),否则移除该属性。
path:矢量路径
path.render(src/renderers/svg.js)把 Path 及其子类(矩形、圆、椭圆、多边形、星形等)落成<path>元素,属性映射关系如下:
| Two.js 属性 | SVG 属性 | 说明 |
|---|---|---|
_flagVertices | d | 经svg.toString生成路径数据 |
_flagFill | fill | 纯色直接写值;渐变/纹理写url(#id) |
_flagStroke | stroke | 同上 |
_flagLinewidth | stroke-width | 经getEffectiveStrokeWidth处理 |
_flagOpacity | stroke-opacity/fill-opacity | 透明度同时作用于描边与填充 |
_flagCap | stroke-linecap | 线帽样式 |
_flagJoin | stroke-linejoin | 线段连接样式 |
_flagMiter | stroke-miterlimit | 斜接限制 |
dashes | stroke-dasharray/stroke-dashoffset | 虚线样式 |
_flagVisible | visibility | visible/hidden |
_flagMask | clip-path | 遮罩引用 |
关于stroke-width,这里调用的是getEffectiveStrokeWidth(src/utils/math.js):当strokeAttenuation(默认开启)为true时直接返回原始线宽,让描边跟随变换缩放;为false时则解算世界矩阵、取scaleX/scaleY中较大者做补偿,使描边在屏幕上保持恒定宽度。
另外path还支持裁剪(clip):当对象设置了_clip时,元素会被移入对应的clipPath节点;取消裁剪则恢复原状(src/renderers/svg.js)。源码中还保留了一段关于 Chromium 裁剪 bug(issue 370951)的注释掉的组级双向往返实现,可供研究裁剪历史行为时参考。
points:散点
points.render(src/renderers/svg.js)输出<path>,d属性由svg.pointsToString生成。它额外支持sizeAttenuation:当关闭尺寸衰减时,会解算世界矩阵的缩放值来调整点的大小,保证点在不同缩放层级下视觉尺寸稳定。
text:文本
text.render(src/renderers/svg.js)把Two.Text渲染为<text>,并利用开头的alignments/baselines映射表做语义翻译:
| Two.js 属性 | SVG 属性 |
|---|---|
_flagFamily | font-family |
_flagSize | font-size |
_flagLeading | line-height |
_flagAlignment | text-anchor(start/middle/end) |
_flagBaseline | dominant-baseline(hanging/middle/ideographic/alphabetic) |
_flagStyle/_flagWeight/_flagDecoration | font-style/font-weight/text-decoration |
_flagDirection | direction |
_flagValue | textContent(文本内容直接写入元素) |
文本同样支持填充、描边、透明度、虚线与遮罩。注意文本的透明度使用的是单个opacity属性(而非 path 的双 opacity),因为<text>作为一个整体元素透明度语义更合适。
渐变:linear-gradient 与 radial-gradient
Two.js 的渐变对象在 SVG 后端中被翻译成<linearGradient>与<radialGradient>元素,统一挂到<defs>下(src/renderers/svg.js)。
- 线性渐变:
x1 / y1 / x2 / y2由left/right两个端点向量输出;spreadMethod来自spread属性;gradientUnits来自units属性。 - 径向渐变:
cx / cy来自center,fx / fy来自focal,半径输出为r。 - 渐变的色标(
stops)被渲染为<stop>子元素:offset由stop._offset * 100 + '%'生成,stop-color与stop-opacity分别来自颜色与透明度。
在tests/suite/svg.js的Two.makeLinearGradient测试(tests/suite/svg.js)中,可以看到默认输出为spreadMethod="pad"、gradientUnits="objectBoundingBox",色标形如<stop offset="0%" stop-color="rgb(255, 100, 100)" stop-opacity="1">。这些属性与 Two.js 渐变类的 API 一一对应,相关用法可参见 wiki/docs/effects/gradient/README.md。
texture:纹理与图案
texture.render(src/renderers/svg.js)把Two.Texture渲染为<pattern>,实现"图案填充":
- 图案元素设置
patternUnits="userSpaceOnUse",id 作为填充引用键; - 内部
<image>元素的href/xlink:href根据图片来源分支:<canvas>走toDataURL('image/png'),<img>/<image>直接引用src; - 通过
offset、scale、repeat计算图案的x/y位移、width/height,并按是否no-repeat决定preserveAspectRatio(xMidYMid保持居中,否则拉伸为none)。
图案会被追加到<defs>下,并以fill="url(#textureId)"的形式被路径引用,最终实现平铺或拉伸的纹理填充效果,详见 wiki/docs/effects/texture/README.md。
与 Two 主类协同:构造、尺寸与 SVG 导入
从使用者的角度,Two.SVGRenderer几乎完全被 src/two.js 的主类封装,你通常通过Two实例间接操作它。构造相关选项包括:
| 选项 | 默认值 | 说明 |
|---|---|---|
type | Two.Types.svg | 渲染器类型,见 wiki/docs/two/README.md |
width/height | 640/480 | 舞台尺寸,构造时经renderer.setSize生效 |
domElement | 自动创建 | 指定的<svg>,且会覆盖type推断 |
fullscreen | false | 让舞台自适应window |
fitted | false | 让舞台自适应父元素 |
autostart | false | 是否立即进入requestAnimationFrame循环 |
由于 SVG 是结构化文档,Two.js 还提供了two.interpret(svg)/two.load(url, callback)等 SVG 导入能力(解析入口见 src/utils/interpret-svg.js 与 src/two.js 中的Two.Utils.read)。导入测试覆盖了 D.svg、K.svg、donut.svg 等样例(见 tests/images/interpretation 与 tests/suite/svg-interpreter.js),这些能力让 Two.js 可以把既有 SVG 资产直接纳入场景图,再由Two.SVGRenderer原样输出。关于导入矩阵行为,可通过Two.AutoCalculateImportedMatrices(src/constants.js)控制是否重新推算导入 SVG 的变换矩阵。
渲染正确性的测试验证
Two.SVGRenderer的行为在 tests/suite/svg.js 中被系统地验证,测试方式具有很强的参考价值:构造new Two({ width: 400, height: 400 })→ 通过two.makeXxx(...)创建对象 →two.update()触发渲染 →querySelector('#' + id)取回 SVG 元素 → 断言 DOM 属性。例如:
Two.makeLine断言d为'M 0 0 L 400 400 ';Two.makeRectangle断言d为'M -50 -50 L 50 -50 L 50 50 L -50 50 Z ';Two.makeCircle/Two.makeEllipse断言贝塞尔近似的C段坐标;Two.makePath同时断言d与transform="matrix(1 0 0 1 0 0)";- 渐变测试断言
linearGradient/radialGradient的坐标、spreadMethod、gradientUnits及内嵌<stop>的 HTML。
这套测试既是对渲染器正确性的背书,也是理解"Two.js 对象 → SVG 属性"映射关系的最佳入门教材。你还可以运行仓库中的 QUnit 测试套件(tests/index.html)在浏览器中直接观察每个用例的渲染产物。
总结
Two.SVGRenderer是 Two.js 默认且最贴近浏览器原生能力的渲染后端:它以<svg />为输出目标,通过 src/renderers/svg.js 中的Utils工具集,把场景图翻译为<path>、<text>、<linearGradient>、<radialGradient>、<pattern>与<clipPath>等标准 SVG 元素,并依靠脏标记机制实现高效的增量更新。无论是把 Two.js 场景输出为可缩放、可检索、可被 CSS 与浏览器开发者工具直接操控的矢量 DOM,还是复用既有 SVG 素材,Two.SVGRenderer都是最适合的起点。掌握本文梳理的构造参数、核心成员(domElement/scene/defs)、两个关键方法(setSize/render)以及各类对象的属性映射表,你就能熟练驾驭 Two.js 的 SVG 渲染路径,并能在需要时深入到源码层面定制渲染行为。
- 图形学
- 前端
【免费下载链接】two.js
A renderer agnostic two-dimensional drawing api for the web
相关推荐
Two.js CanvasRenderer 详解:场景图到 `<canvas />` 的 2D 渲染管线
Two.js CanvasRenderer 详解:场景图到 <canvas / 的 2D 渲染管线 导读 Two.CanvasRenderer 是 Two.js
图形学前端three.js 中 SVGObject 的完整指南:把 SVG 图形接入 3D 场景并与渲染管线深度交错
three.js 中 SVGObject 的完整指南:把 SVG 图形接入 3D 场景并与渲染管线深度交错 本文围绕 three.js 官方 API 文档 SV
前端3D渲染图形学GameDevMind 图形与渲染技术图谱:从场景基础到现代渲染管线的完整实战指南
GameDevMind 图形与渲染技术图谱:从场景基础到现代渲染管线的完整实战指南 本文基于 GameDevMind 知识图谱中的《2.1.1 图形与渲染》核心
文档教程知识库游戏开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考