3. 回放页面骨架搭建
盘口回放页面的骨架,决定了后续所有行情渲染、逐笔回放、播放控制逻辑能否被清晰、稳定地挂载。本节不追求视觉美化,而是先把“页面由哪些语义区块组成、每个区块承担什么职责、它们之间如何用 CSS 布局划分空间”这三件事定下来。骨架搭得对,后面写 JavaScript 时只需按 id 或 data 属性找到容器,把数据往里填即可;骨架搭得乱,回放逻辑就会和 DOM 结构纠缠在一起,越写越难维护。
一、先定语义结构:四个职责明确的区域
一个盘口回放页面在功能上可以拆成四块:行情区负责展示标的的价格走势或分时图;盘口区展示买卖五档(或十档)报价与挂单量;成交区展示逐笔成交明细;控制区承载播放、暂停、步进、倍速、时间轴拖动等操作。这四块在语义上并不是平级的“div 堆叠”,而应使用 HTML5 语义标签表达其角色:
- 整个回放器用一个
<section>或<main>包裹,标注aria-label="盘口回放器"; - 行情区、盘口区、成交区各自用
<section>,并配<h3>标题,便于屏幕阅读器跳转; - 控制区用
<form>或带role="toolbar"的容器,因为播放/暂停/倍速本质是一组操作控件; - 逐笔成交这类“多行同构数据”用
<table>或<ul>,而不是一堆无意义的<div>。
语义化的直接收益是:后续用 document.querySelector('[data-region="orderbook"]') 这类选择器定位容器时,结构含义一目了然;同时键盘用户可以通过标题快速在区域间跳转。下面这张结构关系图展示了四个区域在 DOM 中的层级与并列关系,以及每个区域内部应挂载的关键子容器。
二、用 Grid 划分主布局,用 Flex 处理区内排列
四个区域的空间关系适合用 CSS Grid 表达,因为它是二维的:行情区通常占据左侧较大面积,盘口区与成交区在右侧上下或左右排列,控制区与时间轴横跨底部。一个可直接使用的布局方案如下(示例参数,可按实际屏幕调整):
.replay-root {
display: grid;
grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
grid-template-rows: minmax(0, 1fr) auto auto;
grid-template-areas:
"chart orderbook"
"chart trades"
"control control"
"timeline timeline";
gap: 10px;
height: 100vh;
padding: 10px;
box-sizing: border-box;
}
[data-region="chart"] { grid-area: chart; }
[data-region="orderbook"] { grid-area: orderbook; }
[data-region="trades"] { grid-area: trades; }
[data-region="control"] { grid-area: control; }
#timeline { grid-area: timeline; }
这里的关键点有三个。第一,minmax(0, 2fr) 中的 0 不能省:如果写成 2fr,当行情区内部有长文本或 canvas 时,网格项可能被内容撑破,导致右侧盘口区被挤压。第二,grid-template-areas 让“哪个区域在哪”变成可读的字符画,调整布局时只改这几行,不用动 DOM。第三,height: 100vh 配合 minmax(0, 1fr) 行,使行情区能随窗口伸缩,而控制区和时间轴保持内容高度。
区域内部则更适合 Flex。例如盘口区需要“卖五档在上、买五档在下、中间显示价差”,用 display: flex; flex-direction: column; 加 justify-content: space-between 即可;成交区是一个可滚动的列表,用 flex: 1; overflow-y: auto; 让列表占满剩余高度并独立滚动。Grid 管“大块怎么分”,Flex 管“块内怎么排”,两者分工明确,不要用 Grid 去硬排每一行报价,也不要拿 Flex 去模拟二维网格。
三、UI 框架还是原生组件:按回放页面的真实需求选
回放页面的控件其实很有限:几个按钮、一个下拉框、一个滑块、若干表格行。这类需求下,引入完整 UI 框架(如带主题、表单校验、弹窗体系的重型库)往往得不偿失——它会带来额外体积和样式覆盖成本,而回放页面的核心复杂度在数据渲染与时钟同步,不在表单。更务实的策略是原生组件优先,按需引入轻量库:
- 按钮、下拉、滑块直接用原生
<button>、<select>、<input type="range">,配合少量自定义 CSS 统一外观; - 逐笔成交列表若行数可达数千,用原生
<table>加固定表头即可,必要时再引入虚拟滚动; - 只有当需要复杂交互组件(如可拖拽缩放的 K 线图、日期范围选择器)时,才引入对应的单一功能库,而不是整套框架。
判断标准可以落到一个具体问题上:这个库是否解决了回放页面的主要矛盾?如果主要矛盾是“每秒要重绘几十行盘口和成交数据”,那么性能优化(如 requestAnimationFrame 批量更新、DOM 复用)比 UI 框架重要得多。骨架阶段就把这个判断做掉,可以避免后期为了换框架而重构布局。
data-region 属性,并约定“JavaScript 只通过 data-region 和内部固定 id 访问 DOM”。这样即使以后把某个区域从 <section> 换成自定义组件,选择器也不用改,回放逻辑与结构解耦。四、骨架搭完后的自检
布局写完后,不要急着写回放逻辑,先用静态占位内容验证骨架是否成立:给行情区放一个固定尺寸的 <canvas>,给盘口区填五行假报价,给成交区填二十行假成交,然后做三件事——把浏览器窗口从 1920px 缩到 1024px,观察盘口区是否被挤压变形;把成交区内容增加到 500 行,观察是否只在成交区内部滚动而不撑破整页;用 Tab 键从控制区按钮依次切换,观察焦点顺序是否与视觉顺序一致。这三项通过,说明 Grid 区域划分、Flex 内部滚动和语义焦点顺序都已就位,后续把假数据换成真实回放数据时,页面不会因为结构问题返工。
至此,回放页面的“容器”已经准备好:四个语义区域各司其职,Grid 负责二维分区,Flex 负责区内排列,控件策略确定为原生优先。下一步要做的,就是让这些容器真正“动起来”——建立统一回放时钟,并让各区域按同一时间戳读取数据、重绘界面。