数字化大屏适配方案选型:固定画布、响应式与混合布局
谈“大屏适配”时,常会把 scale、rem、vw/vh、Flex 和 Grid 放在同一张候选清单里。其实它们不在同一层:
- 页面策略:固定画布、完全响应式,或二者结合的混合模式。
- 布局工具:Grid、Flex、绝对定位、媒体查询、容器查询。
- 长度单位:
px、%、rem、vw/vh、cqi等。 - 图表适配:容器尺寸变化后的重绘与信息密度调整。
因此,问题不应是“选择 scale 还是 Grid”,而应是“页面采用哪种空间策略,内部用什么布局工具”。Grid 和 Flex 可以出现在所有三种页面策略中。 本文只负责选型和展示各方案的结构;选定固定画布或固定舞台混合方案后,再阅读实现篇:从画布缩放到图表响应。可运行演示在 example/digital-screen-adaptation。
1. 先定义“适配成功”
没有目标设备清单,就无法选方案。至少记录:
| 问题 | 示例答案 | 会影响什么 |
|---|---|---|
| 主要展示设备 | 展厅 1920×1080、3840×2160 | 是否允许以 16:9 画布为基准 |
| 宽高比范围 | 16:9 为主,偶尔 21:9 | 要留白、扩展内容,还是重排 |
| 最小可用窗口 | 1280×720 | 整体缩小后的文字是否可读 |
| 数据是否变化 | 卡片固定,列表行数会增加 | 能否保持绝对固定布局 |
| 观看与操作距离 | 远距离观看,少量点击 | 字号、点击区域、滚动是否合适 |
| 是否必须一屏展示 | 关键信息必须,明细可滚动 | 是否能用完全响应式纵向延展 |
这里最重要的是可读性。假设设计稿标题为 24 CSS px,画布在 1280×720 下缩放到 2/3,视觉上约为 16 CSS px。若后排观众要求更大的字,仅仅让画面完整落进窗口仍不足够。可以提高设计稿字号、减少内容,或为该尺寸设计新布局。
2. 用设备清单做第一次选型
先看需求中有没有不能通过整屏缩放满足的条件,再决定页面策略:
| 从清单中读到的条件 | 优先选择 | 判断依据 |
|---|---|---|
| 只有少数固定比例横屏;内容和模块位置稳定;最小屏缩放后仍可读 | 固定画布 | 同一张设计稿可以等比呈现,且无需为变化的数据腾出新空间 |
| 存在竖屏、可调整窗口,或列表与卡片数量持续变化;允许滚动和重排 | 完全响应式 | 必须按真实可用空间重新分配区域,而非只改变整张画布的大小 |
| 页面始终是 16:9,但不同尺寸需要改变舞台内的面板宽度或隐藏次要内容 | 混合布局:固定舞台 + 内部弹性布局 | 整张 1920×1080 舞台仍等比缩放;只调整舞台内部,21:9 屏幕两侧仍可能留白 |
| 整页要铺满 16:9、21:9 等屏幕;中央地图或三维场景保持 16:9,两侧面板利用剩余空间 | 混合布局:响应式外壳 + 固定比例核心 | 整页跟随真实视口变化;只有中央核心区受固定比例约束 |
表中只有两种混合布局。后者同时覆盖“超宽屏两侧要放内容”和“侧栏、工具区要伸缩”这两类需求,无需为它们选两个不同方案。例如在 2520×1080 的屏幕上,固定舞台仍是居中的 1920×1080,左右各留约 300px;响应式外壳则占满 2520px,中间可放 1920×1080 的主视觉,左右各用约 300px 放面板。前一种在舞台内部改变列宽,并不会把内容延伸到舞台外。
可以按下面的顺序判断,而不是先决定用 scale、rem 或 Grid:
需要支持竖屏、自由调整窗口,或大量变化的数据吗?
├─ 是 → 允许整体重排、滚动?
│ ├─ 是 → 完全响应式;若有地图等强视觉核心,给核心区域保留固定比例
│ └─ 否 → 需求冲突:先确定哪些信息必须一屏展示,其余内容如何分页或收起
└─ 否 → 设备是否主要为同一比例的固定横屏?
├─ 是 → 最小屏上等比缩放后,文字和操作目标是否仍可用?
│ ├─ 是 → 固定画布
│ └─ 否 → 混合布局:增加紧凑档、减少信息,必要时另做小屏布局
└─ 否 → 可以留白则用固定画布;必须利用额外空间则用响应式外壳 + 固定比例核心
例如,展厅只用 1920×1080 和 3840×2160,内容固定,而且在最小屏上验收可读,就从固定画布开始;同一展厅增加 21:9 屏幕,如果允许两侧留白,仍可沿用固定画布;如果必须利用两侧空间,则从响应式外壳 + 固定比例核心开始。若还要求在可拖动的运营窗口中查看不断增长的列表,则从完全响应式开始,并单独规定哪些关键信息始终可见。
这是初选,不是最终结论。拿设备清单中的最小窗口、最宽比例、最多数据分别验收;任何一项出现文字过小、内容被裁切或操作困难,就调整信息密度、布局档位或页面策略。下面再看三种策略各自怎样实现。
3. 三种页面策略
A. 固定画布 + 整体等比缩放
按 1920×1080 设计画布排版,再用 scale = min(视口宽/1920, 视口高/1080) 居中缩放。下面的 .content 是画布内部的主体内容容器,它的三个直接子元素分别是左侧栏、中央主区域和右侧栏:
<div class="viewport"> <!-- 真实窗口中的可用区域 -->
<div class="stage"> <!-- 整张 1920×1080 设计画布 -->
<div class="content"> <!-- 画布内的三列主体 -->
<aside class="left-panel">左侧指标</aside>
<main class="main-panel">中央地图或图表</main>
<aside class="right-panel">右侧指标</aside>
</div>
</div>
</div>
.viewport 负责居中,.stage 负责整张画布的固定尺寸与缩放,.content 只负责排列画布内的业务区域。示例中 .content 占满 .stage 的 1920px 宽度,两侧各 420px、两个间隙各 24px,中央列得到 1032px。对应 CSS 如下:
/* .viewport:真实窗口中的舞台容器,负责把画布居中。 */
.viewport {
display: flex; /* 用 Flex 布置设计画布。 */
align-items: center; /* 画布纵向居中。 */
justify-content: center; /* 画布横向居中。 */
width: 100%; /* 占满真实窗口的可用宽度。 */
height: 100dvh; /* 占满当前动态视口高度。 */
overflow: hidden; /* 避免画布在缩放前的布局尺寸产生滚动条。 */
}
/* .stage:承载整张设计稿的固定画布。 */
.stage {
width: 1920px; /* 画布使用设计稿的基准宽度。 */
height: 1080px; /* 画布使用设计稿的基准高度。 */
flex: none; /* 防止 Flex 在计算缩放前压缩画布。 */
transform: scale(var(--stage-scale, 1)); /* 按运行时写入的统一倍率缩放整张画布。 */
transform-origin: center center; /* 从画布中心缩放,配合外层居中。 */
}
/* .content:画布内左、中、右三个业务区域的布局容器。 */
.content {
display: grid; /* 让三个业务区域按二维网格排布。 */
grid-template-columns: 420px minmax(0, 1fr) 420px; /* 左右侧栏各占 420px;中间区域分配剩余宽度,且允许缩到 0。 */
gap: 24px; /* 三个区域之间各留 24px。 */
}
上面的 --stage-scale 取 min(视口宽/1920, 视口高/1080)。如何在 Vue 中测量真实容器、更新倍率并清理观察器,由实现篇统一说明。下文的固定舞台混合布局使用同一个缩放层,只改变画布内部的 Grid 规则。
这些 Grid 列宽仍是固定画布坐标,不是完全响应式。换成 Flex 也一样:display: flex 只描述元素如何分配画布内部的空间,并不自动决定页面是否随着真实视口重排。
适合:固定内容的展厅屏、指挥中心主屏、严格按一张设计稿验收的 16:9 横屏。代价:比例不同会留白;缩小时文字和交互目标一起缩小;宽高比差异较大时空间利用率低。
B. 完全响应式布局
页面直接占据真实视口,不设固定设计画布,也不做整屏 scale。用 Grid 设计主区域,用 Flex 排布区域内部的一维内容;可用空间不足时用断点重排。先看与下方 CSS 对应的 HTML:
<div class="dashboard">
<aside class="panel left-panel">
<div class="panel-body">
<h2>左侧指标</h2>
<div>指标卡片、列表等内容</div>
</div>
</aside>
<main class="panel main-chart">
<div class="panel-body">
<h2>中央主图</h2>
<div class="chart">图表挂载节点</div>
</div>
</main>
<aside class="panel right-panel">
<div class="panel-body">
<h2>右侧指标</h2>
<div>指标卡片、列表等内容</div>
</div>
</aside>
</div>
.dashboard 是直接随视口变化的三列网格;三个 .panel 是它的直接子元素。.panel-body 负责面板内纵向排列,中央面板中的 .chart 占用剩余空间;.left-panel、.main-chart、.right-panel 则用于在断点处明确指定三个区域的位置。
/* .dashboard:完全响应式页面的三列主容器。 */
.dashboard {
display: grid; /* 让左右面板与中间主区组成网格。 */
grid-template-columns: minmax(240px, 1fr) minmax(480px, 2.2fr) minmax(240px, 1fr); /* 左右列至少 240px、各分 1 份;中列至少 480px、分 2.2 份。 */
grid-template-areas: "left main right"; /* 宽屏时三块内容排在同一行。 */
gap: clamp(12px, 1.2vw, 24px); /* 列间距随视口变化,但限制在 12–24px。 */
min-height: 100dvh; /* 页面至少占满当前动态视口高度。 */
}
/* .panel:每个网格面板;允许它随可用空间缩小。 */
.panel {
min-width: 0; /* 避免长内容把所在网格列撑宽。 */
min-height: 0; /* 避免内部内容把面板撑高。 */
}
.left-panel { grid-area: left; } /* 左侧指标所在区域。 */
.main-chart { grid-area: main; } /* 中央主图所在区域。 */
.right-panel { grid-area: right; } /* 右侧指标所在区域。 */
/* .panel-body:面板内标题、图表等内容的纵向容器。 */
.panel-body {
display: flex; /* 使用 Flex 分配面板内部空间。 */
flex-direction: column; /* 标题与图表从上到下排列。 */
min-height: 0; /* 允许图表所在区域收缩。 */
}
/* .chart:面板内的图表节点。 */
.chart {
flex: 1; /* 占用标题之外的剩余高度。 */
min-height: 240px; /* 保留可读的图表高度下限。 */
}
/* 视口不超过 1100px 时,左右指标并排,主图独占下一行。 */
@media (max-width: 1100px) {
.dashboard {
grid-template-columns: repeat(2, minmax(0, 1fr)); /* 左右两列等宽,且都允许收缩到 0。 */
grid-template-areas:
"left right"
"main main"; /* 两侧指标仍显示;主图横跨下一行的两列。 */
}
}
/* 视口不超过 680px 时,所有区域改为一列。 */
@media (max-width: 680px) {
.dashboard {
grid-template-columns: minmax(0, 1fr); /* 只留一列,宽度占满容器可用空间。 */
grid-template-areas:
"main"
"left"
"right"; /* 窄屏先看主图,再向下滚动查看两侧指标。 */
}
}
适合:多种比例与窗口尺寸、数据量变化较大、需要滚动与操作的运营平台;明确要支持竖屏或平板时也更自然。代价:需要分别设计模块顺序、断点、图表内容密度和滚动规则;无法保证每个设备上都像同一张设计稿。
C. 混合布局
混合有两种常见形式,应明确说出是哪一种:
- 固定舞台 + 内部弹性布局:舞台仍等比缩放,面板用 Grid/Flex。根据真实视口宽高比或缩放系数切换内部列宽、隐藏次要说明、调整图表图例。这是配套示例中的“混合布局”。
- 响应式外壳 + 固定比例核心:页面、侧栏、工具区按真实视口重排;中央地图、三维场景或指挥舱主视觉保持固定比例或
aspect-ratio。适合既有业务操作区又有强视觉核心的系统。
第一种的关键是“只缩放一次”:内层 Grid 改的是布局,不能再让相同内容使用动态根 rem、窗口 vw 和二次 scale 一起计算倍率。第二种要特别注意中央固定比例区域的最大尺寸与溢出处理。
固定舞台 + 内部弹性布局
固定舞台内部按档位改列宽的例子:
<div class="viewport">
<div class="stage">
<div class="content">
<aside>左侧指标</aside>
<main>
<h2>中央主图</h2>
<p class="secondary-note">空间不足时可省略的辅助说明</p>
</main>
<aside>右侧指标</aside>
</div>
</div>
</div>
.stage--compact 是加在 .stage 同一个元素上的档位类;它只影响内部列宽和辅助说明是否显示。下面同时给出外层居中、舞台缩放和内部布局的 CSS:
/* .viewport:占满真实窗口,并将设计画布居中。 */
.viewport {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100dvh;
overflow: hidden;
}
/* .stage:混合模式仍以同一倍率等比缩放固定画布。 */
.stage {
width: 1920px; /* 保留设计稿宽度。 */
height: 1080px; /* 保留设计稿高度。 */
flex: none; /* 防止 Flex 在计算缩放前压缩画布。 */
transform: scale(var(--stage-scale, 1)); /* 对整张舞台缩放一次。 */
transform-origin: center center; /* 从中心缩放,让画布保持居中。 */
}
/* .content:固定舞台内的默认三列业务区。 */
.content {
display: grid; /* 用 Grid 排列左侧栏、主区、右侧栏。 */
grid-template-columns: 420px minmax(0, 1fr) 420px; /* 两侧各 420px,中间分配剩余宽度。 */
gap: 24px; /* 三个区域之间各留 24px。 */
}
/* .stage--compact .content:紧凑档下同一个业务区。 */
.stage--compact .content {
grid-template-columns: 340px minmax(0, 1fr) 340px; /* 两侧改为各 340px,把更多画布宽度留给主区。 */
}
/* .secondary-note:紧凑档可省略的辅助说明文字。 */
.stage--compact .secondary-note {
display: none; /* 隐藏次要说明,减少拥挤。 */
}
缩放倍率和紧凑档都可由外层容器的真实尺寸决定。例如倍率低于经过验收确定的阈值时,给舞台增加 .stage--compact;这个类只切换内部布局,不再缩放一次。倍率测量与 Vue 生命周期代码放在实现篇中。
响应式外壳 + 固定比例核心
这一种没有固定的整页舞台。页面网格占据真实视口;中央主视觉保持 16:9,左右面板使用剩余宽度。下面的 HTML 中,三个区域都是 .shell 的直接子元素:
<div class="shell">
<aside class="shell-left">左侧指标与操作区</aside>
<main class="shell-core">16:9 地图或三维场景</main>
<aside class="shell-right">右侧指标与操作区</aside>
</div>
.shell {
display: grid;
grid-template-columns: minmax(240px, 1fr) minmax(0, 1920px) minmax(240px, 1fr);
grid-template-areas: "left core right";
align-items: center;
min-height: 100dvh;
}
.shell-left { grid-area: left; min-width: 0; }
.shell-right { grid-area: right; min-width: 0; }
.shell-core {
grid-area: core;
justify-self: center;
width: min(100%, 1920px, 177.778dvh); /* 宽度不超过容器宽度、设计上限和视口高度允许的 16:9 宽度。 */
aspect-ratio: 16 / 9; /* 保持 16:9,并根据宽度自动计算高度。 */
min-width: 0;
}
@media (max-width: 1280px) {
.shell {
grid-template-columns: minmax(0, 1fr);
grid-template-areas:
"core"
"left"
"right"; /* 窄屏改为纵向排列,三块内容都保留。 */
}
}
在 2520×1080 的视口上,中间区域最多为 1920×1080,两侧各获得约 300px;视口变窄时,两侧可以缩小,到了断点再排到主视觉下方。这里改变的是元素本身的布局尺寸,没有对整页做 scale()。若中央使用 ECharts 或 Canvas,容器尺寸改变后仍要调用对应的 resize()。
4. px、rem、vw/vh、容器单位怎么选
| 单位或函数 | 参照对象 | 推荐使用位置 | 注意点 |
|---|---|---|---|
px | CSS 像素 | 固定画布内部的设计尺寸、边框 | 外层统一缩放后视觉尺寸会变化 |
%、fr、Flex 比例 | 父级可用空间 | 各方案中的面板分配 | 父级需有可计算的尺寸 |
rem | 根元素字号 | 全局文字与间距令牌 | 根字号动态缩放时,避免再与整屏 scale 叠加 |
vw/vh、dvh | 视口 | 响应式页面、画布外背景 | 固定画布内部使用时,仍参照外部窗口 |
cqi/cqw、@container | 查询容器 | 可复用卡片组件的局部排版 | 要先声明查询容器,不能查询元素自身尺寸 |
clamp() | 组合最小、首选、最大尺寸 | 响应式字体、间距、列宽 | 只是限制范围,不会自动解决内容溢出 |
rem 和 vw/vh 并非手机专属,也不是做大屏的必备步骤。固定画布里通常先用 px + Grid/Flex;完全响应式页面更常用 fr/% + clamp() + 断点。dvh 对浏览器动态工具栏较有意义,固定全屏 Electron 中通常没有这种工具栏,但仍应按真实内容区测量。关于动态视口单位可参照 MDN 的长度单位说明。
当同一个卡片会被放进宽度不同的面板时,可用容器查询处理卡片内部布局:
<div class="card-slot">
<div class="card">
<span>今日访问量</span>
<strong>12,480</strong>
<span class="card-description">较昨日增长 8%</span>
</div>
</div>
.card-slot 是查询容器,.card 是它的子元素;下面的 @container 根据 .card-slot 的宽度调整卡片,而不是根据整页视口宽度调整。
/* .card-slot:指标卡片的外层容器,作为查询尺寸的参照。 */
.card-slot {
container: metric-card / inline-size; /* 把该容器命名为 metric-card,并允许按其横向尺寸查询。 */
}
/* .card:指标名称、数值与描述的默认排布。 */
.card {
display: flex; /* 卡片内容按一行排列。 */
align-items: baseline; /* 让不同字号的文字沿基线对齐。 */
gap: 12px; /* 卡片内容之间留 12px。 */
}
/* 查询 card-slot 的宽度,而不是整个视口的宽度。 */
@container metric-card (width < 300px) {
/* .card:容器较窄时的卡片内容。 */
.card {
flex-direction: column; /* 名称和数值改为上下排列。 */
gap: 4px; /* 纵向排列时缩小间距。 */
}
/* .card-description:容器较窄时优先省略的说明文字。 */
.card-description {
display: none; /* 隐藏辅助描述,保留主要指标。 */
}
}
媒体查询关注视口,容器查询关注组件所在容器;后者尤其适合复用组件,但不能替代页面级的内容顺序决策。MDN 的容器查询指南说明了这一区别。
5. 选型完成后仍要处理图表
页面策略只决定图表容器如何获得空间。ECharts 或 Canvas 不会因为外层选用了 Grid、Flex 或 scale() 就自动完成内部适配:容器的布局宽高变化后仍要调用 resize(),空间不足时还要调整坐标轴标签、图例和数据密度。
这些属于实现问题,不再在选型篇重复展开。Vue 中的初始化、ResizeObserver、合帧、卸载和图表内部适配见实现篇的 ECharts 章节。
6. 在示例里比较三种策略
运行:
cd example/digital-screen-adaptation
npm install
npm run dev
在页面底部选择“固定画布 / 混合布局 / 完全响应式”,然后改变窗口宽高。前两种保留 1920×1080 的舞台;混合模式会在紧凑档改变三列 Grid 比例并减少次要文字;完全响应式模式取消舞台缩放,在 1100px 与 680px 附近把三列重排为两列、一列,必要时页面滚动。再切换“普通 / 紧凑”信息密度,可以验证图表在窗口不变、容器变化时仍会重新布局。
不要把示例断点当作通用标准。真实项目的阈值应由标题长度、卡片最小宽度、图表标签宽度以及实际观看距离决定。对极小窗口,明确内容优先级比无限压缩更重要。
评论