前端大屏适配
- 快速交付、比例固定的展示型大屏,优先用 scale;
- 正式项目推荐混合方案;纯 rem 配置成本高、收益有限,不建议作为大屏首选。
| 方案 | 一句话总结 | 适合场景 | 不适合场景 |
|---|---|---|---|
| scale | 整体等比缩放,简单粗暴 | 比例固定的展示型大屏 | 超宽屏 / 非标比例 / 有交互 |
| vw/vh | 视口单位,真正的流式适配 | 需要铺满全屏的响应式大屏 | ECharts 字体适配麻烦 |
| rem | 根字体驱动,移动端经典方案 | 内容丰富、组件化开发 | 配置繁琐,效果接近 scale |
| 混合方案 | rem 管布局 + vw 管字体 + JS 管图表 | 生产级项目 | 小 demo 用不着 |
scale 等比缩放
scale 通过 CSS transform: scale() 对整个容器做整体等比缩放,是门槛最低、最直接的适配思路。
优点
- 开发成本极低:所有尺寸按设计稿 1:1 书写 px,无需任何换算
- 还原度高:等比缩放,设计稿怎么画基本就怎么呈现
- 兼容性好:
transform的浏览器支持广泛,无明显兼容风险
缺点
- 字体发虚:当缩放比非整数(如 0.833)时,浏览器对亚像素做栅格化会导致文字边缘模糊。缓解手段是给文本容器加
will-change: transform或-webkit-font-smoothing: antialiased,但只能减轻、无法根除。 - 交互坐标错位:
scale之后 DOM 的逻辑坐标与视觉坐标不再一致。若大屏存在 tooltip、弹窗、拖拽等交互,鼠标命中位置会偏移,在 ECharts tooltip 上表现尤为明显。 - 非标比例留白:16:9 设计稿投到 32:9 拼接屏时,两侧会出现大片空白;若改用
Math.max拉伸填充,内容则会发生形变。
javascript
const designWidth = 1920; // 设计稿宽度
const designHeight = 1080; // 设计稿高度
function resetScale() {
const { innerWidth, innerHeight } = window;
const WR = innerWidth / designWidth;
const HR = innerHeight / designHeight;
// 取较小值,保证内容完整显示
const ratio = Math.min(WR, HR);
const marginLeft = (innerWidth - designWidth * ratio) / 2;
const marginTop = (innerHeight - designHeight * ratio) / 2;
const app = document.getElementById('app');
app.style.width = designWidth + 'px';
app.style.height = designHeight + 'px';
app.style.transform = `scale(${ratio})`;
app.style.transformOrigin = 'left top';
// 居中处理
app.style.marginLeft = marginLeft + 'px';
app.style.marginTop = marginTop + 'px';
}
window.addEventListener('resize', resetScale);
resetScale();vw/vh 视口单位
vw/vh 是 CSS 视口单位(1vw = 视口宽度的 1%,1vh = 视口高度的 1%),数值天然随视口流式变化。
优点
- 真流式适配:内容铺满整屏,不会产生留白
- 无缩放副作用:不存在 scale 带来的模糊与坐标偏移
- 宽高解耦:宽度、高度独立计算,异形比例屏幕也能适配
缺点
- ECharts 字号需手动换算:需要编写 JS 函数将 px 映射为 vw/vh。
- 极端比例下内容挤压:如 1080×1920 竖屏,按 vw 算出的宽度极小,布局会被严重压缩,需加最小宽度兜底。
- 编写成本偏高:所有尺寸都需经过转换函数,不如直接写 px 顺手;可借助
postcss-px-to-viewport等 PostCSS 插件自动转换来抵消这部分成本。
javascript
export function fitChartSize(px, base = 1920) {
const clientWidth = document.documentElement.clientWidth;
return Number(((px * clientWidth) / base).toFixed(3));
}
// 使用
const option = {
title: {
textStyle: {
fontSize: fitChartSize(18)
}
},
grid: {
left: fitChartSize(60),
right: fitChartSize(20)
}
};
echarts.setOption(option);此外,窗口 resize 后,ECharts 需重新 setOption 才能使字号生效,仅调用 chart.resize() 不足以更新文字尺寸。
SCSS 中封装 vw/vh 函数(注意此处为 SCSS 语法,并非 LESS):
scss
@use "sass:math";
$designWidth: 1920; // 设计稿宽度
$designHeight: 1080; // 设计稿高度
@function vw($px) {
@return math.div($px, $designWidth) * 100vw;
}
@function vh($px) {
@return math.div($px, $designHeight) * 100vh;
}css
/* 使用 SCSS 函数 vw / vh */
.container {
width: vw(460); /* 460 / 设计稿宽度 * 100vw */
height: vh(320); /* 320 / 设计稿高度 * 100vh */
padding: vh(20) vw(24);
font-size: vw(14); /* 字体也用 vw */
border-radius: vw(8);
}rem 根字体驱动
rem 的原理是动态改写 <html> 的 font-size,从而驱动整页以根字号为基准进行缩放。
javascript
// flexible.js
const BASE_WIDTH = 1920;
const BASE_HEIGHT = 1080;
const BASE_RATIO = BASE_WIDTH / BASE_HEIGHT;
const BASE_FONT_SIZE = 16;
function updateRootFontSize() {
const { clientWidth, clientHeight } = document.documentElement;
const widthIsLarger = clientWidth > clientHeight;
// 宽高比判断,取较小缩放比
const ratio = widthIsLarger && BASE_RATIO
? clientHeight / BASE_HEIGHT
: clientWidth / BASE_WIDTH;
document.documentElement.style.fontSize = `${ratio * BASE_FONT_SIZE}px`;
}
updateRootFontSize();
window.addEventListener('resize', updateRootFontSize);配合 postcss-pxtorem 自动将 px 转为 rem:
js
// postcss.config.js
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 16,
propList: ['*'],
minPixelValue: 2
}
}
};混合方案
混合方案按职责分层:布局交给 vw/vh 铺满,组件内部用 rem / px 保持独立,第三方图表用 JS 动态算 px,极端比例再借助 clamp() 与 min-width 兜底。
架构设计图:
text
┌─────────────────────────────────────────┐
│ 浏览器视口 (100vw × 100vh) │
│ │
│┌──────────┐┌───────────────────────────┐│
││ 左侧栏 ││ 主内容区 ││
││ w:20vw ││ w:80vw ││
││ h:100vh ││ h:100vh ││
││ ││ ││
││ 内部组件 ││ ┌────────────────┐ ││
││ 用rem ││ │ ECharts图表 │ ││
││ ││ │ JS计算px │ ││
││ ││ └────────────────┘ ││
│└──────────┘└───────────────────────────┘│
└─────────────────────────────────────────┘布局层用 vw/vh
css
.layout-left {
width: 20vw;
height: 100vh;
}
.layout-main {
width: 80vw;
height: 100vh;
}组件内用 CSS clamp() 做弹性字体
clamp() 让字号在设定区间内自适应:既不会在超大屏上膨胀为巨字,也不会在小屏上小到无法辨识。
css
.card-title {
/* 最小 12px,理想 1vw,最大 24px */
font-size: clamp(12px, 1vw, 24px);
}
.card-value {
font-size: clamp(24px, 2.5vw, 56px);
font-weight: bold;
}ECharts 封装自适应 hook(Vue 3)
要点:resize 后不仅要调 chart.resize(),还必须重新 setOption,否则字号不会随视口更新。下面把 option 构造抽成 getOption,在挂载与 resize 时统一重渲染。
javascript
// useChartResize.ts
import { onMounted, onUnmounted, ref } from 'vue';
import * as echarts from 'echarts';
import type { Ref } from 'vue';
export function useChartResize(
chartRef: Ref<HTMLElement | null>,
getOption: () => echarts.EChartsCoreOption
) {
let chart: echarts.ECharts | null = null;
const fitSize = (px: number, base = 1920) => {
const width = document.documentElement.clientWidth;
return Math.round((px * width) / base);
};
const render = () => {
if (chart) chart.setOption(getOption());
};
const handleResize = () => {
if (!chart) return;
chart.resize();
render(); // 重新设置包含字体大小的 option,否则缩放变化后字体不变
};
onMounted(() => {
if (!chartRef.value) return;
chart = echarts.init(chartRef.value);
render();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
chart?.dispose();
chart = null;
});
return { chart, fitSize };
}使用方式(getOption 内部用 fitSize 计算字号,resize 时自动重算):
javascript
import { ref } from 'vue';
import { useChartResize } from './useChartResize';
const chartRef = ref<HTMLElement | null>(null);
const { fitSize } = useChartResize(chartRef, () => ({
title: {
textStyle: { fontSize: fitSize(18) }
},
grid: {
left: fitSize(60),
right: fitSize(20)
}
}));极端比例兜底
css
#app {
min-width: 1024px;
min-height: 600px;
overflow: auto; /* 实在太小就出滚动条 */
}选型建议
- 临时大屏 / 比例固定(如固定 16:9 汇报屏):直接采用
scale,交付速度最快。 - 响应式大屏 / 需铺满异形屏:使用
vw/vh或混合方案。 - 生产级数据可视化项目:推荐混合方案——以
vw/vh管布局、clamp()管字体弹性、JS 管 ECharts,并以min-width兜底极端比例,在还原度与可维护性之间取得最佳平衡。