如何做一款定制化的数据大屏?
开发可视化数据大屏如何做自适应?
vw vh、rem、scale 到底哪种比较好?
目前比较常见的方案主要有三种:

vw/vh;- CSS
scale; rem + vw/vh。
这三种方案没有绝对的好坏,最终还是要根据项目需求来选择。
- 如果希望实现简单,客户也能够接受屏幕比例不一致时出现留白,可以直接选择
scale; - 如果需要兼容不同比例的大屏,并希望页面尽量铺满屏幕,可以选择
vw/vh; rem可以理解为scale与vw/vh之间的一种折中方案,但在数据大屏中并不一定比前两种更合适。
下面分别介绍三种方案的实现方式。
方案一:使用 vw/vh 实现大屏自适应(推荐)
1. 实现思路
按照设计稿的尺寸,将px按比例计算转为vw和vh,转换公式如下
json
假设设计稿尺寸为 1920*1080(做之前一定问清楚 ui 设计稿的尺寸)
即:
网页宽度=1920px
网页高度=1080px
我们都知道
网页宽度=100vw
网页宽度=100vh
所以,在 1920px*1080px 的屏幕分辨率下
1920px = 100vw
1080px = 100vh
这样一来,以一个宽 300px 和 200px 的 div 来说,其所占的宽高,以 vw 和 vh 为单位,计算方式如下:
vwDiv = (300px / 1920px ) * 100vw
vhDiv = (200px / 1080px ) * 100vh
所以,就在 1920*1080 的屏幕分辨率下,计算出了单个 div 的宽高
当屏幕放大或者缩小时,div 还是以 vw 和 vh 作为宽高的,就会自动适应不同分辨率的屏幕
2. Sass 方案
新建一个 utils.scss 文件:
scss
// utils.scss
@use 'sass:math';
// UI 设计稿宽度
$design-width: 1920;
// UI 设计稿高度
$design-height: 1080;
// 将设计稿中的横向像素转换成 vw
@function vw($px) {
@return math.div($px, $design-width) * 100vw;
}
// 将设计稿中的纵向像素转换成 vh
@function vh($px) {
@return math.div($px, $design-height) * 100vh;
}
在 Vue 组件中使用:
vue
<template>
<div class="box">数据大屏</div>
</template>
<style lang="scss" scoped>
@use '@/styles/utils.scss' as *;
.box {
width: vw(300);
height: vh(100);
margin-left: vw(10);
margin-top: vh(10);
font-size: vh(16);
border: vh(2) solid red;
background-color: #000;
}
</style>
这样开发时仍然可以直接使用设计稿标注的像素值,不需要关心每一个值最后应该等于多少 vw 或 vh。
3. Less 方案
如果项目使用 Less,可以新建一个 utils.less 文件:
less
@charset "utf-8";
// UI 设计稿宽度
@designWidth: 1920;
// UI 设计稿高度
@designHeight: 1080;
// 横向尺寸转换
.px2vw(@name, @px) {
@{name}: (@px / @designWidth) * 100vw;
}
// 纵向尺寸转换
.px2vh(@name, @px) {
@{name}: (@px / @designHeight) * 100vh;
}
// 字体尺寸转换
.px2font(@px) {
font-size: (@px / @designWidth) * 100vw;
}
在 Vue 组件中使用:
vue
<template>
<div class="box">数据大屏</div>
</template>
<style lang="less" scoped>
@import '@/styles/utils.less';
.box {
.px2vw(width, 300);
.px2vh(height, 100);
.px2vw(margin-left, 10);
.px2vh(margin-top, 10);
.px2font(16);
background-color: #000;
}
</style>
4. JavaScript 转换函数
有些样式需要通过 JavaScript 动态设置,这时也可以封装三个简单的工具函数:
js
// screenUtils.js
const designWidth = 1920;
const designHeight = 1080;
// px 转 vw
export const px2vw = (px) => {
return `${(px * 100) / designWidth}vw`;
};
// px 转 vh
export const px2vh = (px) => {
return `${(px * 100) / designHeight}vh`;
};
// 字体按照设计稿宽度进行转换
export const px2font = (px) => {
return `${(px * 100) / designWidth}vw`;
};
使用时直接传入设计稿中的像素值:
js
const style = {
width: px2vw(300),
height: px2vh(100),
fontSize: px2font(16),
};
屏幕变化后,图表自动调整
这种使用方式有个弊端,就是屏幕尺寸发生变化后,需要手动刷新一下才能完成自适应调整
为了解决这个问题,你需要在各个图表中监听页面尺寸变化,重新调整图表,在 vue 项目中,也可以借助element-resize-detector,最好封装个 resize 的指令,在各图表中就只要使用该指令就可以了,毕竟作为程序员,能偷懒就偷懒
- 安装 element-resize-detector
css
pnpm install element-resize-detector --save
- 引入工具包在组件中使用或者在单独的 js 中使用
javascript
import resizeDetector from 'element-resize-detector'
- 封装 directive
ini
// directive.js
import * as ECharts from "echarts";
import elementResizeDetectorMaker from "element-resize-detector";
import Vue from "vue";
const HANDLER = "_vue_resize_handler";
function bind(el, binding) {
el[HANDLER] = binding.value
? binding.value
: () => {
let chart = ECharts.getInstanceByDom(el);
if (!chart) {
return;
}
chart.resize();
};
// 监听绑定的div大小变化,更新 echarts 大小
elementResizeDetectorMaker().listenTo(el, el[HANDLER]);
}
function unbind(el) {
// window.removeEventListener("resize", el[HANDLER]);
elementResizeDetectorMaker().removeListener(el, el[HANDLER]);
delete el[HANDLER];
}
// 自定义指令:v-chart-resize 示例:v-chart-resize="fn"
Vue.directive("chart-resize", { bind, unbind });
- main.js 中引入
arduino
import '@/directive/directive';
- html 代码
xml
<template>
<div class="linechart">
<div ref="chart" v-chart-resize class="chart"></div>
</div>
</template>
这里要注意的是,图表中如果需要 tab 切换动态更新图表数据,在更新数据时一定不要用 echarts 的 dispose 方法先将图表移除,再重新绘制,因为 resize 指令中挂载到的图表实例还是旧的,就监听不到新的 chart 元素的 resize 了,更新数据只需要用 chart 的 setOption 方法重新设置配置项即可。
图表字体、间距、位移等尺寸自适应
echarts 的字体大小只支持具体数值(像素),不能用百分比或者 vw 等尺寸,一般字体不会去做自适应,当宽高比跟 ui 稿比例出入太大时,会出现文字跟图表重叠的情况
这里我们就需要封装一个工具函数,来处理图表中文字自适应了
- 默认情况下,这里以你的设计稿是 1920*1080 为例,即网页宽度是 1920px (做之前一定问清楚 ui 设计稿的尺寸)
- 把这个函数写在一个单独的工具文件
dataUtil.js里面,在需要的时候调用 - 其原理是计算出当前屏幕宽度和默认设计宽度的比值,将原始的尺寸乘以该值
- 另外,其它 echarts 的配置项,比如间距、定位、边距也可以用该函数
- 编写 dataUtil.js 工具函数
javascript
// Echarts图表字体、间距自适应
export const fitChartSize = (size,defalteWidth = 1920) => {
let clientWidth = window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth;
if (!clientWidth) return size;
let scale = (clientWidth / defalteWidth);
return Number((size*scale).toFixed(3));
}
- 将函数挂载到原型上
javascript
import {fitChartSize} from '@src/utils/dataUtil.js'
Vue.prototype.fitChartFont = fitChartSize;
- 这样你可以在
.vue文件中直接使用this.fitChartSize()调用
css
<template>
<div class="chartsdom" ref="chart" v-chart-resize></div>
</template>
<script>
export default {
name: "dashboardChart",
data() {
return {
option: null,
};
},
mounted() {
this.getEchart();
},
methods: {
getEchart() {
let myChart = this.$echarts.init(this.$refs.chart);
const option = {
backgroundColor: "transparent",
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c}%",
},
grid: {
left: this.fitChartSize(10),
right: this.fitChartSize(20),
top: this.fitChartSize(20),
bottom: this.fitChartSize(10),
containLabel: true,
},
calculable: true,
series: [
{
color: ["#0db1cdcc"],
name: "计划投入",
type: "funnel",
width: "45%",
height: "70%",
x: "5%",
minSize: "10%",
funnelAlign: "right",
center: ["50%", "50%"], // for pie
data: [
{
value: 30,
name: "下单30%",
},
{
value: 55,
name: "咨询55%",
},
{
value: 65,
name: "点击65%",
},
{
value: 60,
name: "访问62%",
},
{
value: 80,
name: "展现80%",
},
].sort(function (a, b) {
return a.value - b.value;
}),
roseType: true,
label: {
normal: {
formatter: function () {},
position: "inside",
},
},
itemStyle: {
normal: {
borderWidth: 0,
shadowBlur: this.fitChartSize(20),
shadowOffsetX: 0,
shadowOffsetY: this.fitChartSize(5),
shadowColor: "rgba(0, 0, 0, 0.3)",
},
},
},
{
color: ["#0C66FF"],
name: "实际投入",
type: "funnel",
width: "45%",
height: "70%",
x: "50%",
minSize: "10%",
funnelAlign: "left",
center: ["50%", "50%"], // for pie
data: [
{
value: 35,
name: "下单35%",
},
{
value: 40,
name: "咨询40%",
},
{
value: 70,
name: "访问70%",
},
{
value: 90,
name: "点击90%",
},
{
value: 95,
name: "展现95%",
},
].sort(function (a, b) {
return a.value - b.value;
}),
roseType: true,
label: {
normal: {
position: "inside",
},
},
itemStyle: {
normal: {
borderWidth: 0,
shadowBlur: this.fitChartSize(20),
shadowOffsetX: 0,
shadowOffsetY: this.fitChartSize(5),
shadowColor: "rgba(0, 0, 0, 0.3)",
},
},
},
],
};
myChart.setOption(option, true);
},
},
beforeDestroy() {},
};
</script>
<style lang="scss" scoped>
.chartsdom {
width: 100%;
height: 100%;
}
</style>
可以参考一下这个 代码 big-screen-adapter.git
5. vw/vh 方案的优缺点
优点:
- 可以根据视口宽高动态调整元素尺寸;
- 屏幕比例变化后,页面仍然可以尽量铺满整个屏幕;
- 适合需要兼容多种分辨率和宽高比的数据大屏。
缺点:
- 字体、间距、定位和边距等尺寸都需要进行换算;
- 当实际屏幕比例与设计稿差异较大时,横向和纵向的缩放比例并不一致;
- 项目中的特殊组件和动态样式可能需要单独处理。
整体来说,如果项目需要兼容不同比例的大屏,又希望内容尽量填满整个视口,vw/vh 是比较合适的方案。
方案二:scale
通过 css 的 scale 属性,根据屏幕大小,对图表进行整体的等比缩放,从而达到自适应效果
html 部分
ini
<div className="screen-wrapper">
<div className="screen" id="screen">
</div>
</div>
js 部分
xml
<script>
export default {
mounted() {
// 初始化自适应 ----在刚显示的时候就开始适配一次
handleScreenAuto();
// 绑定自适应函数 ---防止浏览器栏变化后不再适配
window.onresize = () => handleScreenAuto();
},
deleted() {
window.onresize = null;
},
methods: {
// 数据大屏自适应函数
handleScreenAuto() {
const designDraftWidth = 1920; //设计稿的宽度
const designDraftHeight = 960; //设计稿的高度
// 根据屏幕的变化适配的比例
const scale =
document.documentElement.clientWidth /
document.documentElement.clientHeight <
designDraftWidth / designDraftHeight
? document.documentElement.clientWidth / designDraftWidth
: document.documentElement.clientHeight / designDraftHeight;
// 缩放比例
document.querySelector(
'#screen',
).style.transform = `scale(${scale}) translate(-50%, -50%)`;
},
},
};
</script>
css部分
css
/*
除了设计稿的宽高是根据您自己的设计稿决定以外,其他复制粘贴就完事
*/
.screen-root {
height: 100%;
width: 100%;
.screen {
display: inline-block;
width: 1920px; //设计稿的宽度
height: 960px; //设计稿的高度
transform-origin: 0 0;
position: absolute;
left: 50%;
top: 50%;
}
}
实现思路
如何缩放
当屏幕宽高比 < 设计稿宽高比,我们需要缩放的比例是屏幕宽度 / 设计稿宽度
当屏幕宽高比 > 设计稿宽高比,我们需要缩放的比例是屏幕高度 / 设计稿高度
javascript
const scale = document.documentElement.clientWidth / document.documentElement.clientHeight < designDraftWidth / designDraftHeight ?
(document.documentElement.clientWidth / designDraftWidth) :
(document.documentElement.clientHeight / designDraftHeight);
如果我们拿到的设计稿宽高为: 1920 * 960 px ,而我们的屏幕大小是 1440 * 900 px,那么 1440/900 = 1.6,920/960 = 2
因为 1.6 < 2 (当前屏幕宽高比小于设计稿宽高比)
所以我们需要缩放的比例是:屏幕宽度除以设计稿宽度 = 1440/1920 = 0.75
如何居中
首先我们利用 transform:translate(-50%,-50%) ,将动画的基点设为左上角
transform-origin:设置动画的基点(中心点),默认点是元素的中心点
语法
transform-origin: x-axis y-axis z-axis;
然后利用transform:translate(-50%,-50%),将图表沿 x,y 轴移动 50%

接下来利用绝对定位将图表定位到中间位置
css
position: absolute;
left: 50%;
top: 50%;
简单的方法-使用插件
v-scale-screen 是使用 css 属性 transform 实现缩放效果的一个大屏自适应组件,通过 scale 进行等比例计算,达到等比例缩放的效果,同时也支持铺满全屏,宽度等比,高度等比,等自适应方案
方案三:rem + vw/vh
rem 是相对于根元素 html 的 font-size 进行计算的。
使用这种方案时,通常会先根据屏幕宽度动态计算根元素的字号,再把设计稿中的 px 转换为 rem:
js
const designWidth = 1920;
function setRootFontSize() {
const clientWidth = document.documentElement.clientWidth;
document.documentElement.style.fontSize =
`${(clientWidth / designWidth) * 100}px`;
}
setRootFontSize();
window.addEventListener('resize', setRootFontSize);
假设约定设计稿中的 100px 对应 1rem,那么:
css
.box {
width: 3rem; /* 设计稿中的 300px */
height: 1rem; /* 设计稿中的 100px */
}
在一些项目中,也会将 rem 与 vw/vh 混合使用:
- 页面主体尺寸使用
rem; - 横向、纵向定位使用
vw/vh; - 特殊尺寸再通过 JavaScript 动态计算。
这种方式能够统一控制一部分尺寸,但整体实现和维护成本也会更高。从最终效果来看,它仍然需要面对屏幕宽高比不同的问题,因此并不一定比直接使用 vw/vh 更简单。
如果项目本身已经有成熟的 rem 适配体系,可以继续沿用;如果是新开发的数据大屏,则没有必要为了适配而专门引入一套 rem 方案。
三种方案应该怎么选择
最后再总结一下:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
vw/vh |
能跟随屏幕宽高变化,兼容不同比例 | 每个尺寸都要转换,处理相对细致 | 需要铺满屏幕、兼容多种分辨率 |
scale |
实现简单,代码量少,还原度高 | 屏幕比例不一致时会留白 | 固定环境、演示大屏、允许留白 |
rem + vw/vh |
可以统一控制部分尺寸 | 方案更复杂,效果与前两种有重叠 | 已有成熟 rem 体系的项目 |
如果只给一个建议:
- 项目要求不高,想快速完成适配,优先使用
scale; - 项目需要适配不同尺寸和不同比例的大屏,优先使用
vw/vh; rem可以作为已有项目的补充,但不建议把它作为新大屏项目的第一选择。
大屏自适应没有一种方案能够覆盖所有情况。开发之前应先确认设计稿尺寸、目标设备分辨率、实际屏幕宽高比,以及客户是否可以接受留白。只有先把这些问题确认清楚,后续的技术选型才不会反复返工。