Skip to content

🅁 总阅读量: -

🅅 总访问量: -

◷ 更新于: 2026-08-12

🅆 字数: 0

🅁 阅读: -

前端CSS大屏适配EChartsscalevw/vhrem

前端大屏适配

  • 快速交付、比例固定的展示型大屏,优先用 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 兜底极端比例,在还原度与可维护性之间取得最佳平衡。

基于 CC BY-NC-SA 4.0 许可发布