| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>列表滚动体验 Demo · Fresh Mint</title>
- <style>
- :root {
- --primary: #10b981;
- --primary-dark: #059669;
- --primary-bg: #ecfdf5;
- --text: #1e293b;
- --text-muted: #64748b;
- --border: #e2e8f0;
- --surface: #fff;
- --bg: #f1f5f9;
- --table-head: #f8fafc;
- --row-alt: #f8fafc;
- --row-hover: #f0fdf4;
- --radius: 10px;
- --danger: #dc2626;
- --warning: #d97706;
- --shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
- font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
- }
- * { box-sizing: border-box; }
- body {
- margin: 0;
- background: var(--bg);
- color: var(--text);
- font-size: 14px;
- line-height: 1.5;
- }
- .page {
- max-width: 1180px;
- margin: 0 auto;
- padding: 24px 20px 48px;
- }
- .hero {
- background: linear-gradient(135deg, #ecfdf5 0%, #fff 55%, #f8fafc 100%);
- border: 1px solid var(--border);
- border-radius: 14px;
- padding: 22px 24px;
- margin-bottom: 18px;
- box-shadow: var(--shadow);
- }
- .hero h1 {
- margin: 0 0 8px;
- font-size: 22px;
- font-weight: 700;
- color: var(--text);
- }
- .hero h1::before {
- content: "";
- display: inline-block;
- width: 4px;
- height: 1.05em;
- margin-right: 10px;
- border-radius: 2px;
- background: var(--primary);
- vertical-align: -0.12em;
- }
- .hero p {
- margin: 0;
- color: var(--text-muted);
- max-width: 780px;
- }
- .problem-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-top: 14px;
- }
- .pill {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 4px 10px;
- border-radius: 999px;
- font-size: 12px;
- font-weight: 600;
- border: 1px solid transparent;
- }
- .pill.warn {
- background: #fffbeb;
- color: #b45309;
- border-color: #fde68a;
- }
- .pill.ok {
- background: var(--primary-bg);
- color: var(--primary-dark);
- border-color: #a7f3d0;
- }
- .tabs {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-bottom: 14px;
- }
- .tab {
- appearance: none;
- border: 1px solid var(--border);
- background: var(--surface);
- color: var(--text);
- border-radius: 8px;
- padding: 8px 14px;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- transition: 0.15s ease;
- }
- .tab:hover { border-color: #a7f3d0; background: var(--primary-bg); }
- .tab.active {
- background: var(--primary);
- border-color: var(--primary);
- color: #fff;
- }
- .tab .badge {
- display: inline-block;
- margin-left: 6px;
- padding: 1px 6px;
- border-radius: 999px;
- font-size: 11px;
- font-weight: 700;
- background: rgba(255,255,255,0.22);
- }
- .tab:not(.active) .badge {
- background: #fee2e2;
- color: #b91c1c;
- }
- .tab:not(.active).recommend .badge {
- background: var(--primary-bg);
- color: var(--primary-dark);
- }
- .panel {
- background: var(--surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- box-shadow: var(--shadow);
- overflow: hidden;
- }
- .panel-top {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 16px;
- padding: 14px 18px;
- border-bottom: 1px solid var(--border);
- background: var(--table-head);
- }
- .panel-top h2 {
- margin: 0 0 4px;
- font-size: 16px;
- font-weight: 700;
- }
- .panel-top .desc {
- margin: 0;
- color: var(--text-muted);
- font-size: 13px;
- max-width: 720px;
- }
- .meta {
- flex-shrink: 0;
- font-size: 12px;
- color: var(--text-muted);
- text-align: right;
- }
- .meta strong { color: var(--primary-dark); }
- .callout {
- margin: 0;
- padding: 10px 18px;
- font-size: 13px;
- border-bottom: 1px solid var(--border);
- }
- .callout.bad { background: #fef2f2; color: #991b1b; }
- .callout.good { background: var(--primary-bg); color: #065f46; }
- .callout code {
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 12px;
- background: rgba(0,0,0,0.05);
- padding: 1px 5px;
- border-radius: 4px;
- }
- .toolbar {
- display: flex;
- align-items: center;
- gap: 8px;
- flex-wrap: wrap;
- padding: 8px 16px;
- background: var(--table-head);
- border-bottom: 1px solid var(--border);
- }
- .sel {
- font-size: 12px;
- font-weight: 600;
- color: var(--text-muted);
- padding-right: 10px;
- margin-right: 4px;
- border-right: 1px solid var(--border);
- min-height: 36px;
- display: inline-flex;
- align-items: center;
- }
- .sel b { color: var(--primary-dark); margin: 0 2px; }
- .btn {
- height: 36px;
- padding: 0 14px;
- border-radius: 8px;
- border: 1px solid #d9e8e2;
- background: #fff;
- color: var(--text);
- font-size: 13px;
- font-weight: 600;
- cursor: default;
- }
- .btn.main {
- background: var(--primary);
- border-color: var(--primary);
- color: #fff;
- }
- .viewport {
- /* 模拟管理端内容区可视高度 */
- height: min(420px, 58vh);
- overflow: auto;
- background: #fff;
- position: relative;
- }
- .viewport-label {
- position: sticky;
- top: 0;
- z-index: 20;
- pointer-events: none;
- display: flex;
- justify-content: flex-end;
- padding: 6px 10px 0 0;
- height: 0;
- }
- .viewport-label span {
- pointer-events: none;
- font-size: 11px;
- font-weight: 700;
- color: #0369a1;
- background: #e0f2fe;
- border: 1px solid #7dd3fc;
- border-radius: 999px;
- padding: 2px 8px;
- }
- table.data {
- width: max-content;
- min-width: 1400px;
- border-collapse: collapse;
- font-size: 13px;
- }
- table.data th,
- table.data td {
- padding: 10px 14px;
- border-bottom: 1px solid var(--border);
- text-align: center;
- white-space: nowrap;
- }
- table.data th {
- font-size: 12px;
- font-weight: 600;
- color: var(--text-muted);
- background: var(--table-head);
- }
- table.data tbody tr:nth-child(even) td { background: var(--row-alt); }
- table.data tbody tr:hover td { background: var(--row-hover); }
- table.data .left { text-align: left; }
- table.data .op { min-width: 180px; }
- .link { color: var(--primary-dark); font-weight: 600; text-decoration: none; }
- .tag {
- display: inline-block;
- padding: 2px 8px;
- border-radius: 6px;
- font-size: 12px;
- font-weight: 600;
- color: #fff;
- background: var(--primary-dark);
- }
- .tag.warn { background: var(--warning); }
- .pager {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 10px 16px;
- border-top: 1px solid var(--border);
- color: var(--text-muted);
- font-size: 13px;
- background: #fff;
- }
- /* —— 方案 A:表体内嵌滚动(推荐) —— */
- .scheme-a .table-frame {
- height: 320px;
- overflow: auto;
- border-top: 1px solid var(--border);
- }
- .scheme-a thead th {
- position: sticky;
- top: 0;
- z-index: 3;
- box-shadow: 0 1px 0 rgba(0,0,0,0.06);
- }
- .scheme-a .th-check,
- .scheme-a .td-check {
- position: sticky;
- left: 0;
- z-index: 2;
- background: #fff;
- box-shadow: 6px 0 10px -6px rgba(0,0,0,0.12);
- }
- .scheme-a thead .th-check {
- z-index: 6;
- background: var(--table-head);
- }
- .scheme-a tbody tr:nth-child(even) .td-check { background: var(--row-alt); }
- .scheme-a tbody tr:hover .td-check { background: var(--row-hover); }
- .scheme-a .th-op,
- .scheme-a .td-op {
- position: sticky;
- right: 0;
- z-index: 2;
- background: #fff;
- box-shadow: -6px 0 10px -6px rgba(0,0,0,0.12);
- }
- .scheme-a thead .th-op {
- z-index: 5;
- background: var(--table-head);
- }
- .scheme-a tbody tr:nth-child(even) .td-op { background: var(--row-alt); }
- .scheme-a tbody tr:hover .td-op { background: var(--row-hover); }
- /* —— 方案 B:页面滚动 + 表头 sticky + 底部悬浮横滚条 —— */
- .scheme-b .page-scroll {
- height: 320px;
- overflow: auto;
- border-top: 1px solid var(--border);
- position: relative;
- }
- .scheme-b thead th {
- position: sticky;
- top: 0;
- z-index: 3;
- box-shadow: 0 1px 0 rgba(0,0,0,0.06);
- }
- .scheme-b .float-hbar-wrap {
- position: sticky;
- bottom: 0;
- z-index: 8;
- height: 14px;
- background: rgba(248, 250, 252, 0.92);
- border-top: 1px solid var(--border);
- backdrop-filter: blur(2px);
- }
- .scheme-b .float-hbar {
- overflow-x: auto;
- overflow-y: hidden;
- height: 14px;
- }
- .scheme-b .float-hbar-inner {
- height: 1px;
- }
- /* —— 方案 C:顶部镜像横滚条 + 表体内嵌滚动 —— */
- .scheme-c .top-hbar {
- overflow-x: auto;
- overflow-y: hidden;
- height: 14px;
- border-top: 1px solid var(--border);
- background: var(--table-head);
- }
- .scheme-c .top-hbar-inner { height: 1px; }
- .scheme-c .table-frame {
- height: 306px;
- overflow: auto;
- }
- .scheme-c thead th {
- position: sticky;
- top: 0;
- z-index: 3;
- box-shadow: 0 1px 0 rgba(0,0,0,0.06);
- }
- .hint-row {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 10px;
- margin-top: 14px;
- }
- @media (max-width: 900px) {
- .hint-row { grid-template-columns: 1fr; }
- }
- .hint-card {
- background: #fff;
- border: 1px solid var(--border);
- border-radius: 10px;
- padding: 12px 14px;
- box-shadow: var(--shadow);
- }
- .hint-card h3 {
- margin: 0 0 6px;
- font-size: 13px;
- font-weight: 700;
- }
- .hint-card p {
- margin: 0;
- font-size: 12px;
- color: var(--text-muted);
- }
- .hidden { display: none !important; }
- .choose {
- margin-top: 18px;
- padding: 16px 18px;
- background: #fff;
- border: 1px solid var(--border);
- border-radius: 12px;
- }
- .choose h3 { margin: 0 0 10px; font-size: 15px; }
- .choose-options {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 10px;
- }
- @media (max-width: 900px) {
- .choose-options { grid-template-columns: 1fr; }
- }
- .choose-opt {
- border: 1px solid var(--border);
- border-radius: 10px;
- padding: 12px;
- cursor: pointer;
- transition: 0.15s ease;
- background: #fff;
- }
- .choose-opt:hover { border-color: #a7f3d0; background: var(--primary-bg); }
- .choose-opt.selected {
- border-color: var(--primary);
- box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.25);
- background: var(--primary-bg);
- }
- .choose-opt .letter {
- display: inline-flex;
- width: 22px;
- height: 22px;
- align-items: center;
- justify-content: center;
- border-radius: 6px;
- background: var(--primary);
- color: #fff;
- font-size: 12px;
- font-weight: 700;
- margin-right: 6px;
- }
- .choose-opt strong { font-size: 13px; }
- .choose-opt p {
- margin: 8px 0 0;
- font-size: 12px;
- color: var(--text-muted);
- }
- </style>
- </head>
- <body>
- <div class="page">
- <div class="hero">
- <h1>列表横向 / 纵向滚动体验 Demo</h1>
- <p>
- 对齐 web-admin Fresh Mint。用下方 Tab 切换「现状问题」与三种改法,
- 请在灰色模拟视口内<strong>纵向滚动到中部</strong>、再试<strong>横向滚动</strong>,感受差异。
- </p>
- <div class="problem-list">
- <span class="pill warn">问题 1:横滚条在表最底部,行多时够不着</span>
- <span class="pill warn">问题 2:纵滚到中部后看不到表头</span>
- <span class="pill ok">目标:横滚条常在可视区底部 · 表头始终可见</span>
- </div>
- </div>
- <div class="tabs" role="tablist">
- <button class="tab active" data-tab="now" type="button">现状(问题复现)<span class="badge">Bug</span></button>
- <button class="tab recommend" data-tab="a" type="button">方案 A · 表体内嵌滚动<span class="badge">推荐</span></button>
- <button class="tab" data-tab="b" type="button">方案 B · 表头 sticky + 悬浮横条</button>
- <button class="tab" data-tab="c" type="button">方案 C · 顶部镜像横条</button>
- </div>
- <!-- 现状 -->
- <section class="panel scheme-now" data-panel="now">
- <div class="panel-top">
- <div>
- <h2>现状:整页拉长,表跟着页面滚</h2>
- <p class="desc">与多数业务列表一致:表格无固定高度,横向滚动发生在整张表底部。</p>
- </div>
- <div class="meta">模拟视口<br /><strong>请先向下滚半屏</strong></div>
- </div>
- <p class="callout bad">
- 复现:① 纵向滚到行 20+ → 表头消失;② 想横看「操作」列 → 必须滚到最底才能碰到横滚条。
- </p>
- <div class="toolbar">
- <span class="sel">已选 <b>0</b> 项</span>
- <button class="btn main" type="button">新增</button>
- <button class="btn" type="button">批量导出</button>
- </div>
- <div class="viewport" id="vp-now">
- <div class="viewport-label"><span>内容区可视范围(向下滚)</span></div>
- <div class="table-scroll-full" style="overflow-x:auto;">
- <table class="data" id="table-now"></table>
- </div>
- <div class="pager"><span>共 60 条</span><span>1 / 3 页(示意)</span></div>
- </div>
- </section>
- <!-- 方案 A -->
- <section class="panel scheme-a hidden" data-panel="a">
- <div class="panel-top">
- <div>
- <h2>方案 A:表格视口填满高度(内嵌滚动)</h2>
- <p class="desc">
- 对齐规范:动态 <code>height</code> 使表体尽量撑满,
- <strong>分页始终可见且下方留 10px</strong>;
- 纵滚在表体内,表头 sticky,横滚条贴表体可视区底。
- </p>
- </div>
- <div class="meta">实现成本低<br /><strong>与 EP 官方行为一致</strong></div>
- </div>
- <p class="callout good">
- 一次解决两个问题:表头始终在表体顶部;横滚条始终在表体可视区底部(不必滚到第 60 行);分页下约 10px。
- </p>
- <div class="toolbar">
- <span class="sel">已选 <b>0</b> 项</span>
- <button class="btn main" type="button">新增</button>
- <button class="btn" type="button">批量导出</button>
- </div>
- <div class="viewport" style="height:auto; overflow:visible;">
- <div class="table-frame" id="vp-a">
- <table class="data" id="table-a"></table>
- </div>
- <div class="pager"><span>共 60 条 · 分页固定在表外</span><span>1 / 3 页</span></div>
- </div>
- </section>
- <!-- 方案 B -->
- <section class="panel scheme-b hidden" data-panel="b">
- <div class="panel-top">
- <div>
- <h2>方案 B:保留整页滚动 + sticky 表头 + 底部悬浮横滚条</h2>
- <p class="desc">
- 表仍可随页面变高;用 sticky 钉住表头;在滚动容器底部再放一条「镜像」横滚条,始终可见。
- </p>
- </div>
- <div class="meta">交互灵活<br /><strong>实现与同步成本更高</strong></div>
- </div>
- <p class="callout good">
- 适合「不想裁切表体高度、希望一屏看更多行」的场景;需维护真实横滚与悬浮条 scrollLeft 双向同步。
- </p>
- <div class="toolbar">
- <span class="sel">已选 <b>0</b> 项</span>
- <button class="btn main" type="button">新增</button>
- <button class="btn" type="button">批量导出</button>
- </div>
- <div class="viewport" style="height:auto; overflow:visible;">
- <div class="page-scroll" id="vp-b">
- <div class="table-x" id="b-table-x" style="overflow-x:auto;">
- <table class="data" id="table-b"></table>
- </div>
- <div class="float-hbar-wrap">
- <div class="float-hbar" id="b-float-hbar">
- <div class="float-hbar-inner" id="b-float-inner"></div>
- </div>
- </div>
- </div>
- <div class="pager"><span>共 60 条</span><span>试拖底部细条或表内横滚</span></div>
- </div>
- </section>
- <!-- 方案 C -->
- <section class="panel scheme-c hidden" data-panel="c">
- <div class="panel-top">
- <div>
- <h2>方案 C:表体内嵌滚动 + 顶部镜像横滚条</h2>
- <p class="desc">
- 在方案 A 基础上,表头上方再放一条横滚条:人眼在看标题附近时也能横移,无需移到表底。
- </p>
- </div>
- <div class="meta">A 的增强<br /><strong>双条同步,略占高度</strong></div>
- </div>
- <p class="callout good">
- 横滚可达性最好(顶/底都能拖)。若采用 A,可按「列特别多」的页面再叠加顶部条。
- </p>
- <div class="toolbar">
- <span class="sel">已选 <b>0</b> 项</span>
- <button class="btn main" type="button">新增</button>
- <button class="btn" type="button">批量导出</button>
- </div>
- <div class="viewport" style="height:auto; overflow:visible;">
- <div class="top-hbar" id="c-top-hbar">
- <div class="top-hbar-inner" id="c-top-inner"></div>
- </div>
- <div class="table-frame" id="vp-c">
- <table class="data" id="table-c"></table>
- </div>
- <div class="pager"><span>共 60 条</span><span>顶条与表体横滚已同步</span></div>
- </div>
- </section>
- <div class="hint-row">
- <div class="hint-card">
- <h3>与现有代码的关系</h3>
- <p>
- 规范 §7.6 已写 <code>max-height + overflow</code>;HTML demo 也有 sticky 表头。
- 业务页多数未给 <code>el-table</code> 设高度,bx 的 <code>.table-scroll</code> 仅 <code>overflow-x</code>,故问题仍在。
- </p>
- </div>
- <div class="hint-card">
- <h3>推荐落地路径</h3>
- <p>
- 优先方案 A:公共列表用 <code>useListTableViewportHeight</code> 填满视口(分页下 10px),分页留在表外。
- 列极多的页可再加方案 C 顶条。
- </p>
- </div>
- <div class="hint-card">
- <h3>注意点</h3>
- <p>
- 固定高度后筛选区展开会挤表体,需用「视口剩余高度」动态计算;
- 展开行 / 树表要回归测 <code>doLayout</code>。
- </p>
- </div>
- </div>
- <div class="choose">
- <h3>看完后请选择倾向方案(可点选,并在对话里告诉我)</h3>
- <div class="choose-options">
- <div class="choose-opt" data-choice="a" tabindex="0">
- <span class="letter">A</span><strong>表体内嵌滚动</strong>
- <p>成本最低,一次修两个问题;与 Element Plus / 规范最贴合。</p>
- </div>
- <div class="choose-opt" data-choice="b" tabindex="0">
- <span class="letter">B</span><strong>sticky + 悬浮横条</strong>
- <p>保留「整表随页面变高」;同步逻辑与兼容成本更高。</p>
- </div>
- <div class="choose-opt" data-choice="c" tabindex="0">
- <span class="letter">C</span><strong>A + 顶部镜像条</strong>
- <p>在 A 上增强横滚可达性;适合列特别宽的列表。</p>
- </div>
- </div>
- </div>
- </div>
- <script>
- const COLS = [
- { key: "check", label: "", cls: "th-check" },
- { key: "idx", label: "序号" },
- { key: "code", label: "单据编号", left: true },
- { key: "name", label: "名称", left: true },
- { key: "customer", label: "客户" },
- { key: "region", label: "区域路径" },
- { key: "amount", label: "金额" },
- { key: "status", label: "状态" },
- { key: "owner", label: "负责人" },
- { key: "dept", label: "所属部门" },
- { key: "created", label: "创建时间" },
- { key: "updated", label: "更新时间" },
- { key: "remark", label: "备注", left: true },
- { key: "op", label: "操作", cls: "th-op op" }
- ];
- function buildTable(tableEl, rows) {
- const thead = document.createElement("thead");
- const hr = document.createElement("tr");
- COLS.forEach((c) => {
- const th = document.createElement("th");
- th.textContent = c.label;
- if (c.cls) th.className = c.cls;
- if (c.left) th.classList.add("left");
- hr.appendChild(th);
- });
- thead.appendChild(hr);
- const tbody = document.createElement("tbody");
- for (let i = 1; i <= rows; i++) {
- const tr = document.createElement("tr");
- const cells = [
- { html: '<input type="checkbox" />', cls: "td-check" },
- { text: String(i) },
- { html: `<a class="link" href="#">SO-2026-${String(i).padStart(4, "0")}</a>`, left: true },
- { text: `示例业务名称 ${i}`, left: true },
- { text: i % 3 === 0 ? "华东化工" : i % 3 === 1 ? "山峡贸易" : "三墙供应链" },
- { text: "华东 / 上海 / 浦东" },
- { text: (1200 + i * 37).toLocaleString("zh-CN") },
- { html: i % 5 === 0 ? '<span class="tag warn">待处理</span>' : '<span class="tag">进行中</span>' },
- { text: i % 2 ? "张三" : "李四" },
- { text: "销售一部" },
- { text: "2026-08-01 09:12" },
- { text: "2026-09-01 18:00" },
- { text: i % 4 === 0 ? "需要横向滚动才能看到本列与操作列" : "—", left: true },
- { html: '<a class="link" href="#">查看</a> · 编辑', cls: "td-op op" }
- ];
- cells.forEach((cell) => {
- const td = document.createElement("td");
- if (cell.html) td.innerHTML = cell.html;
- else td.textContent = cell.text;
- if (cell.cls) td.className = cell.cls;
- if (cell.left) td.classList.add("left");
- tr.appendChild(td);
- });
- tbody.appendChild(tr);
- }
- tableEl.innerHTML = "";
- tableEl.appendChild(thead);
- tableEl.appendChild(tbody);
- }
- ["table-now", "table-a", "table-b", "table-c"].forEach((id) => {
- buildTable(document.getElementById(id), 60);
- });
- // Tabs
- const tabs = document.querySelectorAll(".tab");
- const panels = document.querySelectorAll("[data-panel]");
- tabs.forEach((tab) => {
- tab.addEventListener("click", () => {
- tabs.forEach((t) => t.classList.remove("active"));
- tab.classList.add("active");
- const key = tab.dataset.tab;
- panels.forEach((p) => {
- p.classList.toggle("hidden", p.dataset.panel !== key);
- });
- if (key === "b") syncB();
- if (key === "c") syncC();
- });
- });
- // Choice
- document.querySelectorAll(".choose-opt").forEach((el) => {
- el.addEventListener("click", () => {
- document.querySelectorAll(".choose-opt").forEach((x) => x.classList.remove("selected"));
- el.classList.add("selected");
- });
- });
- // Scheme B sync
- function syncB() {
- const x = document.getElementById("b-table-x");
- const bar = document.getElementById("b-float-hbar");
- const inner = document.getElementById("b-float-inner");
- const table = document.getElementById("table-b");
- inner.style.width = table.scrollWidth + "px";
- let lock = false;
- const bind = (from, to) => {
- from.onscroll = () => {
- if (lock) return;
- lock = true;
- to.scrollLeft = from.scrollLeft;
- lock = false;
- };
- };
- bind(x, bar);
- bind(bar, x);
- }
- // Scheme C sync
- function syncC() {
- const frame = document.getElementById("vp-c");
- const top = document.getElementById("c-top-hbar");
- const inner = document.getElementById("c-top-inner");
- const table = document.getElementById("table-c");
- inner.style.width = table.scrollWidth + "px";
- let lock = false;
- frame.onscroll = () => {
- if (lock) return;
- lock = true;
- top.scrollLeft = frame.scrollLeft;
- lock = false;
- };
- top.onscroll = () => {
- if (lock) return;
- lock = true;
- frame.scrollLeft = top.scrollLeft;
- lock = false;
- };
- }
- // init sync widths after paint
- requestAnimationFrame(() => {
- syncB();
- syncC();
- });
- window.addEventListener("resize", () => {
- syncB();
- syncC();
- });
- </script>
- </body>
- </html>
|