p5.js 无障碍与包容性承诺解读:从社区宣言到 describe()、textOutput() 与友好错误系统的工程落地
【免费下载链接】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 作为面向艺术家、设计师与编程学习者的客户端 JavaScript 创意编程平台,其无障碍(accessibility)工作不仅停留在口号层面,而是以明确的项目级承诺、可落地的 API 设计与持续的社区治理来推进。本文以 contributor_docs/es/access.md(英文版见 contributor_docs/access.md)这份社区宣言为骨架,结合 contributor_docs/web_accessibility.md 与 src/accessibility 目录下的真实源码实现,系统梳理 p5.js 的"无障碍优先"原则、四类屏幕阅读器可访问输出的用法与内部机制、友好错误系统(FES)、多语言本地化等落地实践,帮助读者理解:一个开源项目如何把"为被排斥群体服务"的价值判断,翻译成一行行可运行的代码与可操作的贡献准则。
一、承诺的起点:只接受能"增加无障碍"的新特性
1.1 2019 贡献者大会的转向
根据 access.md 的记载,在 2019 年 p5.js 贡献者大会上,p5.js 做出了一项关键承诺:只接受能够增加无障碍(inclusion 与 accessibility)的新特性请求,不再接受与此目标相悖的特性请求。这一承诺并非一次性表态,而是被写入项目核心价值体系,与官方社区声明(Community Statement)保持一致。
这份承诺的完整表述包含三个递进层次:
- 只增不减:新功能必须服务于"扩大可及性",否则不进入主线;
- 修复优先:项目愿意修复代码库中任何位置的 Bug,不因模块归属而区别对待;
- 一致性即无障碍:工具的一致性本身就能降低新手的使用门槛,因此 API 的一致性被视作无障碍工作的一部分。
access.md 明确给出的两个特性示例,如今都能在源码中找到对应实现:
| 示例 | 类型 | 仓库中的实现位置 |
|---|---|---|
| 面向低性能硬件的性能提升(framebuffer 的绘制/读取支持) | 性能即无障碍 | src/webgl/p5.Framebuffer.js |
新增arcVertex()以配合beginShape()/endShape()绘制圆弧 | API 一致性 | src/shape/custom_shapes.js |
从源码结构看,arcVertex()已作为自定义形状模块的一部分实现,这说明文档中列举的"示例性特性"并非空头支票,而是已经进入代码库的真实能力。
1.2 什么是 p5.js 语境下的"access"
access.md 特别澄清:增加 access不等于扩大社区的总人数,而是让 p5.js 对"因结构性压迫而被排除在社区之外的人"持续可用、可及。这一承诺同时覆盖三层对象:
- p5.js 提供的工具与平台本身;
- p5.js 领导层的构成、决策与行动;
- 社区文化——明确抵制科技行业"速度、增长、竞争"的惯性,优先"刻意、缓慢、调适与负责"作为集体关怀的行动。
宣言以非穷尽式列表列出了 access 的服务对象,包括:非英语使用者、黑人/原住民/有色人种及边缘族裔、LGBTQIA+ 群体、跨性别与非二元性别群体、盲人/聋人/听障/残障/神经多样性/慢性病患者、低收入或缺乏文化与金融资本的人群、几乎没有开源与创意编程经验的人群、不同教育背景者、所有年龄段(含儿童与老人)、不同技术条件与网络条件的人群、不同宗教背景的人群,以及这些群体的所有交叉组合。
值得注意的术语细节(文档脚注明确说明):
- "S/sordo"(聋人):大写 S 指文化意义上的聋人群体(Deaf community),小写 s 指听力学意义上的失聪者;
- "person-first vs identity-first":残障群体内部对"人先语言"与"身份先语言"存在偏好差异,项目承认并尊重这种分歧;
- "语言帝国主义":指英语等语言因帝国扩张与全球化而持续支配、挤压母语的现象——这正是翻译工作被列为无障碍举措的原因。
二、屏幕阅读器可访问的画布输出:textOutput() 与 gridOutput()
2.1 问题背景:canvas 天生对屏幕阅读器不友好
<canvas>本质上是像素网格,它不会向屏幕阅读器提供任何关于"画了什么形状"的语义信息。p5.js 的无障碍方案分为两大类:
- 库自动生成(library-generated):
textOutput()与gridOutput(),自动描述画布上的基础形状; - 用户自定义(user-generated):
describe()与describeElement(),由创作者撰写描述文本。
所有描述内容都会被插入为<canvas>元素的子元素(fallback 模式)或紧邻的可见<div>(label 模式)。完整的机制讲解见 contributor_docs/web_accessibility.md,源码集中在 src/accessibility 目录,入口为 src/accessibility/index.js,其中注册了五个 addon:describe、gridOutput、textOutput、outputs、colorNamer。
2.2 textOutput():文本化的形状清单
以一个 400×400 画布、一个橙色圆与一个品红方块的草图为例:
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 location=top left area=1% fuchsia square location = bottom right area = 2%
其生成的 DOM 结构(节选)展示了无障碍语义标签的使用方式:
<canvas id="defaultCanvas0" class="p5Canvas" width="400" height="400"> <div id="defaultCanvas0accessibleOutput" role="region" aria-label="Canvas Outputs"> <div id="defaultCanvas0textOutput"> Text Output <div id="defaultCanvas0textOutputSummary" aria-label="text output summary"> <p id="defaultCanvas0textOutput_summary">Your output is a, 400 by 400 pixels, white canvas ...</p> <ul id="defaultCanvas0textOutput_list"> <li><a href="#defaultCanvas0textOutputshape0">orange circle</a>, at top left, covering 1% of the canvas.</li> ... </ul> </div> <table id="defaultCanvas0textOutput_shapeDetails" summary="text output shape details">...</table> </div> </div> </canvas>2.3 gridOutput():空间网格化的形状地图
gridOutput()用 HTML<table>把画布内容布局为一张 10×10 的网格,每个形状依据其在画布上的空间位置落入对应单元格;表格之前有一段总述(背景色、画布尺寸、对象数量与类型):
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 %
2.4 LABEL 与 FALLBACK 两种显示模式
textOutput()、gridOutput()(以及后面的describe()、describeElement())都接受一个可选的display参数:
FALLBACK(默认):描述只对屏幕阅读器可见,嵌入<canvas>内部;LABEL:在<canvas>旁额外创建一个可见的<div>,显示与无障碍描述相同的文本。
官方明确建议:LABEL模式会对屏幕阅读器用户产生冗余信息,因此只应在开发调试阶段使用,发布或分享草图给屏幕阅读器用户前应移除。
从源码看,两种模式由 src/accessibility/outputs.js 中的_createOutput()统一创建 HTML 结构:Fallback 模式把输出容器插入<canvas>内部,Label 模式则用insertAdjacentHTML('afterend', ...)把可见容器放在画布之后;当textOutput与gridOutput同时启用时,代码会保证 grid 输出排在 text 输出之后。
三、用户自定义描述:describe() 与 describeElement()
当自动输出无法表达复杂语义(例如多个基本形状共同构成"一颗心")时,p5.js 提供了由创作者撰写描述的能力,实现在 src/accessibility/describe.js。
3.1 describe():整张画布的语义描述
describe(text, display)的第一个参数是描述画布的字符串,第二个可选参数同样是LABEL/FALLBACK。示例:
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); }实现细节(均可从源码确认):
_descriptionText()会检查文本不是LABEL/FALLBACK,并确保以标点结尾——若不以.,;?!结尾,会自动补一个.,以改善屏幕阅读器的断句朗读(src/accessibility/describe.js);_describeHTML()负责创建 fallback 结构,并在LABEL模式下于画布旁创建可见<div>;- 源码中若传入
LABEL或FALLBACK字样作为描述文本,会直接抛出错误'description should not be LABEL or FALLBACK'。
3.2 describeElement():为"有意义的一组形状"命名
describeElement(name, text, display)用于描述共同构成语义的多个形状(例如用多行代码画出的"心")。第一个参数是元素名,第二个是元素描述,第三个可选参数控制显示模式:
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); }源码中的两个辅助函数值得注意:
_elementName()确保元素名以冒号:结尾(若以.;,结尾则替换为:,否则追加:),使其在表格中作为行头时朗读更自然;- 元素名会被清洗(
name.replace(/[^a-zA-Z0-9]/g, ''))后用作 HTML id,避免特殊字符破坏 DOM 结构; - 每个元素描述以
<th scope="row">元素名</th><td>描述</td>的形式进入画布描述表格,多个describeElement()会累积为多行表格(fallback 模式)或多行可见表格(label 模式)。
四、背后机制:无障碍输出的数据管线
4.1 六个核心私有方法
src/accessibility/outputs.js 是自动输出的枢纽,除公开的textOutput()/gridOutput()外,还包含一组贯穿 p5.js 各模块的私有方法:
| 方法 | 职责 | 被调用位置(源码证据) |
|---|---|---|
_createOutput() | 创建输出 HTML 结构,初始化this.ingredients(存 shapes/colors/pShapes)与this.dummyDOM | outputs.js 内部 |
_updateAccsOutput() | 在setup()/draw()结束时,若ingredients与当前输出不同才更新输出 | src/core/main.js(_setup)、src/core/rendering.js(draw)、src/core/structure.js(redraw) |
_addAccsOutput() | 初始化_accessibleOutputs并判断是否启用了文本/网格输出 | outputs.js |
_accsBackground() | 在background()末尾重置 shapes,并调用颜色命名 | src/core/p5.Renderer2D.js |
_accsCanvasColors() | 在fill()/stroke()末尾更新填充/描边颜色名 | src/core/p5.Renderer2D.js(stroke)、#L241(fill) |
_accsOutput() | 构建ingredients.shapes数据 | src/shape/2d_primitives.js 中的arc/ellipse/line/point/quadrilateral/rectangle/triangle分支 |
_accsOutput()在记录形状时还会调用一组几何辅助函数:_getMiddle()(矩形、圆弧、椭圆、三角形、四边形的质心)、_getPos()("top left""mid right"等方位描述,依据形状中心在画布 40%/60% 分界线上的位置判断)、_canvasLocator()(映射到 10×10 网格的坐标)与_getArea()(形状面积占画布面积的百分比)。
值得强调的性能设计:_updateAccsOutput()只在setup()与draw()结束时调用一次,且仅在ingredients内容发生变化时才重写 DOM。这样做的目的是避免持续更新 DOM 内容而淹没屏幕阅读器——否则屏幕阅读器用户将永远无法读完画布描述。
4.2 更新层:textOutput.js 与 gridOutput.js
- src/accessibility/textOutput.js 的
_updateTextOutput()依据ingredients构建摘要、形状列表与形状详情表,并由_textSummary()、_shapeList()、_shapeDetails()三个辅助函数支撑; - src/accessibility/gridOutput.js 的
_updateGridOutput()对应_gridSummary()、_gridMap()(在 10×10 数组中按位置放置形状,同格多形状以空格连接)、_gridShapeDetails()。
4.3 颜色命名:color_namer.js
自动输出需要把 RGBA 值翻译成人类可读的颜色名,这一工作由 src/accessibility/color_namer.js 的_rgbColorName()完成:它先调用p5.color_conversion._rgbaToHSBA()得到 HSB 值,再通过_calculateColor()与内置的colorLookUp色表比对,返回最接近的颜色名(如 black、gray、white、red、crimson、brown、peach 等),并有少量colorExceptions处理特殊近似。文档说明该算法源自为 2018 年 Processing 基金会奖学金项目开发的 color-namer,并经过盲人屏幕阅读器专家用户的咨询校准。
4.4 已知限制:WebGL 模式
源码中的_updateTextOutput()与_updateGridOutput()均包含 WebGL 守卫分支:当this._renderer.isP3D为真时,会向控制台输出'textOutput() does not yet work in WebGL mode.'/'gridOutput() does not yet work in WebGL mode.'并直接返回。即自动输出目前仅适用于 2D 渲染模式,WebGL 模式下的自动形状描述尚不支持。
五、友好错误系统(FES):让报错成为无障碍的一部分
access.md 将"让错误消息更有用、更能支持使用者"列为增加 access 的典型举措,并明确指向 FES(Friendly Error System,友好错误系统)。这一系统在仓库中的位置:
- 核心实现:src/friendly_errors/fes_core.js、src/friendly_errors/fes.js、src/friendly_errors/param_validator.js、src/friendly_errors/browser_errors.js;
- 贡献指南:contributor_docs/friendly_error_system.md、contributor_docs/how-to-add-friendly-error-messages.md;
- 覆盖能力:参数数量校验(too few/too many)、参数类型校验(wrong type)、常见拼写错误的近似匹配(misspelled-close-match)、变量重声明/未定义引用等浏览器错误的中文化提示,以及 p5.js 成员函数名拼写纠错——对应的手工测试样例集中在 test/manual-test-examples/fes 目录。
FES 的定位是降低"工具本身"对新手和边缘群体的阻碍:一个能给出可操作提示的报错系统,本身就是缩小技术门槛差异的无障碍基础设施。
六、翻译与国际化:对抗语言帝国主义
access.md 把"将文档与资料翻译为更多语言"列在示例之首,并直言这是为了去中心化语言帝国主义。仓库中的实际支撑:
- 翻译数据:translations/en/translation.json(英文基准)、translations/es/translation.json、translations/ja/translation.json、translations/ko/translation.json、translations/zh/translation.json、translations/hi/translation.json;
- 加载与切换逻辑:translations/index.js、translations/dev.js;
- 贡献者文档本身也是多语言的,contributor_docs 下设有
es、zh-Hans、ja、ko、hi、pt-br、sk等语言目录,本文所依据的 contributor_docs/es/access.md 即为西班牙语版本。
七、维护策略:如何把承诺变成治理规则
access.md 的"维护(Mantenimiento)"一节明确了落地的治理机制:
- 特性门槛:不接受不支持无障碍目标的特性请求,这一标准会反映在 issue 与 pull request 模板中;
- Bug 修复无差别:无论 Bug 位于代码库何处都愿意修复;
- 一致性优先:工具的一致性让初学者更容易掌握,因此 API 一致性本身被当作无障碍特性。
同时,access.md 自述是一份"活文档"(living document):关于"优先 access 意味着什么"的讨论仍在持续,社区被邀请通过 GitHub issue 或 hello@p5js.org 参与修订。文档脚注还记录了这份西班牙语版本的修订脉络:它在 2023 年开放源码艺术贡献者会议上与 Evelyn Masso、Nat Decker、Bobby Joe Smith III 等十余位社区成员协作修订,并由 Bobby Joe Smith III 与 Nat Decker 在 Processing 基金会奖学金支持下最终定稿发布。
八、给贡献者的实践清单
综合以上内容,若你想在 p5.js 中践行这一承诺,可以从四个层面入手:
- 使用层面:在你的草图中调用
describe()/describeElement()为画布与元素撰写语义描述,用textOutput()/gridOutput()开启自动输出;开发时用LABEL调试,发布前切回默认的FALLBACK模式。 - 测试层面:仓库为无障碍功能提供了单元测试,可参考 test/unit/accessibility/describe.js 与 test/unit/accessibility/outputs.js,理解
describe()标点补全、LABEL/FALLBACK分支等行为的预期。 - 贡献层面:提交特性前自问"这个特性是否增加 access";翻译 FES 消息或贡献者文档(参考 translations 与 contributor_docs/zh-Hans 的既有工作);为 WebGL 模式下的自动输出补全能力。
- 治理层面:参与"活文档"的讨论,通过 issue 提出对 access 定义与优先级的不同意见——这正是文档所邀请的集体探索。
p5.js 的无障碍工作证明了一条可复制的路径:把抽象的价值承诺转化为具体的 API(describe()、textOutput()等)、具体的算法(颜色命名、面积占比、10×10 定位网格)与具体的治理规则(特性门槛、无差别修复 Bug),最终让"为被排斥者服务"从宣言走进每一行代码。
【免费下载链接】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),仅供参考