p5.js 网页可访问性深度解析textOutput / gridOutput / describe 的架构与源码实现【免费下载链接】p5.jsp5.js is a client-side JS platform that empowers artists, designers, students, and anyone to learn to code and express themselves creatively on the web. It is based on the core principles of Processing. Looking for p5.js 2.0? http://beta.p5js.org项目地址: https://gitcode.com/GitHub_Trending/p5/p5.js本技术指南以 p5.js 仓库中的 contributor_docs/zh-Hans/web_accessibility.md 为骨架面向贡献者、维护者以及希望在创作中兼顾屏幕阅读器用户的开发者完整讲解 p5.js 可访问性功能的双轨架构由库自动生成的基本形状输出textOutput()、gridOutput()与用户自行编写的画布描述describe()、describeElement()。读完本文你将掌握这四个 API 的用法、LABEL/FALLBACK显示模式的区别并能够顺着 src/accessibility/ 目录下的源码理解从形状绘制到屏幕阅读器输出的完整数据流为后续参与该模块的贡献打下基础。概述为什么画布需要可访问性HTML 的canvas元素本质上是位图bitmap它只保存像素数据无法向屏幕阅读器提供画布上画了什么的结构化信息。因此一个画了红色圆形和蓝色正方形的草图在屏幕阅读器用户听来可能只是一张图片甚至毫无内容。为解决这个问题p5.js 提供了两类可访问性能力库生成的、针对基本形状的可访问输出——使用textOutput()和gridOutput()由 p5.js 自动分析画布上的基本形状并生成文本 / 网格描述用户生成的画布描述——使用describe()和describeElement()由创作者自己为画布或画布中的元素撰写描述文字。两类能力均以fallback仅屏幕阅读器可见和label可见于画布旁两种方式呈现下文逐一展开。一、库生成的可访问输出textOutput() 与 gridOutput()textOutput()把形状讲成一段话textOutput()为画布生成三段式文本输出相关实现见 src/accessibility/textOutput.js画布总描述包含画布大小、画布颜色与元素数量。例如您的输出是一个大小为 400x400 像素的蓝色画布其中包含以下 4 个形状源码模板见 src/accessibility/textOutput.js 中_textSummary()生成的Your output is a, ${width} by ${height} pixels, ${background} canvas containing the following ${numShapes} shapes:。形状列表逐条描述每个形状的颜色、位置与面积例如左上角的橙色椭圆覆盖画布的 1%每条都可以被单独选中以获取更多细节。形状详情表格以表格形式描述形状、颜色、位置、坐标与面积例如橙色椭圆位置左上角面积2。一个完整的用法示例摘自 src/accessibility/outputs.js 中textOutput()的文档示例function setup() { // 开启文本输出。 textOutput(); // 绘制几个形状。 background(200); fill(255, 0, 0); circle(20, 20, 20); fill(0, 0, 255); square(50, 50, 50); // 再补充一段画布总描述。 describe(A red circle and a blue square on a gray background.); }在draw()中使用时p5.js 会随每一帧画面更新输出例如让一个红色圆形从左向右移动的动态描述持续保持同步源码示例见 src/accessibility/outputs.js 第 75–115 行。gridOutput()把形状摆进空间网格gridOutput()将画布内容按每个形状的空间位置布局成一个 HTML 表格相关实现见 src/accessibility/gridOutput.js画布简要描述在表格之前输出包含背景颜色、画布大小、对象数量与对象类型例如淡紫蓝色画布尺寸为200x200包含4个对象 - 3个椭圆和1个矩形源码模板见_gridSummary()生成的${background} canvas, ${width} by ${height} pixels, contains ... shapes: ...。空间网格每个元素根据其位置被放入表格的对应单元格单元格内描述该位置的颜色与形状类型例如橙色椭圆。每个单元格都可单独选中以获取更多细节。元素列表以列表形式描述形状、颜色、位置与面积例如橙色椭圆 位置左上角 面积1%。与textOutput()的表格充当列表不同gridOutput()的表格充当网格这是两者在输出结构上的本质区别源码注释见 src/accessibility/outputs.js 第 20–24、143–149 行。function setup() { // 开启网格输出。 gridOutput(); background(200); fill(255, 0, 0); circle(20, 20, 20); fill(0, 0, 255); square(50, 50, 50); describe(A red circle and a blue square on a gray background.); }显示模式FALLBACK 与 LABEL两个函数都接受一个可选的display参数决定输出的显示方式源码见 src/accessibility/outputs.js 中textOutput()/gridOutput()的实现参数值行为适用场景FALLBACK输出只对屏幕阅读器可见作为画布的备用fallback内容默认模式正式发布LABEL额外创建一个与画布相邻的div输出对所有人可见开发调试传入LABEL时如textOutput(LABEL)、gridOutput(LABEL)p5.js 会同时生成fallback label两份输出。对屏幕阅读器用户而言LABEL会造成不必要的冗余朗读因此官方建议仅在开发过程中使用LABEL在发布或与屏幕阅读器用户共享草稿前将其删除源码注释见 src/accessibility/outputs.js 第 26–32、155–161 行。二、用户生成的可访问描述describe() 与 describeElement()describe()为整张画布写描述describe()为画布创建供屏幕阅读器访问的整体描述实现见 src/accessibility/describe.js第一个参数text必填描述画布的字符串。第二个参数display可选决定显示方式取值FALLBACK默认仅屏幕阅读器可见或LABEL在画布旁创建一个附加的描述div。所有描述默认都成为画布元素的子 DOM 的一部分只有传入LABEL时才在画布旁生成可见div。示例function setup() { background(pink); // 绘制一颗心。 fill(red); noStroke(); circle(67, 67, 20); circle(83, 67, 20); triangle(91, 73, 75, 95, 59, 73); // 为整张画布补充描述默认 FALLBACK仅屏幕阅读器可见。 describe(A pink square with a red heart in the bottom-right corner.); // 调试阶段可改用 LABEL 让描述可见。 // describe(A pink square with a red heart in the bottom-right corner., LABEL); }describe()的实现由两个辅助函数支撑_descriptionText()校验文本不是LABEL或FALLBACK并确保文本以标点结尾。若字符串不以.、,、;、?、!结尾则在末尾自动补一个.返回处理后的文本源码见 src/accessibility/describe.js 第 293–309 行。_describeHTML()创建画布的备用 HTML 结构。FALLBACK模式下创建一个带roleregion与aria-labelCanvas Description的容器并填充描述pLABEL模式下则在画布元素之后插入一个类名为p5Label的可见div源码见第 316–380 行。describeElement()为单个元素写描述describeElement()为绘图元素或一组共同产生含义的形状创建描述。所谓元素可以是单个形状也可以是多个形状组合而成的整体例如几个重叠的圆共同构成一只眼睛第一个参数name必填元素名称字符串。第二个参数text必填元素描述字符串。第三个参数display可选决定显示方式规则与describe()一致传入LABEL时创建相邻于画布的附加元素描述div。function setup() { background(pink); // 描述第一个元素并绘制它。 describeElement(Circle, A yellow circle in the top-left corner.); noStroke(); fill(yellow); circle(25, 25, 40); // 描述第二个元素并绘制它。 describeElement(Heart, A red heart in the bottom-right corner.); fill(red); circle(66.6, 66.6, 20); circle(83.2, 66.6, 20); triangle(91.2, 72.6, 75, 95, 58.6, 72.6); // 再给整张画布一个总描述。 describe(A red heart and yellow circle over a pink background.); }describeElement()同样由辅助函数支撑源码见 src/accessibility/describe.js_elementName()校验元素名称不是LABEL或FALLBACK并确保名称以冒号:结尾若以.、;、,结尾则替换为:。_descriptionText()与describe()共用负责标点收尾与关键字校验。_describeElementHTML()创建元素描述的 HTML 结构。元素描述以表格形式组织——每个元素一行行头单元格th scoperow为元素名称相邻单元格td为描述文本元素的特殊字符会被从 HTMLid中剔除以保证合法源码见第 232–284、404–485 行。三、底层架构outputs.js 与可访问输出的数据流虽然textOutput()和gridOutput()的对外入口位于 src/accessibility/outputs.js但整个输出体系由分布在多个文件中的函数协同创建与更新。这一节梳理完整的数据流。ingredients所有输出的数据中枢_createOutput()会初始化this.ingredients对象源码见 src/accessibility/outputs.js 第 280–290 行它存储了所有输出的数据this.ingredients { shapes: {}, // 形状集合按形状类型分组 colors: { background: white, fill: white, stroke: black }, // 颜色名称 pShapes: , // 上一次形状集合的字符串快照 pBackground: // 上一次背景颜色的快照 };此外若this.dummyDOM不存在_createOutput()会将其创建为画布元素的父节点dummyDOM保存了body内相关 DOM 元素的 HTML 集合后续所有查询与插入都基于它进行。核心函数职责一览函数职责源码位置textOutput()将this._accessibleOutputs.text置为true并调用_createOutput(textOutput, Fallback)若传入LABEL则同时置textLabel并创建 Label 输出src/accessibility/outputs.js 第 117–134 行gridOutput()将this._accessibleOutputs.grid置为true并创建 Fallback 输出传入LABEL时同时创建 Label 输出第 246–263 行_createOutput()为所有可访问输出创建 HTML 结构输出类型text/grid与显示方式Fallback/Label不同则结构不同同时初始化ingredients与dummyDOM第 280–378 行_updateAccsOutput()在setup()/draw()结束时调用仅当ingredients与当前输出不同时才调用_updateGridOutput()/_updateTextOutput()避免频繁刷新给屏幕阅读器造成负担第 382–404 行_addAccsOutput()返回this._accessibleOutputs.grid \|\| this._accessibleOutputs.text供外部判断可访问输出是否开启第 266–277 行_accsBackground()在background()结束时调用重置ingredients.shapes若背景色与先前不同则通过_rgbColorName()更新颜色名称第 408–419 行_accsCanvasColors()在fill()/stroke()结束时调用将填充与描边颜色保存到ingredients.colors.fill/.stroke并取颜色名第 422–436 行_accsOutput()构建ingredients.shapes所有用于输出的形状数据在基本形状函数结束时被调用第 439–500 行形状数据的采集_accsOutput() 与辅助函数_accsOutput()是形状数据进入ingredients的唯一入口。它会做形状归一化宽高相等的椭圆记为circle、宽高相等的矩形记为square并针对不同形状收集颜色、面积、位置等字段线段与点使用描边色其余使用填充色。当同一类型的相同形状已存在时通过JSON.stringify比较去重避免重复输出源码见第 439–500 行。根据调用它的形状不同_accsOutput()会调用若干非原型辅助函数位于 src/accessibility/outputs.js 内_getMiddle(f, args)返回矩形、弧形、椭圆、三角形、四边形与线段的中心点或质心。矩形/椭圆/弧/圆/正方形取(x w/2, y h/2)三角形取三顶点坐标平均四边形取四顶点平均线段取两端点中点第 503–528 行。_getPos(x, y)返回形状在画布上的方位描述如top left、mid left、bottom middle、middle等。它基于画布宽高 40% / 60% 的阈值将画布划分为九宫格第 531–562 行。_canvasLocator(args, canvasWidth, canvasHeight)将形状映射到画布的 10×10 网格返回{ locX, locY }网格坐标第 565–580 行。_getArea(objectType, shapeArgs)返回形状面积占画布总面积的百分比。对不同形状使用不同公式弧形使用弧度占比计算扇形面积并对OPEN/CHORD模式修正三角形区域椭圆/圆形使用 π 近似四边形与三角形使用鞋带公式shoelace formula并会结合像素密度与当前变换矩阵反算画布面积以得到百分比第 583–673 行。函数在库中的真实调用链当this._accessibleOutputs.text或this._accessibleOutputs.grid为true时p5.js 库中多个函数会调用上述可访问性函数_accsOutput()在以下形状函数中被调用见 src/shape/2d_primitives.jsp5.prototype.triangle()第 1309 行矩形渲染路径_renderRect()对应rectangle第 1237 行p5.prototype.quad()对应quadrilateral第 978 行p5.prototype.point()第 824 行p5.prototype.line()第 641 行椭圆渲染路径_renderEllipse()对应ellipse第 503 行p5.prototype.arc()第 321 行_updateAccsOutput()在以下生命周期节点被调用p5.prototype.redraw()、p5.prototype.resizeCanvas()及_setup见 src/core/structure.js 第 344 行、src/core/rendering.js 第 262 行、src/core/main.js 第 269 行确保在setup()与draw()结束时同步输出。_accsCanvasColors()在渲染器的stroke()与fill()中被调用见 src/core/p5.Renderer2D.js 第 223、241 行通过可选链?.(调用以保证兼容。_accsBackground()在p5.Renderer2D.prototype.background()中被调用见 src/core/p5.Renderer2D.js 第 184 行。差分更新机制避免屏幕阅读器负担_updateAccsOutput()的关键设计是仅在内容变化时更新源码见 src/accessibility/outputs.js 第 382–404 行它比较JSON.stringify(this.ingredients.shapes)与快照this.ingredients.pShapes、背景色与pBackground只有不相同时才写入新快照并调用各输出的更新函数。由于_updateAccsOutput()只在setup()与draw()结束时被调用且形状没有变化时不触发更新从而避免了每一帧都重写 DOM 给屏幕阅读器带来的过多干扰。四、textOutput.js 与 gridOutput.js输出内容的构建textOutput.jssrc/accessibility/textOutput.js 包含更新文本输出的全部函数核心是_updateTextOutput()它由outputs.js中的_updateAccsOutput()在_accessibleOutputs.text或_accessibleOutputs.textLabel为true时调用。_updateTextOutput()使用this.ingredients构建文本输出及 Label 版本的三部分内容——摘要、形状列表、形状详情表格并仅在内容与当前 DOM 不同时更新源码第 11–51 行。构建过程由三个非原型辅助函数支撑_textSummary(numShapes, background, width, height)构建摘要文本根据形状数量区分单复数表述shape/shapes。_shapeDetails(idT, ingredients)构建形状详情表格每个形状一行线段行额外给出length像素点行不包含面积字段其余行包含area百分比。_shapeList(idT, ingredients)构建形状列表每条li内含一个锚点a href#...链接到表格中对应行使列表项可被单独选中获取详情。gridOutput.jssrc/accessibility/gridOutput.js 包含更新网格输出的全部函数核心是_updateGridOutput()在_accessibleOutputs.grid或_accessibleOutputs.gridLabel为true时被调用。它使用this.ingredients构建摘要、网格与形状列表三部分同样只在内容变化时更新源码第 11–51 行辅助函数包括_gridSummary(numShapes, background, width, height)构建摘要其中形状数量与类型列表如3 ellipses 1 rectangle由_gridShapeDetails()统计返回。_gridMap(idT, ingredients)构建 10×10 的 HTML 表格网格将每个形状按其_canvasLocator()计算出的{ locX, locY }放入对应单元格同一单元格存在多个形状时叠加填充线段在网格中以颜色 line midpoint表示源码第 54–107 行。_gridShapeDetails(idT, ingredients)构建形状列表每行包含形状的颜色、类型、位置location线段附length其余形状附area百分比同时返回总形状数与按类型统计的列表文本。五、color_namer.js颜色的可读命名在生成可访问输出时为画布与形状命名颜色至关重要。src/accessibility/color_namer.js 中的_rgbColorName()接收 RGBA 值并返回人类可读的颜色名称它被 src/accessibility/outputs.js 中的_accsBackground()与_accsCanvasColors()调用。其工作流程源码见 src/accessibility/color_namer.js 第 622–712 行使用color_conversion._rgbaToHSBA()将 RGBA 转为 HSBHSV值调用_calculateColor()计算颜色名称将色相hue舍入到 5 的倍数0、5、10……95将饱和度与亮度舍入到{0, 0.5, 1}三档对舍入后为白色h0, s0, b1的临界值先用colorExceptions数组匹配从白到灰的微妙色阶如gray、light pink无匹配则返回white其余颜色与colorLookUp数组包含 black、gray、white、red、crimson、brick red、brown、peach、magenta 等数百个色名条目逐一比较返回匹配的色名。据文档说明_calculateColor()函数源自 color-namer 库作为 2018 年 Processing Foundation fellowship 的一部分、并与盲人屏幕阅读器专家用户协商开发。文档同时指出部分灰色的阴影目前未被正确命名该函数有待更新更新时务必通过注释解释每行代码以保证贡献者可读性。从源码看当前colorExceptions仅收录了 4 个灰度/浅粉例外第 14–39 行覆盖面有限这正是文档所述问题的根源也是社区可以继续改进的方向。六、模块注册与测试可访问性模块通过 src/accessibility/index.js 统一注册describe、gridOutput、textOutput、outputs、colorNamer五个 addon 全部经由p5.registerAddon()挂载到 p5 原型上随主库一起加载。仓库中的单元测试位于 test/unit/accessibility/ 目录包含describe.js与outputs.js覆盖了describe()、describeElement()、textOutput()、gridOutput()的调用与输出行为是验证本文所述行为的直接依据。七、限制与注意事项WebGL 模式暂不支持textOutput()/gridOutput()在_updateTextOutput()与_updateGridOutput()的开头均有if (this._renderer this._renderer.isP3D)检查见 src/accessibility/textOutput.js 第 12–18 行、src/accessibility/gridOutput.js 第 12–18 行命中时打印textOutput() does not yet work in WebGL mode.或 gridOutput 版本并直接返回。因此这两个函数目前仅适用于 2D 渲染模式。LABEL会造成屏幕阅读器冗余LABEL模式下输出对所有人可见屏幕阅读器会重复朗读官方建议只在开发调试时使用发布前移除。颜色命名的精度限制color_namer.js通过将 HSB 值离散化色相取 5 的倍数、饱和度/亮度取三档与固定查找表比较来命名颜色因此部分灰色阴影与细微色差无法被准确命名属于已知待改进项。性能与负担可访问输出只在setup()/draw()结束时、且内容实际发生变化时才更新 DOM这是为了避免给屏幕阅读器带来不必要的负担在形状数量较大的草图中应留意这一机制的刷新频率。描述文本的自动标点describe()与describeElement()会自动为描述补全标点描述以.结尾、元素名以:结尾以保证屏幕阅读器的朗读停顿自然同时二者都拒绝使用LABEL/FALLBACK作为描述文本或元素名称会抛出错误。结语如何继续深入本文从 contributor_docs/zh-Hans/web_accessibility.md 出发沿textOutput()/gridOutput()/describe()/describeElement()四个 API 梳理了 p5.js 可访问性功能的完整架构outputs.js负责数据采集与 HTML 骨架创建textOutput.js/gridOutput.js负责内容构建describe.js负责用户自定义描述color_namer.js负责颜色命名而ingredients差分更新机制保证了屏幕阅读器的低负担体验。若你想亲自验证或进一步改进该模块例如完善color_namer.js的灰色命名、为 WebGL 模式补充支持可以从 src/accessibility/ 目录与 test/unit/accessibility/ 测试入手并结合仓库中 contributor_docs/ 下的贡献指南继续探索。【免费下载链接】p5.jsp5.js is a client-side JS platform that empowers artists, designers, students, and anyone to learn to code and express themselves creatively on the web. It is based on the core principles of Processing. Looking for p5.js 2.0? http://beta.p5js.org项目地址: https://gitcode.com/GitHub_Trending/p5/p5.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考