/~learn/s/f5901593
> 📅 2026-10-21 建立(2026-10-22 修订:滚动方案由「flex 填满 + 表格内滚动」改为「页面级滚动 + 三级吸顶」) > 起因:截面选基页在 Android 自由窗口(浮窗悬在游戏上方,约 380–400 × 570–600 css px)下顶部挤压表格、底部留 150px 大空白,连续三轮返工才修好;随后又发现表格自己滚动时顶部标题僵住并被截断,第四轮改为页面级滚动 + 导航层/表头两级吸顶。 > 适用范围:本网站所有新增页面、所有改动布局的页面。新页面上线前必须过一遍本文档的验收清单。
---
| 档位 | 媒体查询 | 典型场景 |
| ------ | --------- | --------- |
| 桌面宽屏 | 默认样式 | PC 全屏浏览器 |
| 小屏 / 平板小窗 | @media(max-width:768px),(max-height:760px) | 平板分屏、iPad Stage Manager、手机横屏 |
| 极小窗 / 手机浮窗 | @media(max-width:520px),(max-height:620px) | Android 自由窗口、手机悬浮窗(实测 380×570 / 400×600) |
> ⚠️ 两个档位必须同时写 max-width 和 max-height。只用 max-width 会漏掉「宽但矮」的横屏浮窗,只用 max-height 会漏掉「窄但高」的竖屏浮窗。
---
.page 承担滚动(页面级滚动,不是表格内滚动)@media(max-width:768px),(max-height:760px){
/* .page 变成整页唯一滚动容器:标题/工具栏随内容自然滚出视野 */
.page{height:100vh;height:100dvh;overflow:auto;padding:0 0 8px;
overscroll-behavior-y:contain;-webkit-overflow-scrolling:touch;}
.page > *{padding-left:10px;padding-right:10px;} /* 横向滚动时吸顶块左右不漏缝 */
.tab-pane.active{overflow:visible;} /* 见规则 3,pane 不能再是滚动容器 */
}
.page 滚动后,顶部自然滚走、底部也不留白。max-height:calc(100dvh - 274px) 这类魔数。
position:static),随页面滚动滚出视野;只让「导航/表头」层吸顶。flex-wrap:nowrap + overflow-x:auto(隐藏滚动条),保证高度恒为一行。#pageSub 类)小窗一律 display:none。
小窗下滚动顺序必须是:先让标题区滚走,然后导航层(tabs + 列筛选栏)二次吸顶,表格 thead 再压在导航层下沿三次吸顶。关键约束链(踩过的坑都在这里):
.cs-sticky-head{position:sticky;top:0;left:0;z-index:40;background:var(--bg);} /* 二次吸顶,left:0 防横向滚动被卷走 */
.tab-pane.active{overflow:visible;} /* ① 必须 visible,否则 pane 就是滚动容器 */
.rank-table thead th{top:var(--cs-thead-top,44px);} /* ③ 偏移 = 导航层实测高度 */
.rank-table thead th.cs-frz{top:var(--cs-thead-top,44px);}/* ④ 冻结列表头特异性更高,必须单独覆盖 */
position:sticky 只对最近滚动祖先生效:pane 只要 overflow 任一轴不是 visible,另一轴计算值也会变 auto,thead 就只相对 pane 吸附,页面滚动时表头反而跟着滚走。故小窗下 pane 必须 overflow:visible,横向溢出也交给 .page。.page 承担时,吸顶块要 position:sticky;top:0;left:0 双轴锁定,否则横向拖动会把吸顶栏卷出左边。--cs-thead-top(getBoundingClientRect().height),并在 数据渲染完成后 / resize / 切 tab 时重算,外加 ResizeObserver 监听导航层自身(chips 换行、字体加载都会改高度)。th.cs-frz{position:sticky;top:0},特异性 (0,2,1) 高于 .rank-table thead th(0,1,2),必须在小窗块单独覆盖 top,否则它藏在导航层底下、下层行文字从缝里透出来。scrollTop 推到导航层顶部(if(off>0 && page.scrollTop<off),已滚过就不回拉),让新表格直接从吸顶栏下沿开始。
父级一旦设了 overflow-x:auto,内部 position:absolute 的下拉/气泡会被裁剪。小窗下改为:
.popover{position:fixed;left:10px;right:10px;top:56px;width:auto;max-width:none;}
.popover::before,.popover::after{display:none!important;} /* 小箭头指向已失效,隐藏 */
小窗下把图表外层高度压到 min(320px, 45dvh) 量级,避免一张图占满整个浮窗。
---
Chrome DevTools 用 resize_page 调到 500×570(桌面 Chrome 最小宽度 500,等价覆盖两个小窗档)、emulate 移动视口 393x600x3,mobile,touch(真实浮窗尺寸)和 1280×800,在页面控制台跑:
(() => {
const p = document.querySelector('.page');
const cs = getComputedStyle(p);
const head = document.getElementById('csStickyHead'); // 导航层吸顶块
const th = document.querySelector('.tab-pane.active thead th:not(.cs-frz)');
p.scrollTop = 300; // 先滚过顶部
const top = el => el ? Math.round(el.getBoundingClientRect().top) : null;
return {
pageOverflowY: cs.overflowY, // 小窗必须是 auto
bottomGap: Math.round(innerHeight - p.getBoundingClientRect().bottom), // ≤ 10
headTop: top(head), // 0
headBottom: Math.round(head.getBoundingClientRect().bottom), // 导航层下沿
theadTop: top(th), // 必须 == headBottom
frzTop: top(document.querySelector('thead th.cs-frz')), // 必须 == headBottom
titleTop: top(document.querySelector('.cs-title-row')) // 必须 < 0(已滚出)
};
})()
| 检查项 | 通过标准 |
| -------- | --------- |
| 底部空白 | bottomGap ≤ 10px |
| 滚动主体 | 小窗下 .page 的 overflowY 为 auto;表格 pane 为 visible |
| 二次吸顶 | 滚动后导航层 headTop === 0,标题 titleTop < 0 |
| 三次吸顶 | theadTop === headBottom(含冻结列 th.cs-frz,两者必须一致) |
| 顶部工具栏 | 所有子元素 top 值相同(单行),且不吸顶 |
| 横向溢出 | 横向拖动时吸顶块 left 仍为 0(sticky;left:0 生效) |
| 弹层 | 打开下拉/气泡,四边都不被裁剪 |
| 桌面回归 | 1280×800 下 .page 为 visible、导航层 position:static、thead top:0,布局与改动前一致 |
---
CACHE 还是旧版本号,用户刷新拿到的仍是旧页面(本项目曾因此连续三轮「还是不行」)。见 网络流量监控 同期引入的 SW 机制。unified_server.py 里的 CSS 花括号必须双写 {{ }}:模板走 Python .format(),单层会抛 KeyError。Cache-Control: no-cache 或带版本号查询串。---
CROSS_SECTION_PAGE 模板,@media(max-width:768px),(max-height:760px) 与 @media(max-width:520px),(max-height:620px) 两块:.page 100dvh 唯一滚动容器 + .cs-sticky-head(tabs+列栏)二次吸顶 + thead 三次吸顶,偏移由 csSyncSticky() 写 --cs-thead-top;顶部单行 + 弹层 fixed)