news 2026/9/25 5:48:33

Two.js 渐变关键点(Two.Stop)完全指南:offset、color 与 opacity 的源码级解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Two.js 渐变关键点(Two.Stop)完全指南:offset、color 与 opacity 的源码级解析
  • 图形学
  • 前端

【免费下载链接】two.js

A renderer agnostic two-dimensional drawing api for the web

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

Two.Stop 是 Two.js 中承载渐变颜色关键点(color stop)的核心数据类,每一个 Stop 都以“位置(offset)+ 颜色(color)+ 不透明度(opacity)”三元组定义渐变轴上的一次颜色过渡,是构建 Two.LinearGradient 与 Two.RadialGradient 的最小单元。本文以 wiki/docs/effects/stop/README.md 为骨架,结合 src/effects/stop.js 源码与其在三大渲染器中的实际消费方式,系统讲解 Two.Stop 的构造规则、默认值生成逻辑、实例方法、序列化机制以及渲染层映射,帮助你彻底掌握 Two.js 渐变系统并可独立写出可运行的渐变示例。

Two.Stop 在 Two.js 中的定位

Two.js 是一个渲染器无关(renderer agnostic)的二维绘图 API,支持 Canvas、SVG 与 WebGL 三种后端。渐变填充是其fill/stroke能力的重要组成部分,而Two.Stop正是渐变填充的“调色板刻度”:

  • 一个渐变(Two.Gradient)由若干stops组成,它们是“有序”的,渲染时按数组顺序在渐变轴上逐段过渡;
  • Two.Stop继承自 Two.Element,拥有元素级的能力(id、事件、序列化等);
  • 源码 src/effects/gradient.js 中Gradient.Stop = Stop,即Two.Gradient.Stop与Two.Stop是同一个类,两种写法等价,测试代码中两种形式均有使用。

因此,掌握Two.Stop就等于掌握了 Two.js 渐变系统中“颜色如何随位置变化”的完整机制。

构造函数与默认值“交替”逻辑

Two.Stop的构造函数签名如下(参见 src/effects/stop.js):

参数类型说明
offsetNumber该关键点的位置百分比,用 0~1 之间的值表示。不传时默认值会“交替翻转”(见下文)
colorString该关键点的颜色。不传时默认值会在白色与黑色之间“交替翻转”
opacityNumber不透明度值,默认 1,不能低于 0

Two.Stop的构造函数在 src/effects/stop.js 中的实现如下:

constructor(offset, color, opacity) { super(); for (let prop in proto) { Object.defineProperty(this, prop, proto[prop]); } this._renderer.type = 'stop'; // offset 未传时:根据全局计数器 Stop.Index 决定默认值 this.offset = typeof offset === 'number' ? offset : Stop.Index <= 0 ? 0 : 1; // opacity 未传时:默认 1 this.opacity = typeof opacity === 'number' ? opacity : 1; // color 未传时:根据全局计数器 Stop.Index 决定默认色 this.color = typeof color === 'string' ? color : Stop.Index <= 0 ? '#fff' : '#000'; Stop.Index = (Stop.Index + 1) % 2; }

这里有两个值得注意的细节:

  1. Stop.Index全局计数器的翻转:每创建一个未指定offset/color的 Stop,静态属性Stop.Index就在0和1之间取模翻转((Index + 1) % 2)。于是连续新建的空参数 Stop 会自动获得“第一段在 0 处为白色、第二段在 1 处为黑色”的交替默认值——这正是文档所述“flip flops”的精确含义。
  2. 内部下划线属性:真正的数据存储在_offset、_color、_opacity三个私有字段中,外部访问走的是proto中定义的存取器(getter/setter),setter 除了赋值还会触发脏标记(见下文“属性更新机制”)。

实例属性:offset、color 与 opacity

offset(位置)

  • 类型:Number
  • 语义:0 表示渐变轴的起点,1 表示终点,中间值按比例插值。
  • 默认值:Stop.Index <= 0 ? 0 : 1。
  • SVG 渲染时会被转换为100 * offset + '%'(如0.25→offset="25%"),见 src/renderers/svg.js。

color(颜色)

  • 类型:String
  • 支持 CSS 颜色字符串,包括'#fff'、'#ff0000'、'rgb(255, 100, 100)'、'rgba(...)'等形式。测试中大量使用new Two.Stop(0, 'rgb(255, 100, 100)')这类写法(见 tests/suite/canvas.js)。
  • 默认值:Stop.Index <= 0 ? '#fff' : '#000'。
  • SVG 渲染时映射为stop-color属性,见 src/renderers/svg.js。

opacity(不透明度)

  • 类型:Number,默认1,文档注明“不能低于 0”。
  • 渲染器支持限制:文档与源码双重提示——opacity属性仅在 SVG 渲染器上原生支持(映射为stop-opacity,见 src/renderers/svg.js)。Canvas 与 WebGL 渲染器通过ctx.createLinearGradient+addColorStop实现渐变(见 src/renderers/canvas.js 与 src/renderers/webgl.js),该 API 不支持独立的 stop 不透明度。
  • 跨渲染器等价方案:文档给出的替代方案是“把 opacity 直接编码进color的rgba字符串”,例如用'rgba(255, 0, 0, 0.5)'代替“红色 + opacity 0.5”,这样在 Canvas/WebGL 下同样能获得半透明过渡效果。

静态成员

Two.Stop.Index

  • 类型:Number
  • 定义于 src/effects/stop.js,源码默认值0。
  • 作用:作为全局计数参照,决定“未指定参数”的 Stop 的默认offset与color(构造完成后(Index + 1) % 2翻转)。它是模块级共享状态,会随 Stop 的创建次数在 0/1 间往复。

Two.Stop.Properties

  • 类型:String[]
  • 值:['offset', 'opacity', 'color'](见 src/effects/stop.js)。
  • 作用:声明所有Two.Stop实例共有的可序列化/可复制属性,copy、clone、toObject、fromObject均以该数组为白名单循环处理。

Two.Stop.fromObject(obj)

  • 返回:Two.Stop
  • 实现位于 src/effects/stop.js:内部new Stop().copy(obj)构造实例,若obj含id字段则一并恢复,从而保证反序列化后 id 不变。
const stop = Two.Stop.fromObject({ offset: 0.25, color: '#ff0000', opacity: 0.8 }); // 等价于 new Two.Stop(0.25, '#ff0000', 0.8),但 id 可由对象携带

文档注明:fromObject与toObject成对使用(nota-bene 提示),二者组合可实现 Stop 的完整 JSON 往返。

实例方法:copy、clone、toObject

copy(stop)

  • 参数:stop,作为拷贝来源的Two.Stop引用。
  • 语义:把一个 Stop 的属性完整复制到另一个 Stop 上。
  • 实现见 src/effects/stop.js:先调用父类Element的copy,再遍历Stop.Properties,凡源对象含有的属性一律this[k] = stop[k](走 setter,因此会同步触发脏标记与父渐变更新)。
const a = new Two.Stop(0, '#ff0000'); const b = new Two.Stop(); // 默认值 b.copy(a); // b 现在与 a 属性一致

clone(parent)

  • 参数:parent(可选),目标父渐变(Two.Gradient)。
  • 返回:Two.Stop
  • 语义:以当前实例属性创建全新 Stop。实现见 src/effects/stop.js:遍历Stop.Properties逐项赋值;若传入parent且其存在stops集合,会把克隆体push进parent.stops——这是Gradient.clone内部克隆 stops 时使用的同款机制(见 src/effects/gradient.js)。
const parent = new Two.LinearGradient(); // 已有 stops 集合 const s = new Two.Stop(0.5, '#00ff00'); const twin = s.clone(parent); // twin 属性与 s 相同,并被加入 parent.stops

toObject()

  • 返回:普通Object(JSON 兼容)。
  • 语义:返回表示该 Stop 的纯对象。实现见 src/effects/stop.js:调用父类toObject得到基础对象并设置renderer.type = 'stop',再把Stop.Properties三个字段逐一写入。
const s = new Two.Stop(0.5, '#00ff00', 1); s.toObject(); // 输出类似:{ renderer: { type: 'stop' }, offset: 0.5, opacity: 1, color: '#00ff00' }

flagReset()(内部方法)

  • 私有方法,文档归类为内部机制。实现见 src/effects/stop.js:把_flagOffset、_flagColor、_flagOpacity三个脏标记统一复位为false。渲染器消费完一个 Stop 后调用它,保证“只有真正变化的属性才会被更新”,避免每帧重复写入 DOM 或重新构建渐变对象。

属性更新机制:脏标记与父渐变联动

Two.Stop的属性全部由proto中的存取器托管(见 src/effects/stop.js),核心行为如下:

  • 每个属性各自持有独立脏标记:_flagOffset、_flagOpacity、_flagColor,初始均为true(表示首次渲染需要全量写入);
  • 每次 setter 赋值都会:设置对应脏标记为true,并且如果存在this.parent(父渐变),会把parent._flagStops置为true,通知父渐变“stops 已变更,需要重建渐变效果”;
  • 三个 setter 均为enumerable: true,因此会被Object.assign、JSON.stringify等遍历到。

与父渐变的联动还有另一层:Two.Gradient通过Collection持有 stops(见 src/effects/gradient.js),并在插入/移除 stop 时调用BindStops/UnbindStops(src/effects/gradient.js)。BindStops会把每个 stop 的change事件绑定到渐变的_renderer.flagStops上——这意味着直接修改stop.color = ...、stop.offset = ...或stop.opacity = ...时,无需手动调用任何更新方法,父渐变会在下一帧渲染时自动感知变化并重建渐变。这正是 Two.js “渲染前统一_update、仅对变化属性生效”的脏标记架构在渐变子系统中的落地。

三大渲染器如何消费 Stop

SVG 渲染器(原生支持 opacity)

SVG 渲染器会把每个 Stop 渲染为<stop>子元素,挂在<linearGradient>/<radialGradient>的<defs>中。相关实现见 src/renderers/svg.js(线性渐变)与 src/renderers/svg.js(径向渐变):

if (stop._flagOffset) { attrs.offset = 100 * stop._offset + '%'; } if (stop._flagColor) { attrs['stop-color'] = stop._color; } if (stop._flagOpacity) { attrs['stop-opacity'] = stop._opacity; } if (!stop._renderer.elem) { stop._renderer.elem = svg.createElement('stop', attrs); } else { svg.setAttributes(stop._renderer.elem, attrs); }

要点:

  • offset由 0~1 的浮点值换算为百分数字符串(100 * offset + '%');
  • 只有在对应脏标记为真时才写入该属性,实现“变化才更新”;
  • 每个 Stop 复用其_renderer.elem,仅在 stops 数量变化(lengthChanged)时才重建子节点;
  • 渲染完成后对每个 stop 调用flagReset()复位脏标记。

Canvas 与 WebGL 渲染器(addColorStop)

Canvas 与 WebGL 两种渲染器的渐变路径一致(WebGL 的effect本质上是 2D 上下文的CanvasGradient):先createLinearGradient/createRadialGradient,再遍历 stops 调用addColorStop。见 src/renderers/canvas.js 与 src/renderers/webgl.js:

this._renderer.effect = ctx.createLinearGradient(lx, ly, rx, ry); for (let i = 0; i < this.stops.length; i++) { const stop = this.stops[i]; this._renderer.effect.addColorStop(stop._offset, stop._color); }

注意此处的addColorStop第二参数只接收颜色字符串,因此Stop 的opacity属性在这两种渲染器下不会生效,需要把透明度合并进color的rgba()字符串中才能得到半透明过渡。这是文档中明确标注的兼容性提示,也是跨渲染器编写渐变时的关键注意事项。

重建时机

三种渲染器都以“效果对象不存在或相关脏标记为真”为重建渐变的触发条件。以 Canvas 线性渐变为例(src/renderers/canvas.js):

if ( !this._renderer.effect || this._flagEndPoints || this._flagStops || this._flagUnits ) { // 重建渐变对象并重新 addColorStop }

修改任意 stop 的属性都会经由parent._flagStops = true使该条件成立,从而在下一帧完成渐变重建。

实战:从零搭建带渐变的 Two.js 场景

基础用法:新建 Stop 与 LinearGradient

const two = new Two({ width: 400, height: 400 }).appendTo(document.body); // 方式一:通过 Two.Gradient.Stop 创建关键点 const stops = [ new Two.Gradient.Stop(0, 'rgb(255, 100, 100)'), new Two.Gradient.Stop(1, 'rgb(100, 100, 255)') ]; // 方式二:等价写法 // const stops = [ // new Two.Stop(0, 'rgb(255, 100, 100)'), // new Two.Stop(1, 'rgb(100, 100, 255)') // ]; const gradient = new Two.LinearGradient(0, 0, 400, 0, stops); const rect = two.makeRectangle(200, 200, 300, 200); rect.fill = gradient; rect.noStroke(); two.update();

动态更新 Stop

利用“修改属性自动通知父渐变”的机制,可以实时驱动颜色过渡:

two.bind(Two.Types.update, function () { const t = (two.time % 1000) / 1000; // 0 ~ 1 往复 stops[1].offset = t; // 移动第二个关键点位置 stops[1].color = t > 0.5 ? '#ff0000' : '#0000ff'; });

序列化往返

toObject与fromObject组合可把渐变关键点序列化后再恢复,配合Gradient.fromObject使用(见 src/effects/gradient.js,其中每个 stop 会经new Stop().copy(o)重建):

const json = JSON.stringify(gradient.toObject()); // ...存储或传输... const restored = Two.LinearGradient.fromObject(JSON.parse(json));

半透明关键点的跨渲染器写法

  • 仅在 SVG 渲染器使用:直接设stop.opacity = 0.5;
  • 需要兼容 Canvas/WebGL:改用color携带 alpha,例如new Two.Stop(0, 'rgba(255, 0, 0, 0.5)')。

类型定义与测试佐证

  • TypeScript 类型:完整声明位于 src/effects/stop.d.ts,包含Stop.Index、Stop.Properties、static fromObject、offset/opacity/color实例属性以及copy、clone、toObject、flagReset的签名,可直接用于 TS 项目。
  • 测试用例:仓库测试套件在多处覆盖 Stop 的构造与渐变集成,例如 tests/suite/canvas.js、tests/suite/svg.js、tests/suite/webgl.js 均以new Two.Gradient.Stop(0, 'rgb(255, 100, 100)')形式构造双关键点渐变;tests/suite/dispose.js 等文件则验证 Stop 在资源释放(dispose)场景下的正确性。这些测试也侧面印证了Two.Stop与Two.Gradient.Stop的等价性。

小结

Two.Stop虽小,却是 Two.js 渐变系统的基石:offset决定颜色过渡发生在渐变轴的何处,color决定过渡的目标颜色,opacity在 SVG 下提供额外的透明度维度;全局计数器Stop.Index与Properties白名单驱动了默认值生成、复制、克隆与序列化的整套行为;脏标记机制让 Stop 与父渐变自动联动,实现“改一个属性、下一帧自动重绘”。在 Canvas/WebGL 渲染器下,请记住把透明度写进rgba()颜色字符串以获得等效效果。相关源码与文档可继续深入 src/effects/stop.js、src/effects/gradient.js 以及 wiki/docs/effects/gradient/README.md。

  • 图形学
  • 前端

【免费下载链接】two.js

A renderer agnostic two-dimensional drawing api for the web

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

相关推荐

上一篇:MMPose 中的 CPM(Convolutional Pose Machines)骨干网络:多阶段顺序预测与中间监督的源码级解析
下一篇:Samloader深度解析:三星固件下载与解密的Python技术实践

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

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

Atlas 300V 24G推理卡部署YOLO全流程与踩坑指南

前几天有个做安防项目的朋友发了一张截图给我&#xff0c;问“Atlas 300V 24G到底算不算运算加速卡&#xff1f;能不能拿来跑YOLO&#xff1f;”这个问题我其实被问过很多次。很多人从CUDA那套习惯转过来&#xff0c;第一次接触华为的昇腾设备&#xff0c;容易拿GPU的思维去套A…

作者头像 李华