Initial commit: RHINE LAB · ANALYSIS OS:三维界面与动效实验(本仓库不含个人归档索引数据)

This commit is contained in:
WpyQwq
2026-09-19 13:04:56 +08:00
commit 3f422218a5
1098 changed files with 39410 additions and 0 deletions
+184
View File
@@ -0,0 +1,184 @@
<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8" />
<title>标题与查看器过渡检查</title>
<style>
body {
margin: 0;
background: #eae5e1;
color: #252620;
font: 13px monospace;
}
iframe {
width: 1100px;
height: 620px;
border: 0;
}
pre {
padding: 16px;
white-space: pre-wrap;
}
</style>
<iframe src="/?scene=archive" title="真实交互场景"></iframe>
<pre id="result">正在检查…</pre>
<script type="module">
import { records } from "/src/data.ts";
import { createRollingText } from "@kitlangton/rolling-number";
import "@kitlangton/rolling-number/styles.css";
const iframe = document.querySelector("iframe"),
result = document.querySelector("#result");
const checks = [];
const pause = (ms) => new Promise((r) => setTimeout(r, ms));
function check(ok, label) {
if (!ok) throw new Error(label);
checks.push(label);
}
async function until(fn) {
const start = performance.now();
while (!fn()) {
if (performance.now() - start > 10000) throw new Error("等待状态超时");
await pause(25);
}
}
try {
await until(
() =>
iframe.contentWindow.rhine?.stats().loaded &&
!iframe.contentDocument.querySelector("#loading") &&
iframe.contentWindow.rhine.stats().mode === "archive",
);
const win = iframe.contentWindow,
doc = iframe.contentDocument,
app = win.rhine;
app.archive();
await pause(300);
const title = doc.querySelector("#selected-title");
const value = () => title.querySelector(".rn-value")?.textContent;
const moving = () =>
title
.getAnimations({ subtree: true })
.some((a) => a.playState === "running");
app.select(1);
await until(moving);
check(
value() === records[1].title,
"普通切换播放文字滚动并立即更新可访问标题",
);
app.select(2);
app.select(3);
check(value() === records[3].title, "快速切换以最后选择为目标");
check(
win.getComputedStyle(title, "::after").content === "none",
"快切期间不再显示黑色横条",
);
app.select(4);
app.select(5);
await pause(60);
await until(() => !moving());
check(value() === records[5].title, "停止后滚动落到最终标题");
const finalGlyphs = [...title.querySelectorAll(".rn-visual .rn-face")]
.map((face) => face.textContent)
.join("");
check(finalGlyphs === records[5].title, "滚动完成后没有残留的旧文字");
app.select(6);
app.select(7);
app.detail();
check(
!moving() && value() === records[7].title,
"切进详情结束标题滚动并保留最终文字",
);
await until(() => app.stats().canInspect);
const opener = doc.querySelector('[data-action="model-viewer"]');
opener.focus();
opener.click();
const viewer = doc.querySelector(".model-viewer");
check(
viewer.dataset.transition === "opening" && !viewer.hidden,
"查看器保留进入动画阶段",
);
const before = win.getComputedStyle(viewer).opacity;
viewer
.querySelector("canvas")
.dispatchEvent(
new win.KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
);
check(
viewer.dataset.transition === "closing" && !viewer.hidden,
"进入途中 Esc 转入退出动画",
);
check(
doc.querySelector("#detail-ui").inert,
"退出动画结束前详情仍不接收输入",
);
await until(() => viewer.hidden);
check(
!doc.querySelector("#detail-ui").inert && doc.activeElement === opener,
"退出结束恢复详情与原按钮焦点",
);
opener.click();
await until(
() =>
viewer.dataset.transition === "open" &&
JSON.parse(viewer.dataset.stats || "{}").ready,
);
check(!viewer.hidden, "中途关闭后可重新打开并加载模型");
viewer.querySelector('[data-viewer="explode"]').click();
await pause(100);
viewer.querySelector('[data-viewer="close"]').click();
viewer.querySelector('[data-viewer="close"]').click();
await until(() => viewer.hidden);
check(
!doc.querySelector("#detail-ui").inert,
"拆解中重复关闭也能完整退出",
);
const host = document.createElement("span");
host.style.cssText =
"display:inline-block;font:24px system-ui;margin:16px";
document.body.append(host);
const text = createRollingText(host, {
text: "初始",
transition: "direct",
stagger: "none",
duration: 460,
});
await pause(100);
text.update({ text: "长期实验研究记录" });
await pause(90);
text.update({ text: "光" });
await pause(60);
text.update({ text: "光学研究 🧬 / A-01" });
await pause(100);
await until(
() =>
!host
.getAnimations({ subtree: true })
.some((a) => a.playState === "running"),
);
check(
host.querySelector(".rn-value").textContent === "光学研究 🧬 / A-01",
"长短标题与中文、符号混排能正确收敛",
);
check(
host.querySelectorAll(".rn-slot").length <=
[...new Intl.Segmenter().segment("光学研究 🧬 / A-01")].length,
"连续替换后回收旧字槽",
);
text.update({ text: "最终标题", animated: false });
check(
host.querySelector(".rn-value").textContent === "最终标题" &&
host.getAnimations({ subtree: true }).length === 0,
"减少动态效果立即显示最终标题",
);
text.destroy();
host.remove();
result.textContent = JSON.stringify({ passed: true, checks }, null, 2);
} catch (error) {
result.textContent = JSON.stringify(
{ passed: false, checks, error: error.message },
null,
2,
);
console.error(error);
}
</script>
</html>