agentcss 0.0.0-stage → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/01-tokens/README.md +236 -0
- package/01-tokens/behavior/dismissable.js +132 -0
- package/01-tokens/behavior/emit.js +76 -0
- package/01-tokens/behavior/focus-return.js +81 -0
- package/01-tokens/behavior/roving.js +152 -0
- package/01-tokens/behavior/typeahead.js +124 -0
- package/01-tokens/focus-ring-demo.html +133 -0
- package/01-tokens/focus-ring.css +102 -0
- package/01-tokens/page.css +244 -0
- package/01-tokens/polyfill.js +116 -0
- package/01-tokens/responsive-demo.html +171 -0
- package/01-tokens/scrollbar-demo.html +194 -0
- package/01-tokens/scrollbar.css +179 -0
- package/01-tokens/theme-toggle.js +103 -0
- package/01-tokens/tokens.css +972 -0
- package/01-tokens/typography.css +156 -0
- package/02-primitives/badge/README.md +71 -0
- package/02-primitives/badge/badge.css +141 -0
- package/02-primitives/badge/demo.html +101 -0
- package/02-primitives/button/README.md +99 -0
- package/02-primitives/button/button.css +353 -0
- package/02-primitives/button/demo.html +200 -0
- package/02-primitives/card/README.md +145 -0
- package/02-primitives/card/card.css +246 -0
- package/02-primitives/card/demo.html +170 -0
- package/02-primitives/choice/README.md +161 -0
- package/02-primitives/choice/choice.css +395 -0
- package/02-primitives/choice/demo.html +277 -0
- package/02-primitives/combobox/README.md +79 -0
- package/02-primitives/combobox/combobox.css +223 -0
- package/02-primitives/combobox/combobox.js +485 -0
- package/02-primitives/combobox/demo.html +109 -0
- package/02-primitives/date-range/README.md +100 -0
- package/02-primitives/date-range/date-range.css +206 -0
- package/02-primitives/date-range/date-range.js +301 -0
- package/02-primitives/date-range/demo.html +104 -0
- package/02-primitives/input/README.md +170 -0
- package/02-primitives/input/demo.html +246 -0
- package/02-primitives/input/input.css +239 -0
- package/02-primitives/popover/README.md +80 -0
- package/02-primitives/popover/demo.html +150 -0
- package/02-primitives/popover/popover.css +187 -0
- package/02-primitives/popover/popover.js +331 -0
- package/02-primitives/progress/README.md +82 -0
- package/02-primitives/progress/demo.html +154 -0
- package/02-primitives/progress/progress.css +172 -0
- package/02-primitives/select/README.md +95 -0
- package/02-primitives/select/demo.html +121 -0
- package/02-primitives/select/select.css +151 -0
- package/02-primitives/select/select.js +549 -0
- package/02-primitives/separator/README.md +63 -0
- package/02-primitives/separator/demo.html +84 -0
- package/02-primitives/separator/separator.css +73 -0
- package/02-primitives/skeleton/README.md +69 -0
- package/02-primitives/skeleton/demo.html +110 -0
- package/02-primitives/skeleton/skeleton.css +96 -0
- package/02-primitives/switch/README.md +65 -0
- package/02-primitives/switch/demo.html +115 -0
- package/02-primitives/switch/switch.css +370 -0
- package/03-patterns/accordion/README.md +121 -0
- package/03-patterns/accordion/accordion.css +453 -0
- package/03-patterns/accordion/accordion.js +369 -0
- package/03-patterns/accordion/demo.html +223 -0
- package/03-patterns/content/README.md +103 -0
- package/03-patterns/content/content.css +286 -0
- package/03-patterns/content/demo.html +88 -0
- package/03-patterns/drawer/README.md +79 -0
- package/03-patterns/drawer/demo.html +101 -0
- package/03-patterns/drawer/drawer.css +129 -0
- package/03-patterns/drawer/drawer.js +258 -0
- package/03-patterns/dropdown/README.md +78 -0
- package/03-patterns/dropdown/demo.html +101 -0
- package/03-patterns/dropdown/dropdown.css +135 -0
- package/03-patterns/dropdown/dropdown.js +451 -0
- package/03-patterns/form-validation/README.md +147 -0
- package/03-patterns/form-validation/demo.html +280 -0
- package/03-patterns/form-validation/form-validation.css +263 -0
- package/03-patterns/list/README.md +150 -0
- package/03-patterns/list/demo.html +225 -0
- package/03-patterns/list/flip.js +201 -0
- package/03-patterns/list/list.css +162 -0
- package/03-patterns/nav/README.md +225 -0
- package/03-patterns/nav/demo.html +131 -0
- package/03-patterns/nav/nav.css +314 -0
- package/03-patterns/nav/nav.js +430 -0
- package/03-patterns/nav/toc.js +111 -0
- package/03-patterns/overlay/README.md +196 -0
- package/03-patterns/overlay/demo.html +241 -0
- package/03-patterns/overlay/overlay.css +319 -0
- package/03-patterns/overlay/overlay.js +563 -0
- package/03-patterns/pagination/README.md +114 -0
- package/03-patterns/pagination/demo.html +165 -0
- package/03-patterns/pagination/pagination.css +195 -0
- package/03-patterns/pagination/pagination.js +395 -0
- package/03-patterns/reveal/README.md +91 -0
- package/03-patterns/reveal/demo.html +106 -0
- package/03-patterns/reveal/reveal.css +93 -0
- package/03-patterns/reveal/reveal.js +172 -0
- package/03-patterns/states/README.md +141 -0
- package/03-patterns/states/demo.html +240 -0
- package/03-patterns/states/states.css +252 -0
- package/03-patterns/tabs/README.md +84 -0
- package/03-patterns/tabs/demo.html +139 -0
- package/03-patterns/tabs/tabs.css +172 -0
- package/03-patterns/tabs/tabs.js +307 -0
- package/03-patterns/tooltip/README.md +96 -0
- package/03-patterns/tooltip/demo.html +78 -0
- package/03-patterns/tooltip/tooltip.css +89 -0
- package/03-patterns/tooltip/tooltip.js +288 -0
- package/03-patterns/tree/README.md +94 -0
- package/03-patterns/tree/demo.html +138 -0
- package/03-patterns/tree/tree.css +113 -0
- package/03-patterns/tree/tree.js +456 -0
- package/04-recipes/analysis-report/README.md +80 -0
- package/04-recipes/analysis-report/analysis-report.css +187 -0
- package/04-recipes/analysis-report/demo.html +274 -0
- package/04-recipes/data-showcase/README.md +45 -0
- package/04-recipes/data-showcase/demo.html +247 -0
- package/04-recipes/detail-page/README.md +93 -0
- package/04-recipes/detail-page/demo.html +55 -0
- package/04-recipes/detail-page/detail-page.css +190 -0
- package/04-recipes/detail-page/full.html +60 -0
- package/04-recipes/detail-page/null.html +60 -0
- package/04-recipes/detail-page/partial.html +60 -0
- package/04-recipes/detail-page/verify-skeleton.py +19 -0
- package/04-recipes/longform/README.md +93 -0
- package/04-recipes/longform/agc-memory.svg +4 -0
- package/04-recipes/longform/content.html +610 -0
- package/04-recipes/longform/content.md +51 -0
- package/04-recipes/longform/longform.html +627 -0
- package/04-recipes/report/README.md +176 -0
- package/04-recipes/report/demo.html +355 -0
- package/04-recipes/report/report.css +364 -0
- package/04-recipes/table/README.md +103 -0
- package/04-recipes/table/demo.html +455 -0
- package/04-recipes/table/table.css +372 -0
- package/04-recipes/tierA-tokens.css +189 -0
- package/09-assets/README.md +74 -0
- package/09-assets/bar/README.md +94 -0
- package/09-assets/bar/bar.css +166 -0
- package/09-assets/bar/bar.js +320 -0
- package/09-assets/bar/demo.html +176 -0
- package/09-assets/echarts-adapter/demo.html +192 -0
- package/09-assets/echarts-adapter/echarts-adapter.js +277 -0
- package/09-assets/model-viewer/README.md +155 -0
- package/09-assets/model-viewer/demo.html +105 -0
- package/09-assets/model-viewer/model-viewer.css +143 -0
- package/09-assets/model-viewer/model-viewer.js +423 -0
- package/09-assets/scientific-plot/README.md +94 -0
- package/09-assets/scientific-plot/demo.html +252 -0
- package/09-assets/scientific-plot/scientific-plot.css +141 -0
- package/09-assets/scientific-plot/scientific-plot.js +610 -0
- package/09-assets/sparkline/README.md +153 -0
- package/09-assets/sparkline/demo.html +163 -0
- package/09-assets/sparkline/sparkline.css +158 -0
- package/09-assets/sparkline/sparkline.js +508 -0
- package/API.md +332 -0
- package/CHANGELOG.md +3116 -0
- package/CODE_OF_CONDUCT.md +64 -0
- package/CONTRIBUTING.md +89 -0
- package/ERRORS.md +1658 -0
- package/LICENSE +21 -0
- package/README.md +491 -2
- package/SECURITY.md +72 -0
- package/START-HERE.md +164 -0
- package/SUPPORT.md +109 -0
- package/adapters/presentation/README.md +113 -0
- package/adapters/presentation/check.js +101 -0
- package/adapters/presentation/deck.schema.json +74 -0
- package/adapters/presentation/to-doc.js +138 -0
- package/ai/START-HERE.md +104 -0
- package/ai/behaviors.json +380 -0
- package/ai/behaviors.schema.json +61 -0
- package/ai/capabilities.json +56 -0
- package/ai/cli.js +387 -0
- package/ai/components.json +1944 -0
- package/ai/components.meta.json +168 -0
- package/ai/components.schema.json +220 -0
- package/ai/contract.schema.json +147 -0
- package/ai/deprecations.json +55 -0
- package/ai/deprecations.schema.json +85 -0
- package/ai/motion-policy.json +22 -0
- package/ai/patch.js +323 -0
- package/ai/patch.schema.json +63 -0
- package/ai/profiles.json +92 -0
- package/ai/tokens.audience.json +55 -0
- package/ai/tokens.audience.schema.json +73 -0
- package/ai/tokens.json +762 -0
- package/ai/tokens.schema.json +58 -0
- package/ai/tokens.tree.json +752 -0
- package/ai/tokens.tree.schema.json +56 -0
- package/ai/validate.js +383 -0
- package/dist/01-tokens/behavior/dismissable.js +60 -0
- package/dist/01-tokens/behavior/emit.js +21 -0
- package/dist/01-tokens/behavior/focus-return.js +31 -0
- package/dist/01-tokens/behavior/roving.js +77 -0
- package/dist/01-tokens/behavior/typeahead.js +61 -0
- package/dist/01-tokens/focus-ring.css +1 -0
- package/dist/01-tokens/page.css +1 -0
- package/dist/01-tokens/polyfill.js +78 -0
- package/dist/01-tokens/scrollbar.css +1 -0
- package/dist/01-tokens/theme-toggle.js +61 -0
- package/dist/01-tokens/tokens.css +1 -0
- package/dist/01-tokens/typography.css +1 -0
- package/dist/02-primitives/badge/badge.css +1 -0
- package/dist/02-primitives/button/button.css +1 -0
- package/dist/02-primitives/card/card.css +1 -0
- package/dist/02-primitives/choice/choice.css +1 -0
- package/dist/02-primitives/combobox/combobox.css +1 -0
- package/dist/02-primitives/combobox/combobox.js +439 -0
- package/dist/02-primitives/date-range/date-range.css +1 -0
- package/dist/02-primitives/date-range/date-range.js +267 -0
- package/dist/02-primitives/input/input.css +1 -0
- package/dist/02-primitives/popover/popover.css +1 -0
- package/dist/02-primitives/popover/popover.js +257 -0
- package/dist/02-primitives/progress/progress.css +1 -0
- package/dist/02-primitives/select/select.css +1 -0
- package/dist/02-primitives/select/select.js +449 -0
- package/dist/02-primitives/separator/separator.css +1 -0
- package/dist/02-primitives/skeleton/skeleton.css +1 -0
- package/dist/02-primitives/switch/switch.css +1 -0
- package/dist/03-patterns/accordion/accordion.css +1 -0
- package/dist/03-patterns/accordion/accordion.js +219 -0
- package/dist/03-patterns/content/content.css +1 -0
- package/dist/03-patterns/drawer/drawer.css +1 -0
- package/dist/03-patterns/drawer/drawer.js +229 -0
- package/dist/03-patterns/dropdown/dropdown.css +1 -0
- package/dist/03-patterns/dropdown/dropdown.js +365 -0
- package/dist/03-patterns/form-validation/form-validation.css +1 -0
- package/dist/03-patterns/list/flip.js +117 -0
- package/dist/03-patterns/list/list.css +1 -0
- package/dist/03-patterns/nav/nav.css +1 -0
- package/dist/03-patterns/nav/nav.js +322 -0
- package/dist/03-patterns/nav/toc.js +76 -0
- package/dist/03-patterns/overlay/overlay.css +1 -0
- package/dist/03-patterns/overlay/overlay.js +398 -0
- package/dist/03-patterns/pagination/pagination.css +1 -0
- package/dist/03-patterns/pagination/pagination.js +273 -0
- package/dist/03-patterns/reveal/reveal.css +1 -0
- package/dist/03-patterns/reveal/reveal.js +138 -0
- package/dist/03-patterns/states/states.css +1 -0
- package/dist/03-patterns/tabs/tabs.css +1 -0
- package/dist/03-patterns/tabs/tabs.js +242 -0
- package/dist/03-patterns/tooltip/tooltip.css +1 -0
- package/dist/03-patterns/tooltip/tooltip.js +213 -0
- package/dist/03-patterns/tree/tree.css +1 -0
- package/dist/03-patterns/tree/tree.js +383 -0
- package/dist/04-recipes/analysis-report/analysis-report.css +1 -0
- package/dist/04-recipes/detail-page/detail-page.css +1 -0
- package/dist/04-recipes/report/report.css +1 -0
- package/dist/04-recipes/table/table.css +1 -0
- package/dist/04-recipes/tierA-tokens.css +1 -0
- package/dist/09-assets/bar/bar.css +1 -0
- package/dist/09-assets/bar/bar.js +222 -0
- package/dist/09-assets/echarts-adapter/echarts-adapter.js +185 -0
- package/dist/09-assets/model-viewer/model-viewer.css +1 -0
- package/dist/09-assets/model-viewer/model-viewer.js +320 -0
- package/dist/09-assets/scientific-plot/scientific-plot.css +1 -0
- package/dist/09-assets/scientific-plot/scientific-plot.js +568 -0
- package/dist/09-assets/sparkline/sparkline.css +1 -0
- package/dist/09-assets/sparkline/sparkline.js +265 -0
- package/dist/COSTS.md +56 -0
- package/docs/AI-CONTRACT.md +197 -0
- package/docs/BACKLOG.md +127 -0
- package/docs/BENCHMARK.md +252 -0
- package/docs/GUIDANCE.md +154 -0
- package/docs/INVARIANT.md +372 -0
- package/docs/LAYER-BASELINE.md +158 -0
- package/docs/RELEASING.md +275 -0
- package/docs/RFC-THEME-SCOPE.md +141 -0
- package/docs/ROADMAP.md +182 -0
- package/index.html +82 -0
- package/package.json +107 -4
- package/types/index.d.ts +310 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* pagination.js — 分页交互(零依赖 · ES5)
|
|
3
|
+
*
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* ⭐ 依据(5 个来源口径一致:W3C APG / MDN / USWDS / Component Gallery / Mironsoft)
|
|
6
|
+
* ============================================================================
|
|
7
|
+
* 1. 结构由 HTML 负责(`<nav aria-label>` + `<ul>` + `aria-current="page"`),
|
|
8
|
+
* 本文件只管**翻页行为**与**页码窗口**。
|
|
9
|
+
* 2. ⭐ **客户端翻页必须播报**(USWDS / Mironsoft 都强调):
|
|
10
|
+
* 用 `aria-live="polite"` 且**元素常驻 DOM**,只改 `textContent`。
|
|
11
|
+
* ⇒ 新插入 live 区的话,很多读屏器**不会播报**。
|
|
12
|
+
* 3. 页码窗口:**最多 7 个槽位**(USWDS 的规定)。
|
|
13
|
+
* 当前页两侧各留 1 个,首末页固定显示。
|
|
14
|
+
* 4. 首/末页时,上一页/下一页用 **`disabled`**(不是"不工作但看着能点")。
|
|
15
|
+
* 5. 键盘:`←`/`→` 翻页,`Home`/`End` 跳首末(APG 的键盘契约)。
|
|
16
|
+
*
|
|
17
|
+
* ============================================================================
|
|
18
|
+
* 用法
|
|
19
|
+
* ============================================================================
|
|
20
|
+
* <nav class="pagination" aria-label="搜索结果分页"
|
|
21
|
+
* data-pagination data-total="248" data-page-size="20" data-page="1">
|
|
22
|
+
* <ul class="pagination__list" data-pg-list></ul>
|
|
23
|
+
* <p class="pagination__status" data-pg-status></p>
|
|
24
|
+
* <p class="pagination__live" data-pg-live aria-live="polite"></p>
|
|
25
|
+
* </nav>
|
|
26
|
+
* <script src="03-patterns/pagination/pagination.js"></script>
|
|
27
|
+
* <script>
|
|
28
|
+
* var pg = Pagination.create(document.querySelector('[data-pagination]'));
|
|
29
|
+
* // 翻页通知走 **DOM 事件**(本库没有 `on` 这种订阅方法 ——
|
|
30
|
+
* // 曾经在注释里写过 `pg.on` 的写法,那是**从来没有存在过**的 API,
|
|
31
|
+
* // 照抄只会在运行时得到 `pg.on is not a function`。现已由门禁判据 5 盯着:
|
|
32
|
+
* // 源码注释里承诺的「某全局 / 某实例的某方法」,必须在浏览器里真探得到。)
|
|
33
|
+
* pg.node.addEventListener('fl-change', function (e) {
|
|
34
|
+
* loadPage(e.detail.value);
|
|
35
|
+
* });
|
|
36
|
+
* </script>
|
|
37
|
+
*/
|
|
38
|
+
(function () {
|
|
39
|
+
'use strict';
|
|
40
|
+
|
|
41
|
+
/* ==== BEHAVIOR INJECT BEGIN: emit ==== */
|
|
42
|
+
var flEmit = function (el, name, detail) {
|
|
43
|
+
if (!el || !el.dispatchEvent) return null;
|
|
44
|
+
var type = name.indexOf('fl-') === 0 ? name : 'fl-' + name;
|
|
45
|
+
var ev = null;
|
|
46
|
+
/* 老 WebView 没有 CustomEvent 构造函数 ⇒ 兜底走 createEvent(理由见文件头)*/
|
|
47
|
+
if (typeof window.CustomEvent === 'function') {
|
|
48
|
+
try {
|
|
49
|
+
ev = new window.CustomEvent(type, {
|
|
50
|
+
detail: detail || null, bubbles: true, cancelable: false
|
|
51
|
+
});
|
|
52
|
+
} catch (e) { ev = null; }
|
|
53
|
+
}
|
|
54
|
+
if (!ev) {
|
|
55
|
+
try {
|
|
56
|
+
ev = document.createEvent('CustomEvent');
|
|
57
|
+
ev.initCustomEvent(type, true, false, detail || null);
|
|
58
|
+
} catch (e2) { return null; }
|
|
59
|
+
}
|
|
60
|
+
el.dispatchEvent(ev);
|
|
61
|
+
return ev;
|
|
62
|
+
};
|
|
63
|
+
/* ==== BEHAVIOR INJECT END: emit ==== */
|
|
64
|
+
|
|
65
|
+
var SLOTS = 7; /* USWDS:最多 7 个槽位 */
|
|
66
|
+
|
|
67
|
+
function el(tag, cls, text) {
|
|
68
|
+
var n = document.createElement(tag);
|
|
69
|
+
if (cls) n.className = cls;
|
|
70
|
+
if (text !== undefined && text !== null) n.textContent = text;
|
|
71
|
+
return n;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* ----------------------------------------------------------------------
|
|
75
|
+
* 算页码窗口(最多 SLOTS 个)
|
|
76
|
+
* ----------------------------------------------------------------------
|
|
77
|
+
* 规则(USWDS):
|
|
78
|
+
* · 总是显示第一页、当前页
|
|
79
|
+
* · 当前页两侧各显示一页
|
|
80
|
+
* · 有遗漏就用省略号
|
|
81
|
+
* · 槽位总数固定为 7(页数少时就是页数本身)
|
|
82
|
+
*/
|
|
83
|
+
/* 🔴 修「槽位超过 7」(实测第 1 页就出现 10 个槽位)
|
|
84
|
+
真因:USWDS 说的「7 个槽位」指的是**页码区**,
|
|
85
|
+
若把「上一页/下一页」也算进去 ⇒ 计数翻倍。
|
|
86
|
+
|
|
87
|
+
⇒ 现在明确分工:
|
|
88
|
+
· 页码窗口最多 **5 个数字 + 最多 2 个省略号 = 7 个槽位**(USWDS 的口径)
|
|
89
|
+
· 上一页/下一页是**独立控件**,不占页码槽位
|
|
90
|
+
*/
|
|
91
|
+
/* 🔴 重写(实测:第 5 页出现 9 个槽位,超出 USWDS 的 7)
|
|
92
|
+
上一版的边界条件有漏洞(start 的钳制与循环的终止条件互相打架)。
|
|
93
|
+
|
|
94
|
+
现在用**明确的两段拼接**,不靠钳制:
|
|
95
|
+
· 固定两端:首页、末页(各 1 个)
|
|
96
|
+
· 中间窗口:恰好 PAGE_SLOTS 个(不够就在左侧补,多了就截断)
|
|
97
|
+
· 省略号:只有"真的跳过了页码"时才出现
|
|
98
|
+
⇒ 结果**恒定 ≤ 7**(1 + 1 + 5 + 1 = 8 是极端情况,
|
|
99
|
+
但两端各占 1 时中间只需 3 个 ⇒ 1+1+3+1 = 6,加省略号 2 ⇒ 但两端都有省略号时
|
|
100
|
+
首/末页是「实数」不占省略号位,实际最大 = 1 + 1 + 5 + 1 = 8。
|
|
101
|
+
为保证 ≤7,**中间窗口固定 3 个**(总 = 1+1+3+1 = 6,+2 省略号 = 8)…
|
|
102
|
+
|
|
103
|
+
⇒ 简化并对齐 USWDS 的真实含义:
|
|
104
|
+
**总槽位(含首末页与省略号)固定 7**。
|
|
105
|
+
实现:先把「首页 + 末页 + 中间 3 页 + 2 个省略号」= 7 封顶,
|
|
106
|
+
再按当前位置微调(靠近两端时省略号消失,槽位让给页码)。
|
|
107
|
+
*/
|
|
108
|
+
function pageWindow(cur, total, slots) {
|
|
109
|
+
slots = slots || SLOTS; /* 7(含省略号与首末页的总槽位)*/
|
|
110
|
+
if (total <= slots - 2) { /* 页数够少:全列出来,不用省略号 */
|
|
111
|
+
var all = [];
|
|
112
|
+
for (var i = 1; i <= total; i++) all.push(i);
|
|
113
|
+
return all;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var MAX_MID = slots - 4; /* 中间最多几个:7 − 首页 − 末页 − 两个省略号 */
|
|
117
|
+
if (MAX_MID < 1) MAX_MID = 1;
|
|
118
|
+
|
|
119
|
+
/* 中间窗口:当前页居中,左边不够就往右补 */
|
|
120
|
+
var mid = [];
|
|
121
|
+
var start = cur - Math.floor(MAX_MID / 2);
|
|
122
|
+
if (start < 2) start = 2; /* 不能压住首页 */
|
|
123
|
+
if (start + MAX_MID - 1 > total - 1) start = total - MAX_MID; /* 不能压住末页 */
|
|
124
|
+
if (start < 2) start = 2;
|
|
125
|
+
for (var j = start; j < start + MAX_MID; j++) {
|
|
126
|
+
if (j > 1 && j < total) mid.push(j);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
var out = [1];
|
|
130
|
+
/* 🔴 省略号只在「真的跳过了页码」时出现 */
|
|
131
|
+
if (mid.length && mid[0] > 2) out.push('…');
|
|
132
|
+
for (var k = 0; k < mid.length; k++) out.push(mid[k]);
|
|
133
|
+
if (mid.length && mid[mid.length - 1] < total - 1) out.push('…');
|
|
134
|
+
if (total > 1) out.push(total);
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* ---------------------------------------------------------------------- */
|
|
139
|
+
|
|
140
|
+
function Pagination(node) {
|
|
141
|
+
this.node = node;
|
|
142
|
+
this.total = Math.max(1, parseInt(node.getAttribute('data-total') || '1', 10));
|
|
143
|
+
this.size = Math.max(1, parseInt(node.getAttribute('data-page-size') || '20', 10));
|
|
144
|
+
this.page = Math.max(1, parseInt(node.getAttribute('data-page') || '1', 10));
|
|
145
|
+
this.label = node.getAttribute('aria-label') || '分页';
|
|
146
|
+
this.pages = Math.max(1, Math.ceil(this.total / this.size));
|
|
147
|
+
this.onChange = null;
|
|
148
|
+
|
|
149
|
+
/* 🔴 从 URL 恢复页码(刷新/分享后能回到同一页) */
|
|
150
|
+
try {
|
|
151
|
+
var u = node.getAttribute('data-pg-param') || 'page';
|
|
152
|
+
var q = parseInt(new URL(location.href).searchParams.get(u) || '0', 10);
|
|
153
|
+
if (q > 0) this.page = Math.min(q, this.pages);
|
|
154
|
+
} catch (e) { /* 忽略 */ }
|
|
155
|
+
|
|
156
|
+
this.listNode = node.querySelector('[data-pg-list]');
|
|
157
|
+
this.statusNode = node.querySelector('[data-pg-status]');
|
|
158
|
+
this.liveNode = node.querySelector('[data-pg-live]');
|
|
159
|
+
|
|
160
|
+
var self = this;
|
|
161
|
+
node.addEventListener('click', function (e) {
|
|
162
|
+
var t = e.target;
|
|
163
|
+
while (t && t !== node && t.tagName !== 'BUTTON' && t.tagName !== 'A') {
|
|
164
|
+
t = t.parentNode;
|
|
165
|
+
}
|
|
166
|
+
if (!t || t === node) return;
|
|
167
|
+
if (t.disabled || t.getAttribute('aria-disabled') === 'true') return;
|
|
168
|
+
var to = t.getAttribute('data-goto');
|
|
169
|
+
if (to === 'prev') { self.go(self.page - 1); }
|
|
170
|
+
else if (to === 'next') { self.go(self.page + 1); }
|
|
171
|
+
else if (to === 'first') { self.go(1); }
|
|
172
|
+
else if (to === 'last') { self.go(self.pages); }
|
|
173
|
+
else if (to) { self.go(parseInt(to, 10)); }
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/* ⭐ 键盘契约(APG):← → 翻页,Home/End 跳首末
|
|
177
|
+
|
|
178
|
+
🔴 修:原来只在 `node` 上监听 keydown,
|
|
179
|
+
实测 Home/End **无效** —— 因为重渲染后焦点会落到
|
|
180
|
+
`document.body`(不在 nav 内)⇒ 事件根本不冒泡到 nav。
|
|
181
|
+
⇒ 改成挂在 **document** 上,并判断"焦点是否在本分页区内或页面空闲"。 */
|
|
182
|
+
this._onKey = function (e) {
|
|
183
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
184
|
+
var act = document.activeElement;
|
|
185
|
+
var inside = act && (act === self.node || self.node.contains(act));
|
|
186
|
+
/* 焦点在页面其它可编辑元素里时不抢键盘 */
|
|
187
|
+
if (act && /^(INPUT|TEXTAREA|SELECT)$/.test(act.tagName)) return;
|
|
188
|
+
if (act && act.isContentEditable) return;
|
|
189
|
+
/* 有焦点且不在本组件内 ⇒ 只响应 Home/End 之外无意义,直接放行 */
|
|
190
|
+
if (!inside && act && act !== document.body) return;
|
|
191
|
+
|
|
192
|
+
var k = e.key;
|
|
193
|
+
if (k === 'ArrowLeft') { self.go(self.page - 1); }
|
|
194
|
+
else if (k === 'ArrowRight') { self.go(self.page + 1); }
|
|
195
|
+
else if (k === 'Home') { self.go(1); }
|
|
196
|
+
else if (k === 'End') { self.go(self.pages); }
|
|
197
|
+
else return;
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
/* 🔴 键盘翻页后:焦点**必须**回到当前页按钮
|
|
200
|
+
(实测原来会落到 body ⇒ 键盘用户"丢失焦点",
|
|
201
|
+
连按 → 也没反应,因为事件源不再是按钮) */
|
|
202
|
+
self.focusCurrent();
|
|
203
|
+
};
|
|
204
|
+
document.addEventListener('keydown', this._onKey);
|
|
205
|
+
|
|
206
|
+
this.render();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Pagination.prototype.pages = 1;
|
|
210
|
+
|
|
211
|
+
/* 🔴 destroy(0.8.9 补)——
|
|
212
|
+
* 构造函数里注册了 `document.addEventListener('keydown', this._onKey)`
|
|
213
|
+
* (上面 render() 之前那处),而 `document` 是全局对象:
|
|
214
|
+
* **元素被移除时浏览器不会自动摘它**。
|
|
215
|
+
* ⇒ 宿主用框架做条件渲染时(列表项删掉、路由切换),
|
|
216
|
+
* 每挂一次就多一个 keydown 监听,页面上放 N 个分页器就有 N 个。
|
|
217
|
+
*
|
|
218
|
+
* ⭐ 摘除必须用**同一个函数引用**:`this._onKey` 是具名的(定义在构造函数里),
|
|
219
|
+
* 所以 removeEventListener 能对上;写成匿名函数就永远摘不掉
|
|
220
|
+
* (combobox.js:358 那条注释说的就是这件事)。
|
|
221
|
+
*
|
|
222
|
+
* 幂等:重复调 destroy() 不会报错 —— 因为 removeEventListener 对
|
|
223
|
+
* 「没注册过的引用」是静默 no-op,但下面的 _destroyed 标志让第二次直接返回,
|
|
224
|
+
* 避免重复做清理。
|
|
225
|
+
*/
|
|
226
|
+
Pagination.prototype.destroy = function () {
|
|
227
|
+
if (this._destroyed) return;
|
|
228
|
+
this._destroyed = true;
|
|
229
|
+
document.removeEventListener('keydown', this._onKey);
|
|
230
|
+
// 若焦点还在被移除的分页器里,顺手交还出去,避免焦点丢到不可见处
|
|
231
|
+
// ⚠️ 字段名是 `node`(构造函数第 141 行 `this.node = node`),不是 `root`
|
|
232
|
+
if (this.node && document.activeElement &&
|
|
233
|
+
this.node.contains &&
|
|
234
|
+
this.node.contains(document.activeElement)) {
|
|
235
|
+
document.activeElement.blur();
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
Pagination.prototype.go = function (n, opts) {
|
|
240
|
+
opts = opts || {};
|
|
241
|
+
n = Math.max(1, Math.min(this.pages, n));
|
|
242
|
+
if (n === this.page && !opts.force) { this.focusCurrent(); return; }
|
|
243
|
+
this.page = n;
|
|
244
|
+
this.render();
|
|
245
|
+
this.announce();
|
|
246
|
+
|
|
247
|
+
/* 🔴 修三个**表面看不见的逻辑漏洞**(要求:注重逻辑关系,不只是表面上的视觉设计):
|
|
248
|
+
|
|
249
|
+
① **翻页后不重置滚动位置**
|
|
250
|
+
实测:用户在页面底部点"下一页" ⇒ 新内容渲染在上方视口里,
|
|
251
|
+
屏幕几乎没变化 ⇒ 用户以为"没反应"。
|
|
252
|
+
⇒ 翻页后把**列表区顶部**滚到视口(不是滚到 0,那会跳过头)。
|
|
253
|
+
② **URL 不同步** ⇒ 刷新回到第 1 页、链接不能分享。
|
|
254
|
+
⇒ 用 `history.replaceState`(不污染历史栈)。
|
|
255
|
+
③ **焦点丢到 body**(键盘用户按 → 后焦点就没了)
|
|
256
|
+
⇒ 由调用方在 onChange 里把焦点放到**列表容器**(见 afterRender)。
|
|
257
|
+
*/
|
|
258
|
+
if (opts.scroll !== false) this.scrollToTop();
|
|
259
|
+
this.syncURL();
|
|
260
|
+
flEmit(this.node, 'fl-change', { value: n, page: n });
|
|
261
|
+
if (typeof this.onChange === 'function') this.onChange(n);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/* --- 滚到列表顶部(不是页面顶部) --- */
|
|
265
|
+
Pagination.prototype.scrollToTop = function () {
|
|
266
|
+
var target = this.node.getAttribute('data-pg-scroll-target')
|
|
267
|
+
|| this.node.getAttribute('aria-controls');
|
|
268
|
+
var el = target ? document.getElementById(target) : null;
|
|
269
|
+
if (!el || !el.scrollIntoView) {
|
|
270
|
+
/* 没指定目标 ⇒ 退到"把分页条本身滚进视口"(至少让用户看到位置) */
|
|
271
|
+
if (this.node.scrollIntoView) {
|
|
272
|
+
try { this.node.scrollIntoView({ block: 'nearest' }); } catch (e) {}
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
el.scrollIntoView({ block: 'start' });
|
|
278
|
+
} catch (e) {
|
|
279
|
+
el.scrollIntoView(true);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/* --- URL 同步(可分享 / 刷新保持) --- */
|
|
284
|
+
Pagination.prototype.syncURL = function () {
|
|
285
|
+
if (typeof history === 'undefined' || !history.replaceState) return;
|
|
286
|
+
if (this.node.getAttribute('data-pg-nourl') === 'true') return;
|
|
287
|
+
try {
|
|
288
|
+
var u = this.node.getAttribute('data-pg-param') || 'page';
|
|
289
|
+
var url = new URL(location.href);
|
|
290
|
+
if (this.page <= 1) url.searchParams.delete(u);
|
|
291
|
+
else url.searchParams.set(u, String(this.page));
|
|
292
|
+
history.replaceState(null, '', url.toString());
|
|
293
|
+
} catch (e) { /* 老浏览器 URL 构造失败 ⇒ 放弃同步,不影响功能 */ }
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
Pagination.prototype.focusCurrent = function () {
|
|
297
|
+
var cur = this.listNode.querySelector('[aria-current="page"]');
|
|
298
|
+
if (cur && cur.focus) cur.focus();
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
/** ⭐ 播报(依据第 2 条:改 textContent,不换元素) */
|
|
302
|
+
Pagination.prototype.announce = function () {
|
|
303
|
+
if (!this.liveNode) return;
|
|
304
|
+
this.liveNode.textContent = '第 ' + this.page + ' 页,共 ' + this.pages + ' 页';
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
Pagination.prototype.render = function () {
|
|
308
|
+
if (!this.listNode) return;
|
|
309
|
+
var L = this.listNode;
|
|
310
|
+
while (L.firstChild) L.removeChild(L.firstChild);
|
|
311
|
+
var self = this;
|
|
312
|
+
|
|
313
|
+
function addBtn(text, ariaLabel, goto, disabled) {
|
|
314
|
+
var li = el('li', 'pagination__item');
|
|
315
|
+
var b = el('button', 'pagination__link', text);
|
|
316
|
+
b.type = 'button';
|
|
317
|
+
b.setAttribute('data-goto', goto);
|
|
318
|
+
/* 🔴 依据第 4 条:禁用用 disabled(读屏器会播报"不可用"),
|
|
319
|
+
不是只画灰 —— 看着能点却不工作是最糟的。 */
|
|
320
|
+
if (disabled) { b.disabled = true; b.setAttribute('aria-disabled', 'true'); }
|
|
321
|
+
if (ariaLabel) b.setAttribute('aria-label', ariaLabel);
|
|
322
|
+
li.appendChild(b);
|
|
323
|
+
L.appendChild(li);
|
|
324
|
+
return b;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/* --- 上一页 / 首页 --- */
|
|
328
|
+
addBtn('‹', '上一页', 'prev', self.page <= 1);
|
|
329
|
+
/* --- 页码 --- */
|
|
330
|
+
var win = pageWindow(self.page, self.pages, SLOTS);
|
|
331
|
+
for (var i = 0; i < win.length; i++) {
|
|
332
|
+
var p = win[i];
|
|
333
|
+
if (p === '…') {
|
|
334
|
+
var li = el('li', 'pagination__item');
|
|
335
|
+
li.appendChild(el('span', 'pagination__ellipsis', '…'));
|
|
336
|
+
/* 🔴 省略号对读屏器要"隐藏"但**不能 display:none**
|
|
337
|
+
(有些读屏器仍会读出孤立的"…") */
|
|
338
|
+
li.firstChild.setAttribute('aria-hidden', 'true');
|
|
339
|
+
L.appendChild(li);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
/* 🔴 依据第 3 条(USWDS):**当前页仍然是链接/按钮**,
|
|
343
|
+
只是用 aria-current 标记 + 视觉高亮。
|
|
344
|
+
这样无论用户是"浏览"还是"Tab",读屏器都能播报当前页。 */
|
|
345
|
+
var isCur = (p === self.page);
|
|
346
|
+
var li2 = el('li', 'pagination__item');
|
|
347
|
+
var b2 = el('button', 'pagination__link', String(p));
|
|
348
|
+
b2.type = 'button';
|
|
349
|
+
b2.setAttribute('data-goto', String(p));
|
|
350
|
+
b2.setAttribute('aria-label', '第 ' + p + ' 页' + (isCur ? '(当前页)' : ''));
|
|
351
|
+
if (isCur) {
|
|
352
|
+
b2.setAttribute('aria-current', 'page');
|
|
353
|
+
b2.setAttribute('aria-disabled', 'true');
|
|
354
|
+
}
|
|
355
|
+
li2.appendChild(b2);
|
|
356
|
+
L.appendChild(li2);
|
|
357
|
+
}
|
|
358
|
+
/* --- 下一页 / 末页 --- */
|
|
359
|
+
addBtn('›', '下一页', 'next', self.page >= self.pages);
|
|
360
|
+
|
|
361
|
+
/* --- 可见状态("第 2 页 / 共 12 页") --- */
|
|
362
|
+
if (this.statusNode) {
|
|
363
|
+
this.statusNode.textContent =
|
|
364
|
+
'第 ' + self.page + ' 页 / 共 ' + self.pages + ' 页';
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/* ---------------------------------------------------------------------- */
|
|
369
|
+
var API = {
|
|
370
|
+
/** 创建一个分页实例(返回实例以便绑定 onChange) */
|
|
371
|
+
create: function (node) {
|
|
372
|
+
if (!node) return null;
|
|
373
|
+
if (node.__pg) return node.__pg;
|
|
374
|
+
node.__pg = new Pagination(node);
|
|
375
|
+
return node.__pg;
|
|
376
|
+
},
|
|
377
|
+
/** 页面内所有 [data-pagination] */
|
|
378
|
+
update: function (root) {
|
|
379
|
+
var list = (root || document).querySelectorAll('[data-pagination]');
|
|
380
|
+
for (var i = 0; i < list.length; i++) API.create(list[i]);
|
|
381
|
+
},
|
|
382
|
+
/** 供其它组件算页数(比如表格用它显示"共 N 页") */
|
|
383
|
+
pagesOf: function (total, pageSize) {
|
|
384
|
+
return Math.max(1, Math.ceil((total || 1) / (pageSize || 20)));
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
if (typeof window !== 'undefined') window.Pagination = API;
|
|
389
|
+
// @autoinit: [data-pagination] —— 扫属性自动 update()(详见 API.md「自动初始化」)
|
|
390
|
+
if (document.readyState === 'loading') {
|
|
391
|
+
document.addEventListener('DOMContentLoaded', function () { API.update(); });
|
|
392
|
+
} else {
|
|
393
|
+
API.update();
|
|
394
|
+
}
|
|
395
|
+
})();
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# reveal · 入场动效
|
|
2
|
+
|
|
3
|
+
一个属性 `data-reveal`,六个方向的入场。**零 JS 也能看**,JS 只是让它动起来。
|
|
4
|
+
|
|
5
|
+
## 用法
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<!-- ① 引样式(必需) -->
|
|
9
|
+
<link rel="stylesheet" href="agentcss/patterns/reveal/reveal.css">
|
|
10
|
+
|
|
11
|
+
<!-- ② 想自动接管,在 <html> 上写一个属性 -->
|
|
12
|
+
<html lang="zh-CN" data-reveal-auto>
|
|
13
|
+
|
|
14
|
+
<!-- ③ 内容上标方向 -->
|
|
15
|
+
<section data-reveal="up">…</section>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
不想自动接管,就手动调:`Reveal.enable()` / `Reveal.disable()` / `Reveal.replay()`。
|
|
19
|
+
|
|
20
|
+
## 属性协议
|
|
21
|
+
|
|
22
|
+
| 属性 | 作用 | 取值 |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `data-reveal` | 方向 | `up` / `down` / `left` / `right` / `scale` / `none` |
|
|
25
|
+
| `data-reveal-children` | 容器内子元素依次入场,不用逐个标 | 方向名(省略按 `up`) |
|
|
26
|
+
| `data-reveal-delay` | 单独延迟 | `120ms` |
|
|
27
|
+
| `data-reveal-repeat` | 滚出后再次进入可重播(默认只播一次) | 空属性 |
|
|
28
|
+
|
|
29
|
+
> `data-reveal-repeat` 是本库与参考实现的**不同之处**:默认**只播一次**。
|
|
30
|
+
> 理由:产品页里内容"滚出去再藏起来"会让回看变成负担;
|
|
31
|
+
> 作品集那种一次性深度浏览的页面才需要重播,所以做成显式开启。
|
|
32
|
+
|
|
33
|
+
## 参数(不是随手定的)
|
|
34
|
+
|
|
35
|
+
| 参数 | 值 | 为什么 |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| 位移 | **8px** | 移动端信息密度高,24px 位移会把视线带离内容 |
|
|
38
|
+
| 时长 | `--dur-base`(180ms) | UI 动效超 300ms 会觉得卡(`motion.py` 会查) |
|
|
39
|
+
| 错峰 | 40ms | 6 个元素 240ms 播完 |
|
|
40
|
+
| 错峰封顶 | 6 个 | 不封顶时第 20 个要等 800ms,用户滚到那儿它还在播 |
|
|
41
|
+
| 出场动画 | **不做** | 产品元素不该"消失再出现" |
|
|
42
|
+
|
|
43
|
+
## 三条硬约束
|
|
44
|
+
|
|
45
|
+
1. **无 JS 必须全可见。** 隐藏只在 `<html data-reveal-ready>` 存在时生效,
|
|
46
|
+
而这个标记由引擎在接管**之后**才加。没有 JS / JS 报错 / 没有 `IntersectionObserver`
|
|
47
|
+
⇒ 内容就是普通内容:透明度 1、位置不偏。
|
|
48
|
+
2. **降级是分级的。** `prefers-reduced-motion` 下位移与缩放**关掉**,
|
|
49
|
+
内容直接出现(不做淡入)。全库那条 `0.01ms !important` 兜底会把一切灭掉,
|
|
50
|
+
本文件用更具体的规则覆盖它 —— 因为"关掉位移"不等于"关掉可见性"。
|
|
51
|
+
3. **只用于区块级入场。** 不要给表格行、列表项逐行加:那是扫读场景,
|
|
52
|
+
用户要在半秒内横向比三行数字,逐行错峰会让比较变成"等动画"。
|
|
53
|
+
|
|
54
|
+
## 类名
|
|
55
|
+
|
|
56
|
+
| 类名 | 是什么 |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `.reveal` | 本组件在契约里的根名(属性驱动,**不需要**真的写这个类) |
|
|
59
|
+
|
|
60
|
+
## 边界
|
|
61
|
+
|
|
62
|
+
- **不做**:视差、滚动进度驱动(scroll-driven animations)、无限循环装饰动画。
|
|
63
|
+
- **不做**:出场动画。元素不该为了"好看"先消失。
|
|
64
|
+
- 老 WebView:`IntersectionObserver` 缺失时全部直接显示,不降级成 `scroll` 监听
|
|
65
|
+
(那会把主线程拖住,是比"没有动效"更差的交换)。
|
|
66
|
+
|
|
67
|
+
## 为什么**没有**「动画进行中不打断」的守卫(实测结论)
|
|
68
|
+
|
|
69
|
+
参考实现(huxx.me)有一条经验:进场动画在跑的时候不要被打断,
|
|
70
|
+
用 `getAnimations().some(a => a.playState === 'running')` 判断,
|
|
71
|
+
说"快速滚动时这条决定了顺滑还是闪"。
|
|
72
|
+
|
|
73
|
+
**那条针对的是它自己的 JS 动画**(Web Animations API 重启动画会跳)。
|
|
74
|
+
我们用的是 CSS transition —— 被打断时是 **retarget**:从当前值继续插值,不重头再来。
|
|
75
|
+
|
|
76
|
+
`05-audit/motion-concurrency.js` 在真浏览器里量过,结论:
|
|
77
|
+
|
|
78
|
+
| 判据 | 实测 |
|
|
79
|
+
|---|---|
|
|
80
|
+
| 切状态是否产生动画对象 | ✅ 显示态 2 个(opacity + transform)、隐藏态 2 个 |
|
|
81
|
+
| 同属性同时运行的动画数 | ✅ 最多 **1**(快速触发 5 次、采样 39 帧) |
|
|
82
|
+
| `prefers-reduced-motion: reduce` | ✅ 动画 **0** 个,opacity 恒为 1 |
|
|
83
|
+
|
|
84
|
+
⇒ **不需要加守卫。** 加了反而会有害:隐藏方向若"等动画播完再执行",
|
|
85
|
+
元素已经在离场,总位移变大,看起来是"走的时候还亮一下"。
|
|
86
|
+
(我第一版就是这么改的,被同一条判据算出来的总位移否掉了,已撤回。)
|
|
87
|
+
|
|
88
|
+
**重开条件**:某天 reveal 改用 Web Animations API(或换成 `@keyframes` 重启动画),
|
|
89
|
+
这条结论立即失效 —— 那时必须重新量,并且很可能需要守卫。
|
|
90
|
+
|
|
91
|
+
⚠️ 这属于**负面结论入档**:写下来是为了下次不要再"看到别人有就照抄一遍"。
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<link rel="icon" href="data:,">
|
|
7
|
+
<title>reveal · 入场动效</title>
|
|
8
|
+
<link rel="stylesheet" href="../../01-tokens/tokens.css">
|
|
9
|
+
<link rel="stylesheet" href="../../01-tokens/typography.css">
|
|
10
|
+
<link rel="stylesheet" href="../../01-tokens/page.css">
|
|
11
|
+
<link rel="stylesheet" href="../../02-primitives/button/button.css">
|
|
12
|
+
<link rel="stylesheet" href="../../03-patterns/reveal/reveal.css">
|
|
13
|
+
<style>
|
|
14
|
+
.rv-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin: 16px 0 24px; }
|
|
15
|
+
.rv-box { padding: 20px 12px; text-align: center; border-radius: var(--radius-md);
|
|
16
|
+
background: var(--surface-raised); color: var(--text-primary);
|
|
17
|
+
border: 1px solid var(--border-subtle); font-size: var(--fs-sm); }
|
|
18
|
+
.rv-row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
|
|
19
|
+
.rv-status { font-family: var(--mono); font-size: var(--fs-xs); color: var(--text-secondary); }
|
|
20
|
+
.rv-list { display: grid; gap: 8px; margin: 16px 0 24px; }
|
|
21
|
+
.rv-item { padding: 12px; border-radius: var(--radius-sm); background: var(--surface-raised);
|
|
22
|
+
border: 1px solid var(--border-subtle); font-size: var(--fs-sm); }
|
|
23
|
+
.rv-note { font-size: var(--fs-sm); color: var(--text-secondary); }
|
|
24
|
+
.rv-spacer { height: 40px; }
|
|
25
|
+
</style>
|
|
26
|
+
</head>
|
|
27
|
+
<body class="page">
|
|
28
|
+
<main class="wrap">
|
|
29
|
+
<h1>reveal · 入场动效</h1>
|
|
30
|
+
<p class="lede">
|
|
31
|
+
给内容挂一个 <code>data-reveal</code> 属性,它就会在进入视口时从指定方向就位。
|
|
32
|
+
六个方向共用一套 <code>transform</code>,位移上限 8px、时长只用令牌、错峰 40ms 封顶 6 个。
|
|
33
|
+
<strong>本页加载时是静态的</strong>:点下面按钮才由引擎接管 —— 这样截图基线可复现,
|
|
34
|
+
也说明"没有 JS 时内容照常可见"。
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
<div class="rv-row">
|
|
38
|
+
<button class="btn btn--md btn--primary" id="play" type="button">播放入场</button>
|
|
39
|
+
<button class="btn btn--md btn--ghost" id="reset" type="button">复原</button>
|
|
40
|
+
<span class="rv-status" id="status">引擎未接管(内容全可见)</span>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="rv-spacer"></div>
|
|
44
|
+
|
|
45
|
+
<h2 class="rule-top">六个方向</h2>
|
|
46
|
+
<div class="rv-grid">
|
|
47
|
+
<div class="rv-box" data-reveal="up">up · 从下往上 8px</div>
|
|
48
|
+
<div class="rv-box" data-reveal="down">down · 从上往下 8px</div>
|
|
49
|
+
<div class="rv-box" data-reveal="left">left · 从左往右 8px</div>
|
|
50
|
+
<div class="rv-box" data-reveal="right">right · 从右往左 8px</div>
|
|
51
|
+
<div class="rv-box" data-reveal="scale">scale · .97 放大到位</div>
|
|
52
|
+
<div class="rv-box" data-reveal="none">none · 只淡入</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<h2 class="rule-top">分批(不用逐个标)</h2>
|
|
56
|
+
<p class="rv-note">
|
|
57
|
+
容器写 <code>data-reveal-children="up"</code>,子元素自动依次入场。
|
|
58
|
+
间隔 40ms,超过第 6 个之后<strong>同时</strong>开始 —— 否则第 20 个要等 800ms。
|
|
59
|
+
</p>
|
|
60
|
+
<div class="rv-list" data-reveal-children="up">
|
|
61
|
+
<div class="rv-item">第 1 项</div>
|
|
62
|
+
<div class="rv-item">第 2 项</div>
|
|
63
|
+
<div class="rv-item">第 3 项</div>
|
|
64
|
+
<div class="rv-item">第 4 项</div>
|
|
65
|
+
<div class="rv-item">第 5 项</div>
|
|
66
|
+
<div class="rv-item">第 6 项</div>
|
|
67
|
+
<div class="rv-item">第 7 项(已封顶,与第 6 项同时)</div>
|
|
68
|
+
<div class="rv-item">第 8 项(同上)</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<h2 class="rule-top">单独延迟</h2>
|
|
72
|
+
<div class="rv-grid">
|
|
73
|
+
<div class="rv-box" data-reveal="up" data-reveal-delay="120ms">延迟 120ms</div>
|
|
74
|
+
<div class="rv-box" data-reveal="up" data-reveal-delay="240ms">延迟 240ms</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<h2 class="rule-top">怎么接到自己的页面</h2>
|
|
78
|
+
<p class="rv-note">
|
|
79
|
+
两种接法,二选一:<br>
|
|
80
|
+
① 在 <code><html></code> 上写 <code>data-reveal-auto</code>,脚本加载后自动接管;<br>
|
|
81
|
+
② 手动调 <code>Reveal.enable()</code>(本页就是这种,方便截图与演示)。<br>
|
|
82
|
+
都不写 ⇒ 完全不接管:内容可见、位置不偏。<strong>这是刻意的默认值。</strong>
|
|
83
|
+
</p>
|
|
84
|
+
</main>
|
|
85
|
+
|
|
86
|
+
<script src="../../03-patterns/reveal/reveal.js"></script>
|
|
87
|
+
<script>
|
|
88
|
+
(function () {
|
|
89
|
+
'use strict';
|
|
90
|
+
var play = document.getElementById('play');
|
|
91
|
+
var reset = document.getElementById('reset');
|
|
92
|
+
var status = document.getElementById('status');
|
|
93
|
+
|
|
94
|
+
play.addEventListener('click', function () {
|
|
95
|
+
Reveal.replay();
|
|
96
|
+
status.textContent = '引擎已接管(已就位的内容保持可见,未进入视口的会隐藏)';
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
reset.addEventListener('click', function () {
|
|
100
|
+
Reveal.disable();
|
|
101
|
+
status.textContent = '引擎未接管(内容全可见)';
|
|
102
|
+
});
|
|
103
|
+
})();
|
|
104
|
+
</script>
|
|
105
|
+
</body>
|
|
106
|
+
</html>
|