Repository Wiki
Brandon030722/ark-ui-skill

五风格族展示样例

五风格族展示样例是仓库 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.htmlArk 族样例(Nightline 部署界面,默认 complex 深度)
corporate.htmlCorporate 族样例
endfield.htmlEndfield 族样例
exa.htmlExa 族样例
popucom.htmlPopucom 族样例
showcase.css全部五族共用的样式表(按 data-family / data-depth 属性区分)
showcase.js全部五族共用的交互运行时(深度切换、视图选择、演示动作)
Loading diagram...

架构要点(均可在源码中验证):

  • 五个页面只依赖两个共享文件。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 的头部与根节点声明:

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,把键盘用户直接送到主内容区,跳过顶栏与导航——这是每个样例页的第一条可访问性契约。

顶栏与深度控制

html
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" 把四个按钮声明为一个具名工具组,读屏器可整体导航进入。

侧栏导航与主舞台

html
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

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)用于检验数据密集布局。

数据面板与阶段轨道

html
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

两个细节值得注意:

  1. 纯视觉数据的降级路径:.panel-route 里只有四个 <i> 空元素(视觉上的路径节点),它对读屏器的全部含义都由 aria-label="Route crosses four verified checkpoints" 承载。这是"图形信息必须可文字化"这一约束的样板写法。
  2. live-status 是页面的唯一反馈出口:初始文案自述当前深度,并再次声明"虚构样例、原创几何"。JS 的所有交互最终都会改写这一行。

共享运行时:showcase.js 逐行剖析

整个运行时只有 33 行,但承担了五族样例的全部交互。它不感知任何家族差异——这是刻意的解耦:族属于 CSS,行为属于 JS。

DOM 契约与元素收集

javascript
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():三重状态同步

javascript
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

这个函数是理解整个展示体系的核心,它按顺序完成四件事:

  1. 白名单校验并兜底:非法值回落到 'complex',保证 data-depth 永远是四个合法值之一,CSS 属性选择器不会失配。
  2. 写 DOM 属性:showcaseRoot.dataset.depth = next 触发 showcase.css 中按 [data-depth="..."] 分流的整套样式——密度切换完全走声明式 CSS,JS 不做任何样式计算。
  3. 同步按钮按压态:为每个按钮写 aria-pressed(当前档为 "true",其余 "false")。用属性而非 class,意味着这些按钮对外表现为 toggle 语义,读屏器可朗读状态。
  4. 回写 URL 并播报:用 history.replaceState 更新 ?depth=——选 replaceState 而非 pushState,避免每次点按钮都堆积一条历史记录;最后改写 live-status 文本,播报词刻意强调 "Content and accessible names remain unchanged"(内容与可访问名称保持不变),因为深度切换在体系定义中只改变视觉密度,不改变语义内容。

selectView() 与事件绑定

javascript
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 参数优先

javascript
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 档;而即便某页面漏写内联属性,链尾兜底仍能保证合法状态。

核心流程

Loading diagram...

流程说明:唯一的"分支"发生在初始化——URL 是否携带 depth 参数决定初始档位(见 showcase.js 第 32–33 行)。此后所有交互都收敛为同一条"改属性 → CSS 响应 → live 区播报"的路径,五族页面在流程上完全同构。

使用示例

直接打开与分享指定密度

无需任何构建步骤:

bash
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:

html
<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 / maximalcomplex(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'。

行为:

  1. 写 showcaseRoot.dataset.depth(驱动 CSS 密度样式);
  2. 为每个 [data-set-depth] 按钮写 aria-pressed(命中档为 "true");
  3. 以 history.replaceState 将 ?depth= 回写 URL(不产生历史记录);
  4. 更新 [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 深度的组合下核对层级、导航与播报行为。

相关链接

Sources

(2 files)