Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,5 @@
详情查看器默认完整留在当前窗口内。全部资源在自动模式中使用预览容器的实际宽高、100% 比例重新排版,组件保持正常字号;手动设备模拟按可用宽度等比适应。桌面关闭“适应画布”可按当前设备的原尺寸滚动查看。长演示内容、源码和说明在各自区域内滚动,外层页面无需下滑。高度不超过 480px 时收起站点导航,可用查看器左上角返回按钮回到资源列表。[适配规则与截图](docs/ui/viewport-fit/README.md)。

组件详情在宽屏合并标题与内容操作行,动效参数位于预览工具栏,说明栏减少重复标签与留白。详情说明覆盖在画布右侧,独立滚动;开关详情不改变演示尺寸或位置。窄屏说明覆盖预览,关闭后恢复操作。查看完整画布时可关闭说明或进入全屏。[稳定性对比与验证](docs/ui/stable-details/README.md)。[空间与清晰度对比](docs/ui/component-clarity/README.md)。

原生页面缩放与经典滚动条也纳入验证:自动预览用容器百分比保持小数尺寸,设备模拟按实际宽度适应,避免 110% 等倍率下反复触发滚动条。[缩放稳定性与验证](docs/ui/native-zoom/README.md)。
34 changes: 20 additions & 14 deletions atlas/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,23 +327,29 @@ export function createViewer({
if (layout.style.getPropertyValue("--preview-tools-height") !== toolsHeight)
layout.style.setProperty("--preview-tools-height", toolsHeight);
const automatic = !appState.width && (innerWidth < 1024 || appState.fit);
const desired = automatic
? viewport.clientWidth
: appState.width || appState.selected.preview.width;
// Device presets constrain width. Give their document the visible height,
// so short windows scroll inside the demo instead of shrinking all controls.
const desired = appState.width || appState.selected.preview.width;
// Browser zoom produces fractional content boxes. Percentage constraints
// fit those boxes exactly; writing rounded clientWidth/clientHeight back
// can alternately add/remove classic scrollbars on every observer callback.
canvas.style.width = automatic
? "100%"
: appState.fit
? `min(100%, ${desired}px)`
: desired + "px";
canvas.style.height =
automatic || appState.width
? "100%"
: appState.selected.preview.height + "px";
const scale =
automatic || !appState.fit
? 1
: Math.min(1, viewport.clientWidth / desired);
const h =
automatic || appState.width
? viewport.clientHeight / scale
: appState.selected.preview.height;
canvas.style.width = desired * scale + "px";
canvas.style.height = h * scale + "px";
frame.style.width = desired + "px";
frame.style.height = h + "px";
: Math.min(1, parseFloat(getComputedStyle(canvas).width) / desired);
frame.style.width = automatic ? "100%" : desired + "px";
frame.style.height = automatic
? "100%"
: appState.width
? 100 / scale + "%"
: appState.selected.preview.height + "px";
frame.style.transform = `scale(${scale})`;
element("#preview-scale").textContent = `${Math.round(scale * 100)}%`;
}
Expand Down
38 changes: 38 additions & 0 deletions docs/ui/native-zoom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 原生缩放与真实滚动条下的预览稳定性

本轮处理的是整幅预览 UI 的尺寸振荡。用户确认 Edge 页面缩放 100% 正常,110% 后静置也抖动;Windows 显示缩放为 100%,鼠标移到页面外仍发生。

## 原因与复现

原先读取整数 `clientWidth/clientHeight`,再将它们写回画布和 iframe 的像素尺寸。原生缩放下容器实际宽高可能是小数,整数回写产生溢出。经典滚动条出现后可用宽高缩小,ResizeObserver 再缩小画布,滚动条消失后又放大,形成逐帧循环。

Playwright 默认在无头模式加入 `--hide-scrollbars`。此前测试没有保留滚动条,遗漏了该循环。本轮测试明确移除这个默认参数,启动完整 Chromium,通过浏览器设置 API 改变原生页面缩放,并验证 DPR,未用 CSS zoom、捏合模拟或单纯改变 viewport 代替。

同一旧版本 `66bb953`、1910×873 浏览器窗口、110% 原生缩放下,90 帧触发 90 次 iframe resize,可用宽高在 1727×572 与 1713×558 CSS px 之间反复切换。1440px 窗口的 150%、1273px 窗口的 125% 也复现。

## 修复与结果

自动画布与 iframe 以百分比匹配真实容器;设备模式用 CSS `min()` 限制宽度,再从实际计算宽度得到缩放比例,高度也由百分比承载。原始尺寸模式继续允许必要滚动,不关闭浏览器滚动条,不停止演示动画。

| 条件 | 修复前 | 修复后 |
| ----------------------------- | ------------ | ------------ |
| 110%、90 帧中的 iframe resize | 90 次 | 0 次 |
| 同一静置区间的画布状态 | 两组反复切换 | 一组稳定状态 |
| iframe 内部经典滚动条 | 显示 | 显示 |

[完整对比及逐倍率数据](measurements.json)、[Linux Edge 110% 截图](edge-110.png)。每个倍率的前后状态比较在各自的静置区间内进行;主动改变缩放、窗口、设备或全屏产生的正常尺寸变化不计为抖动。

## 验收

- 完整 Chromium 原生设置:100%、110%、125%、150%、175%、200%、返回 100% 再到 110%。同一页面实时切换,也验证在 110% 重新载入。
- 1910×873、1440×655、900×900 浏览器窗口,保留经典滚动条;自动、375/768px 设备、原始尺寸、详情、全屏及退出。
- 静置逐帧比较画布、卡片、内部根节点和滚动位置,要求零 iframe resize;缩放和控制操作不增加文档请求,原文档和表单输入保留。
- 三引擎既有关键流程与完整生产测试继续执行。另在临时解压的 Microsoft Edge 155.0.4283.62(Linux)中实际执行相同原生缩放与 90 帧检查;没有安装到系统或修改依赖版本。尚未在原生 Windows 上人工验证。

测试见 `tests/native-zoom.spec.mjs` 与 `tests/native-zoom.mjs`。浏览器窗口尺寸包含浏览器 UI,记录与截图以实际网页尺寸及 DPR 为准。

本地最终验证:`npm run check` 通过(严格类型检查与 12 项单元测试);`npm test` 先完成生产构建,再通过全部 156 项浏览器测试(Chromium 140、Firefox 8、WebKit 8),无跳过或 flaky;`npm run test:dev` 的热更新验收通过。原生缩放、跨浏览器与沉浸预览的 31 项定向回归也通过。独立 Linux Edge 与 Chromium 的各 8 轮原生倍率检查,每轮 90 帧均为零 resize、一组稳定状态。

## 发布与回退

CI 成功后以 Merge 合并,沿用现有 Pages 推送自动流程。以部署运行关联的确切 merge SHA、成功状态、线上新代码及 110% 真实滚动条检查共同确认发布。前一版为 `66bb953`;如需回退,应通过回退 PR、CI 和同一 Pages 流程恢复,保留历史,不强推主分支。本轮不额外创建 Release 或升级版本。
Binary file added docs/ui/native-zoom/edge-110.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading