五风格族展示样例
五风格族展示样例是仓库 assets/showcases/ 目录下的一组纯静态 HTML 页面,用于把 Ark UI 技能中定义的五个视觉风格族(ark、corporate、endfield、exa、popucom)渲染成可直接在浏览器打开的"可交互式密度演示页"。每个页面共享同一套 showcase.css 样式与 showcase.js 运行时,仅通过根节点上的 data-family 属性切换视觉族、通过 data-depth 属性切换信息密度档位。
目的与范围
本页覆盖以下内容:
assets/showcases/目录的构成:五个家族样例页面与共享的样式/脚本运行时;- 以
ark.html为代表样例的统一 HTML 骨架(顶栏、侧栏导航、主舞台、状态播报区); - 共享运行时
showcase.js的深度切换、视图选择与演示动作逻辑(逐行剖析); - 深度档位(minimal / moderate / complex / maximal)与 URL 查询参数的持久化机制;
- 展示样例的可访问性契约(skip-link、
aria-pressed、aria-current、aria-live)。
以下主题有意留给兄弟页面,不在本页展开:
- 五个风格族本身的设计令牌与几何语汇定义(属于设计体系章节);
assets/promo/下的宣传页与宣传图渲染管线(promo.html / promo.js / promo.css 及其 output 截图,如02-families-landscape.png);assets/react/下的 ArkUI.jsx / ark-ui.css React 绑定。
概述
展示样例解决的核心问题是:一个风格族不能只靠令牌表来评估,必须在不同信息密度下同时检验几何、层级与排版。因此每个样例页面都被设计成"同一个虚构业务界面"的多密度演示:
- 页面顶部(
.depth-control)提供 01–04 四个按钮,分别对应 minimal、moderate、complex、maximal 四档深度; - 深度选择写回根节点的
data-depth属性并由showcase.js同步到 URL 的?depth=参数,刷新后可保持; - 左侧
.family-rail提供多个虚构视图(如 Operation / Dossiers / Archive),用于检验导航在不同族下的表现; - 主区域
.family-stage演示标题层级、行动按钮、侧面板(<dl>数据对)与阶段轨道; - 页脚
.live-status是一个aria-live="polite"的礼貌播报区,任何交互都会更新这里的文字,供屏幕阅读器与肉眼共同验证。
这些页面是零依赖、零构建的:没有打包器、没有框架,直接 <link> 样式、<script defer> 脚本即可运行,因此可以作为 Ark UI 技能产出物的"验收基准页"长期存在。
架构
assets/showcases/ 由七个文件组成:
| 文件 | 角色 |
|---|---|
| ark.html | Ark 族样例(Nightline 部署界面,默认 complex 深度) |
| corporate.html | Corporate 族样例 |
| endfield.html | Endfield 族样例 |
| exa.html | Exa 族样例 |
| popucom.html | Popucom 族样例 |
| showcase.css | 全部五族共用的样式表(按 data-family / data-depth 属性区分) |
| showcase.js | 全部五族共用的交互运行时(深度切换、视图选择、演示动作) |
架构要点(均可在源码中验证):
- 五个页面只依赖两个共享文件。
ark.html的<head>中以showcase.css?v=2与showcase.js?v=1引用共享资源,?v=后缀是手工版本号,用于在静态文件无构建哈希的情况下强制浏览器刷新缓存。 - 家族差异完全由声明式属性驱动。根节点
<div class="family-showcase" data-family="ark" data-depth="complex">同时声明视觉族与初始深度,CSS 按属性选择器分流;JS 不感知家族,只操作深度与视图,因此五族共用一份 33 行脚本。 - 状态有三个落点:DOM 属性(驱动样式)、URL 参数(可分享/可刷新保持)、
aria-live文本(可访问性反馈)。三者由setDepth()一次同步。
统一 HTML 骨架(以 ark.html 为例)
每个样例页使用完全相同的结构骨架。下面是 ark.html 的头部与根节点声明:
1<head>
2 <meta charset="utf-8" />
3 <meta name="viewport" content="width=device-width, initial-scale=1" />
4 <meta name="color-scheme" content="dark light" />
5 <title>Ark family — Nightline deployment</title>
6 <link rel="stylesheet" href="showcase.css?v=2" />
7 <script src="showcase.js?v=1" defer></script>
8</head>
9<body>
10 <a class="skip-link" href="#sample-main">Skip to sample</a>
11 <div class="family-showcase" data-family="ark" data-depth="complex">Source: ark.html
设计意图解读:
<meta name="color-scheme" content="dark light">让浏览器在五族各自的明暗基调上正确渲染表单控件与滚动条,而不是由 JS 强制切换主题;showcase.js使用defer加载,保证 DOM 就绪后再绑定事件,避免等待load事件造成首帧交互延迟;skip-link指向#sample-main,把键盘用户直接送到主内容区,跳过顶栏与导航——这是每个样例页的第一条可访问性契约。
顶栏与深度控制
1<header class="family-topbar">
2 <a class="family-brand" href="#sample-main"><span class="family-brand-mark" aria-hidden="true"></span><span><strong>Terra Index</strong><small>FICTIONAL SYSTEM / 07</small></span></a>
3 <p class="family-status"><i aria-hidden="true"></i> Shift active</p>
4 <div class="depth-control" role="group" aria-label="Application depth"><span>DEPTH</span><button type="button" data-set-depth="minimal" aria-label="Minimal depth">01</button><button type="button" data-set-depth="moderate" aria-label="Moderate depth">02</button><button type="button" data-set-depth="complex" aria-label="Complex depth">03</button><button type="button" data-set-depth="maximal" aria-label="Maximal depth">04</button></div>
5</header>Source: ark.html
关键点:
- 品牌文案明确标注
FICTIONAL SYSTEM / 07——所有样例内容均为虚构,用于规避与真实产品的混淆; - 四个深度按钮只显示数字 01–04,真正的语义名称放在
aria-label(如 "Minimal depth")中,做到视觉极简与屏幕阅读器可读兼得; role="group"+aria-label="Application depth"把四个按钮声明为一个具名工具组,读屏器可整体导航进入。
侧栏导航与主舞台
1<nav class="family-rail" aria-label="Sample views">
2 <button type="button" data-view="operation" aria-current="page"><span>01</span>Operation</button>
3 <button type="button" data-view="dossiers"><span>02</span>Dossiers</button>
4 <button type="button" data-view="archive"><span>03</span>Archive</button>
5 <p class="family-rail-note">ZONE<br />C-07<br />20:40</p>
6</nav>Source: ark.html
1<main class="family-main" id="sample-main">
2 <section class="family-stage" aria-labelledby="ark-title">
3 <div class="family-decoration" aria-hidden="true"><span></span><span class="decor-secondary"></span><span class="decor-tertiary"></span></div>
4 <div class="family-copy">
5 <p class="family-eyebrow">Deployment control / Night shift</p>
6 <h1 id="ark-title">Operation<br /><span>/ Nightline</span></h1>
7 <p class="family-lede">Compare three active zones, inspect the selected team, and commit the next route from one indexed deployment stage.</p>
8 <div class="family-actions"><button class="family-action primary" type="button" data-demo-action="Deployment review opened for Zone C-07.">Review deployment</button><button class="family-action" type="button" data-demo-action="Dossier index opened with three available teams.">Open dossier</button></div>
9 </div>Source: ark.html
骨架刻意覆盖了一整套真实界面会遇到的组件类型,因此能在不同密度档下暴露问题:
- 纯装饰几何(
.family-decoration三个<span>):检验aria-hidden="true"的装饰是否在低密度档被裁剪、在高密度档仍保持克制; - 文字层级链(eyebrow → h1 → lede → actions):四个层级在 minimal 档必须仍可分辨,在 maximal 档不能挤压;
- 带后果文案的按钮(
data-demo-action):按钮的可访问名称是可见文案,而点击后的行为说明放在该属性里,由 JS 播报; - 数据面板与轨道(下一节):
<dl>键值对与.stage-track阶段卡(SELECTED / AVAILABLE / HELD)用于检验数据密集布局。
数据面板与阶段轨道
1<aside class="family-panel" aria-label="Selected deployment">
2 <div class="panel-head"><span>ZONE // C-07</span><strong>READY</strong></div>
3 <dl><div><dt>Available teams</dt><dd>03</dd></div><div><dt>Valid routes</dt><dd>02</dd></div><div><dt>Decision window</dt><dd>08:40</dd></div></dl>
4 <div class="panel-route" aria-label="Route crosses four verified checkpoints"><i></i><i></i><i></i><i></i></div>
5</aside>
6<div class="stage-track" aria-label="Deployment stages"><article><small>01 / SELECTED</small><strong>North access</strong></article><article><small>02 / AVAILABLE</small><strong>Service corridor</strong></article><article><small>03 / HELD</strong></article></div>
7<p class="live-status" data-live-status aria-live="polite">Application depth: complex. Fictional sample with original geometry.</p>Source: ark.html
两个细节值得注意:
- 纯视觉数据的降级路径:
.panel-route里只有四个<i>空元素(视觉上的路径节点),它对读屏器的全部含义都由aria-label="Route crosses four verified checkpoints"承载。这是"图形信息必须可文字化"这一约束的样板写法。 live-status是页面的唯一反馈出口:初始文案自述当前深度,并再次声明"虚构样例、原创几何"。JS 的所有交互最终都会改写这一行。
共享运行时:showcase.js 逐行剖析
整个运行时只有 33 行,但承担了五族样例的全部交互。它不感知任何家族差异——这是刻意的解耦:族属于 CSS,行为属于 JS。
DOM 契约与元素收集
1const showcaseRoot = document.querySelector('.family-showcase');
2const depthButtons = [...document.querySelectorAll('[data-set-depth]')];
3const navButtons = [...document.querySelectorAll('[data-view]')];
4const actionButtons = [...document.querySelectorAll('[data-demo-action]')];
5const liveStatus = document.querySelector('[data-live-status]');
6const allowedDepths = ['minimal', 'moderate', 'complex', 'maximal'];Source: showcase.js
运行时对页面的全部依赖就是这五个选择器。allowedDepths 是深度的唯一白名单——任何来自页面或 URL 的非法值都会被拒收。展开运算符把 querySelectorAll 的 NodeList 转成真数组,方便后续 forEach 与索引操作。
setDepth():三重状态同步
1function setDepth(depth) {
2 const next = allowedDepths.includes(depth) ? depth : 'complex';
3 showcaseRoot.dataset.depth = next;
4 depthButtons.forEach((button) => button.setAttribute('aria-pressed', String(button.dataset.setDepth === next)));
5 const url = new URL(location.href);
6 url.searchParams.set('depth', next);
7 history.replaceState({}, '', url);
8 if (liveStatus) liveStatus.textContent = `Application depth: ${next}. Content and accessible names remain unchanged.`;
9}Source: showcase.js
这个函数是理解整个展示体系的核心,它按顺序完成四件事:
- 白名单校验并兜底:非法值回落到
'complex',保证data-depth永远是四个合法值之一,CSS 属性选择器不会失配。 - 写 DOM 属性:
showcaseRoot.dataset.depth = next触发showcase.css中按[data-depth="..."]分流的整套样式——密度切换完全走声明式 CSS,JS 不做任何样式计算。 - 同步按钮按压态:为每个按钮写
aria-pressed(当前档为"true",其余"false")。用属性而非 class,意味着这些按钮对外表现为 toggle 语义,读屏器可朗读状态。 - 回写 URL 并播报:用
history.replaceState更新?depth=——选replaceState而非pushState,避免每次点按钮都堆积一条历史记录;最后改写live-status文本,播报词刻意强调 "Content and accessible names remain unchanged"(内容与可访问名称保持不变),因为深度切换在体系定义中只改变视觉密度,不改变语义内容。
selectView() 与事件绑定
1function selectView(button) {
2 navButtons.forEach((candidate) => {
3 if (candidate === button) candidate.setAttribute('aria-current', 'page');
4 else candidate.removeAttribute('aria-current');
5 });
6 if (liveStatus) liveStatus.textContent = `Selected view: ${button.textContent.trim()}.`;
7}
8
9depthButtons.forEach((button) => button.addEventListener('click', () => setDepth(button.dataset.setDepth)));
10navButtons.forEach((button) => button.addEventListener('click', () => selectView(button)));
11actionButtons.forEach((button) => button.addEventListener('click', () => {
12 if (liveStatus) liveStatus.textContent = button.dataset.demoAction;
13}));Source: showcase.js
三类控件、三段平行的绑定:
- 深度按钮 →
setDepth(如上); - 视图按钮 →
selectView:单选语义用aria-current="page"表达(这正是导航当前位置的标准属性,比自造aria-selected更符合规范),选中项朗读"当前页",未选中项移除属性; - 演示动作按钮 → 直接把
data-demo-action的文案播报到 live 区。HTML 中的属性值承担了"剧本"角色(如 "Deployment review opened for Zone C-07."),JS 只做搬运。
注意视图切换不切换任何 DOM 内容——它只改变导航态与播报文本。展示样例的目的是检验导航控件的视觉表现,而不是实现真实路由。
初始化:URL 参数优先
const requestedDepth = new URLSearchParams(location.search).get('depth');
setDepth(requestedDepth || showcaseRoot.dataset.depth || 'complex');Source: showcase.js
优先级链条是 URL 参数 → HTML 内联 data-depth → 'complex'。这带来两个实际能力:
- 评审者可以直接分享形如
popucom.html?depth=maximal的链接,让对方看到指定密度下的 Popucom 族——这是这套样例最常用的评审工作流; ark.html内联data-depth="complex",因此打开即处于 complex 档;而即便某页面漏写内联属性,链尾兜底仍能保证合法状态。
核心流程
流程说明:唯一的"分支"发生在初始化——URL 是否携带 depth 参数决定初始档位(见 showcase.js 第 32–33 行)。此后所有交互都收敛为同一条"改属性 → CSS 响应 → live 区播报"的路径,五族页面在流程上完全同构。
使用示例
直接打开与分享指定密度
无需任何构建步骤:
1# 打开 ark 族默认深度(complex)
2open assets/showcases/ark.html
3
4# 直接评审 corporate 族的 maximal 深度
5open "assets/showcases/corporate.html?depth=maximal"
6
7# 审查 endfield / exa / popucom 三族
8open assets/showcases/endfield.html
9open assets/showcases/exa.html
10open assets/showcases/popucom.html新增一个家族样例的模板
照抄任意现有页面(例如 ark.html),只改三处即可接入共享运行时:<title>、data-family 与初始 data-depth:
<div class="family-showcase" data-family="ark" data-depth="complex">Source: ark.html
showcase.js 通过 .family-showcase 类找到根节点,从不读取 data-family;只要保持骨架类名(family-topbar / family-rail / family-stage / live-status)与 data 属性(data-set-depth / data-view / data-demo-action / data-live-status)不变,新页面自动获得全部交互能力。相反,若改动了 showcase.css?v=2 / showcase.js?v=1 的内容,需同步提升 ?v= 版本号以失效旧缓存。
配置选项
展示样例没有独立配置文件,全部"配置"以 HTML 属性形式内联在页面中,由 showcase.css(按属性选择器分流)与 showcase.js(按 data 属性绑定)消费:
| 属性 / 参数 | 位置 | 合法值 | 默认 | 说明 |
|---|---|---|---|---|
data-family | .family-showcase 根节点 | ark / corporate / endfield / exa / popucom | 无(必填) | 选择视觉风格族,仅被 showcase.css 消费 |
data-depth | .family-showcase 根节点 | minimal / moderate / complex / maximal | complex(JS 兜底) | 当前信息密度档,CSS 与 JS 共同消费 |
data-set-depth | 深度按钮 | 同上四值 | 无 | 按钮对应的深度档;aria-label 提供语义名称 |
data-view | 侧栏导航按钮 | 自由字符串(如 operation) | 无 | 视图标识;JS 仅用于单选与播报,不渲染内容 |
data-demo-action | 演示动作按钮 | 任意句文案 | 无 | 点击后写入 live 区的"剧本"文案 |
data-live-status | .live-status 段落 | 布尔属性 | 无 | 标记 aria-live 播报出口,供 JS 查询 |
?depth= | URL 查询参数 | 同四档深度 | 无 | 初始化时优先级最高;交互后由 replaceState 维护 |
?v= | 资源引用后缀 | 任意版本号 | css=2 / js=1 | 手工缓存失效版本号 |
API 参考(showcase.js)
setDepth(depth: string): void
切换应用深度档,并在 DOM、URL、播报区三处同步状态。
参数:
depth(string):目标深度。必须属于allowedDepths = ['minimal', 'moderate', 'complex', 'maximal'];非法值统一回落为'complex'。
行为:
- 写
showcaseRoot.dataset.depth(驱动 CSS 密度样式); - 为每个
[data-set-depth]按钮写aria-pressed(命中档为"true"); - 以
history.replaceState将?depth=回写 URL(不产生历史记录); - 更新
[data-live-status]文本为Application depth: {next}. Content and accessible names remain unchanged.(liveStatus不存在时静默跳过)。
返回: 无(undefined)。
Source: showcase.js
selectView(button: HTMLButtonElement): void
在 [data-view] 导航组内执行单选,并把选择播报到 live 区。
参数:
button(HTMLButtonElement):被点击的导航按钮。
行为: 命中按钮设 aria-current="page",其余按钮移除该属性;播报 Selected view: {button.textContent.trim()}.。
返回: 无(undefined)。
Source: showcase.js
模块初始化(无导出)
脚本加载(defer)后立即执行:读取 URLSearchParams(location.search).get('depth'),按 URL 参数 → 内联 data-depth → 'complex' 的优先级调用 setDepth() 完成初始状态。文件没有模块导出,也没有入口函数——它是一个"对静态页面的脚本增强",五页共享一份即插即用。
失败模式、边界与并发
- 非法深度值(URL 注入):手改 URL 为
?depth=huge时,allowedDepths.includes校验失败并回落complex,页面样式永不失配。这是运行时唯一的显式防御逻辑。 - live 区缺失:
if (liveStatus)两处守卫保证在骨架不完整的页面上脚本不抛错,仅失去播报能力。 - 根节点缺失:
showcaseRoot.dataset.depth在.family-showcase不存在时会抛TypeError——运行时未做防御,因此保持根类名是接入共享脚本的硬约束。 - 历史记录堆积:选用
replaceState而非pushState,用户连点 04→01→03 不会产生三条历史项,回退行为符合直觉。 - 视图切换不切换内容:
selectView只改导航态与播报,不渲染任何内容——这不是缺陷而是边界声明:密度演示必须保证"内容与可访问名称保持不变",任何内容变化都会污染对比实验。 - 并发/单页脚本:纯单页、无网络请求、无定时器,不存在并发问题;
<script defer>保证 DOM 就绪顺序。
性能与运维要点
- 零依赖零构建:无打包器、无外部 CDN、无框架,五个页面 + 两个资源文件可直接静态托管或本地打开;
- 缓存策略:无构建哈希,靠
showcase.css?v=2/showcase.js?v=1的手工版本号失效缓存——修改共享文件时必须手动递增版本号,否则评审者可能看到旧样式; - 脚本体量:
showcase.js全文 33 行,冷启动成本可忽略; - 键盘可达:skip-link + 原生
<button>+aria-pressed/aria-current/aria-live,全部交互无需鼠标即可完成,也无需自绘焦点管理。
扩展点
- 新增第六族:复制任一样例页,改
data-family并在showcase.css中补充对应属性选择器分支即可,JS 零改动(见上文"使用示例"); - 新增深度档:同时扩展
allowedDepths白名单、HTML 中data-set-depth按钮组与 CSS 的[data-depth]分支——三处必须同步,遗漏 CSS 分支会导致新档无样式; - 让视图真正渲染内容:在
selectView中按button.dataset.view切换 stage 内容即可,当前契约(不渲染)是刻意的评审约束,扩展前需评估是否会破坏密度对比的一致性; - 批量截图:由于深度可由
?depth=驱动,任何无头浏览器脚本都能遍历family × depth的 5×4 组合矩阵自动产出对比图(assets/promo/output/中横竖版成套截图即采用类似的成对产出思路)。
测试
仓库中未发现针对 assets/showcases/ 的自动化测试文件(基于目录清单与检索结果,该目录仅含 7 个 html/css/js 文件)。这些页面本身的验收方式是人工评审:在 5 族 × 4 深度的组合下核对层级、导航与播报行为。
相关链接
- 源码入口:ark.html、corporate.html、endfield.html、exa.html、popucom.html
- 共享运行时与样式:showcase.js、showcase.css
- 关联资产:五族横竖版宣传图与深度图(assets/promo/output/、family-depth-map.zh-CN.svg)
- 相关主题(兄弟页面):风格族定义与设计令牌;宣传页渲染管线(
assets/promo/);React 绑定(assets/react/ArkUI.jsx)