ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

p5.js 画布无障碍:describe()、describeElement()、textOutput() 与 gridOutput() 的实现与演进

2026/9/13 1:30:16 拓冰建站 浏览量
p5.js 画布无障碍:describe()、describeElement()、textOutput() 与 gridOutput() 的实现与演进 p5.js 画布无障碍describe()、describeElement()、textOutput() 与 gridOutput() 的实现与演进【免费下载链接】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 社区 2020 年 Google Summer of CodeGSoC项目p5.js accessibility and canvas descriptions为核心脉络系统讲解 p5.js 将p5.accessibility插件合并进主库、并为画布引入四类无障碍描述能力的完整过程。读者将掌握describe()、describeElement()两个用户自定义描述函数与textOutput()、gridOutput()两个库自动生成输出函数的用法、参数语义、底层 HTML 结构与实现原理并了解它们如何让盲人和低视力用户通过屏幕阅读器访问 p5.js 画布内容。一、项目背景从编辑器特性、插件到库内置的演进canvas 本质上是像素网格默认不向屏幕阅读器提供任何关于其上图形的语义信息。要让依赖屏幕阅读器的用户理解画布内容必须提供文本形式的替代内容。p5.js 的无障碍能力经历了三个阶段编辑器特性阶段早期工作由 Claire Kearney-Volpe、Taeyoon Choi 与 Atul Varma 发起随后与 Mathura Govindarajan 在 p5.js 编辑器中实现了三种可访问画布输出文本输出、网格输出与声音输出。插件阶段2018通过 Processing Foundation FellowshipClaire、Mathura 与本文主角 Luis Morales-Navarro 开发了p5.accessibility.js插件把编辑器中的无障碍输出能力带给任意引入该插件的 sketch。但插件仍要求用户额外引入一个文件并修改 HTML。库内置阶段2020 GSoC在 2019 年 p5.js Contributors Conference 上社区明确了短期行动路线——提供让用户自行书写描述的函数并将插件合并进 p5.js 主库。Luis Morales-Navarro 与导师 Kate Hollenbach 在 GSoC 2020 期间完成了这一目标。需要注意的是合并时团队并没有简单升级旧插件旧插件依赖monkey patching、实体与拦截器机制升级成本高因此团队选择在 p5.js 内部重建文本输出与网格输出功能使输出与主库完全集成。合并后所有描述都以子元素形式插入到canvas元素内部fallback 模式或紧邻canvas的div中label 模式。二、核心贡献两个关键 Pull Request该项目的工作集中在两个 PR 上它们共同构成了 p5.js 无障碍 API 的骨架PR内容落地位置Add describe() and describeElement()新增describe()与describeElement()两个函数以及对应测试、文档与示例src/accessibility/describe.js、test/unit/accessibility/describe.jsMerge Accessibility Add-On into p5.js新增textOutput()与gridOutput()以及创建/更新输出的辅助函数、测试、文档与示例src/accessibility/outputs.js、src/accessibility/textOutput.js、src/accessibility/gridOutput.js、test/unit/accessibility/outputs.js从源码结构看无障碍模块通过 src/accessibility/index.js 统一注册describe、gridOutput、textOutput、outputs、colorNamer五个 addon 被依次p5.registerAddon()注册到 p5 原型上。更详细的实现说明见 contributor_docs/web_accessibility.md。三、库自动生成的无障碍输出textOutput() 与 gridOutput()textOutput()与gridOutput()是库自动生成的输出用户只需在 sketch 中调用一次p5.js 就会在渲染阶段自动采集画布上的基本图形信息生成屏幕阅读器可读的描述。3.1 基本用法与输出效果以官方文档的示例代码为例function setup() { createCanvas(400, 400); } function draw() { background(#ccccff); textOutput(); fill(orange); ellipse(100, 100, 50); fill(fuchsia); rect(300, 300, 50, 50); }调用textOutput()后画布会获得一段总体描述包含画布尺寸、画布颜色与元素数量Your output is a, 400 by 400 pixels, lavender blue canvas containing the following 2 shapes:随后是按颜色、位置与面积描述每个图形的列表orange circle at top left covering 1% of the canvas. fuchsia square, at bottom right, covering 2% of the canvas.列表中的每个元素都可以被选中以获取更多细节同时还会生成一张表格逐行描述每个图形的形状、颜色、位置、坐标与面积orange circle locationtop left area1% fuchsia square location bottom right area 2%gridOutput()则把画布内容布局为一张由 HTMLtable构成的网格每个图形的网格位置对应其在画布上的空间位置。表格之前有一段简短描述包含背景色、画布尺寸、对象数量与对象类型lavender blue canvas, 400 by 400 pixels, contains 2 shapes: 1 circle 1 square每个图形的描述颜色与形状类型如orange circle、fuchsia square被放入表格中与其画布位置对应的单元格可单独选中获取详情表格之后还有一份列出形状、颜色、位置与面积的清单orange circle, location top left, area 1 %fuchsia square, location bottom right, area 2 %3.2 LABEL 与 FALLBACK 两种显示模式两个函数都接受可选的display参数取值由 src/core/constants.js 定义FALLBACK默认描述只嵌入canvas元素内部仅对屏幕阅读器可见是发布 sketch 时推荐的模式。LABEL在canvas元素旁边额外创建一个可见div内容与画布内的描述相同方便非屏幕阅读器用户在编码过程中直观看到输出。官方明确建议LABEL只应作为开发调试手段发布或分享给屏幕阅读器用户前应移除否则会对他们造成不必要的冗余朗读。例如textOutput(LABEL)会在画布下方显示可见的文本输出3.3 底层实现outputs.js 与跨模块调用链虽然textOutput()与gridOutput()的公开入口位于 src/accessibility/outputs.js但输出的创建与更新依赖分布在整个库中的多个辅助函数。这正是输出功能完全集成到库中的含义。公开方法textOutput()将this._accessibleOutputs.text置为true并调用this._createOutput(textOutput, Fallback)gridOutput()同理。传入LABEL时还会额外把textLabel/gridLabel置为true并创建对应的 Label 结构。私有辅助方法均在 outputs.js 中_createOutput()创建所有无障碍输出的 HTML 结构并初始化this.ingredients——这是一个保存输出所需全部数据的对象包括shapes、colors以及记录画布上一帧形状字符串的pShapes同时创建this.dummyDOMbody内 DOM 元素的 HTMLCollection 引用。_updateAccsOutput()在setup()与draw()结束时被调用。只有当this.ingredients与当前输出不一致时才触发各输出的更新方法_updateTextOutput、_updateGridOutput。这种仅在帧末、仅在数据变化时的更新策略避免了div内容持续刷新导致屏幕阅读器无法朗读画布描述。_addAccsOutput()初始化this._accessibleOutputs当grid或text为true时返回true。_accsBackground()在background()末尾被调用重置this.ingredients.shapes并通过_rgbColorName()记录背景色名。_accsCanvasColors()在fill()与stroke()末尾被调用把当前填充色与描边色存入ingredients.colors.fill/ingredients.colors.stroke。_accsOutput()构建this.ingredients.shapes是所有基本图形函数在末尾调用的采集入口必要时调用_getMiddle()返回矩形、圆弧、椭圆、三角形、四边形的质心、_getPos()返回如 top left、mid right 的位置描述、_canvasLocator()把形状映射到 10×10 网格、_getArea()返回形状面积占画布总面积的百分比。从 src/shape/2d_primitives.js 可以看到_accsOutput()的实际接入点arc、ellipse、line、point、quadrilateral、rectangle、triangle这七类基本图形在绘制后都会调用它。此外_updateAccsOutput()在 src/core/main.js、src/core/rendering.jsresizeCanvas与 src/core/structure.jsredraw中被调用_accsBackground()/_accsCanvasColors()在 src/core/p5.Renderer2D.js 的background()、stroke()、fill()实现中被调用。值得注意的细节_accsOutput()会把宽高相等的ellipse归一为circle、宽高相等的rectangle归一为square因此输出描述中会使用更口语化的circle/square而非几何学命名。文本输出更新模块src/accessibility/textOutput.js 的核心方法是_updateTextOutput()由_updateAccsOutput()在text或textLabel为真时调用。它借助三个文件内私有函数构建内容_textSummary()生成摘要、_shapeDetails()生成形状详情表格、_shapeList()生成形状列表。网格输出更新模块src/accessibility/gridOutput.js 的核心方法是_updateGridOutput()由_updateAccsOutput()在grid或gridLabel为真时调用。辅助函数包括_gridSummary()摘要、_gridMap()把形状位置映射到 10×10 网格表、_gridShapeDetails()形状清单每行含形状详情。颜色命名模块src/accessibility/color_namer.js 提供_rgbColorName()接收 RGBA 值借助p5.color_conversion._rgbaToHSBA()转换为 HSB再用_calculateColor()与内置colorLookUp数组中的 HSB 值比较返回最接近的颜色名称字符串。该函数被_accsBackground()与_accsCanvasColors()调用是输出中lavender blue、orange等自然语言颜色名的来源。colorLookUp中同时存在colorExceptions如灰阶与浅粉的例外映射说明颜色命名经过与盲人屏幕阅读器专家用户的协商设计。WebGL 限制_updateTextOutput()与_updateGridOutput()在检测到this._renderer.isP3DWebGL 模式时会在控制台输出textOutput() does not yet work in WebGL mode./gridOutput() does not yet work in WebGL mode.并直接返回即这两个库自动输出目前仅支持 2D 渲染模式src/accessibility/textOutput.js、src/accessibility/gridOutput.js。四、用户自定义描述describe() 与 describeElement()除了库自动生成输出GSoC 项目还带来了两个让作者亲自撰写描述的函数二者都定义在 src/accessibility/describe.js 中。4.1 describe()整幅画布的描述describe()为整个画布创建由 sketch 作者定义的屏幕阅读器描述第一个参数text画布描述字符串必填第二个参数display可选取LABEL或FALLBACK默认FALLBACK。传入LABEL时会在canvas旁创建可见div。function setup() { background(pink); fill(red); noStroke(); circle(67, 67, 20); circle(83, 67, 20); triangle(91, 73, 75, 95, 59, 73); describe(A pink square with a red heart in the bottom-right corner., LABEL); }describe()背后的支撑方法_descriptionText()先校验文本不能是LABEL或FALLBACK否则抛出Error再检查文本是否以.、,、;、?、!结尾若不是则自动补一个.以改善屏幕阅读器的朗读断句src/accessibility/describe.js_describeHTML()为画布创建 fallback HTML 结构当第二个参数为LABEL时在canvas元素旁创建包含描述文本的divsrc/accessibility/describe.js。从 src/accessibility/describe.js 的实现看describe()会在this.descriptions.fallback/this.descriptions.label已存在时做差分更新仅当innerHTML与当前文本不同才重写这与_updateAccsOutput()的节流策略一致都是为了不干扰屏幕阅读器。此外 test/unit/accessibility/describe.js 中的用例验证了这些行为例如describe(a)会生成a.自动补句号、以.或!/?结尾的文本不会重复补标点、describe(label)会抛出description should not be LABEL or FALLBACK错误等。4.2 describeElement()成组元素的描述describeElement()为共同构成意义的一组图形创建屏幕阅读器描述——例如由多行代码绘制出的自定义心形。其参数第一个参数name元素名称如 Heart第二个参数text元素描述如 A red heart in the bottom-right corner.第三个参数display可选LABEL或FALLBACK默认FALLBACK。function setup() { background(pink); noStroke(); describeElement(Heart, A red heart in the bottom-right corner., LABEL); 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., LABEL); }注意示例中describeElement()在绘制图形之前调用这说明描述与绘制顺序无关作者可以在任何位置声明元素。describeElement()的支撑方法_elementName()校验名称不能是LABEL或FALLBACK并把名称规范为以冒号:结尾若以.、;、,结尾则替换为:否则追加:以符合表格行标题的朗读习惯src/accessibility/describe.js_descriptionText()与describe()共用同一标点补全逻辑_describeElementHTML()创建画布 fallback 结构LABEL模式下在画布旁创建包含元素描述的divsrc/accessibility/describe.js。从实现细节看describeElement()会把元素名中的非字母数字字符剔除后用作 HTML id并将元素名 描述组装为th scoperowtd的表格行结构src/accessibility/describe.js。它还维护this.descriptions.fallbackElements/labelElements对象来逐元素做差分更新。五、组合使用与实战建议describe()、describeElement()、textOutput()、gridOutput()四者可以在同一 sketch 中自由组合库自动输出负责画布上有什么形状、在哪里、占多大面积用户描述负责这些形状在语义上是什么如一颗心、一个图表、一张脸。二者互补且 DOM 结构上也有协调fallback 模式下描述容器 id 为canvasId_Description输出容器 id 为canvasIdaccessibleOutput_describeHTML()与_createOutput()都会先检查对方是否已存在从而把描述p与输出div按正确顺序排布避免相互覆盖见 src/accessibility/describe.js 与 src/accessibility/outputs.js。对于动态画布describe()支持模板字符串实时更新描述例如官方示例describe(\A green circle at (${x}, 50) moves from left to right on a gray square.)可在draw()中随帧更新textOutput() 也会在每帧末尾按需刷新形状清单。实战建议依据官方文档与源码行为整理面向屏幕阅读器用户发布时始终使用默认的FALLBACK模式LABEL仅用于本地开发调试。为每个有意义的多图形组合调用一次describeElement()并为整幅画布调用一次describe()作为兜底描述。描述文本尽量以句号、问号等标点结尾库会自动补全但作者主动写全标点更利于朗读节奏。当前textOutput()/gridOutput()不支持 WebGL 模式控制台会提示WebGL sketch 请依赖describe()/describeElement()提供无障碍信息。可同时启用textOutput()与gridOutput()源码显示二者会按文本输出在前、网格输出在后的顺序共存于accessibleOutput容器中屏幕阅读器用户可自行选择更习惯的形态。六、测试与验证无障碍功能配有专门的单元测试test/unit/accessibility/describe.js覆盖describe()与describeElement()的标点补全、LABEL/FALLBACK 校验、HTML 结构创建与相邻画布插入等行为例如测试describe(a)产出a.、describe(LABEL)抛错、调用顺序不影响容器创建等。test/unit/accessibility/outputs.js覆盖textOutput()/gridOutput()的开启逻辑、ingredients数据采集与输出结构。这些测试与模块源码共同保证了四个 API 在 2D 渲染模式下行为稳定可作为二次开发或贡献时的参考起点。七、未来方向项目总结中列出的后续工作包括编写如何描述画布内容的教程在参考文档中改用describe()替代依赖alt生成屏幕阅读器描述考虑把describe()加入官网与编辑器的模板升级用屏幕阅读器使用 p5.js的教程以及调整编辑器中的无障碍设置方式。更重要的是社区认为这些特性需要与社区成员尤其是盲人初学者进行迭代测试并持续产出支持无障碍的教学资源——这提示读者无障碍不是一次性功能而是需要与真实用户共同打磨的长期工程。【免费下载链接】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),仅供参考