前端数据可视化适配方案汇总
1、前言
在日常开发过程中,我们经常会遇到大屏可视化这种需求,而选用技术方案的不同则会呈现出不同的效果。目前,大屏适配方案主要有以下三种:
方案 | 实现方式 | 优点 | 缺点 |
---|---|---|---|
vw vh | 1.按照设计稿的尺寸,将px按比例计算转为vw和vh | 1.可以动态计算图表的宽高,字体等,灵活性较高 2.当屏幕比例跟 ui 稿不一致时,不会出现两边留白情况 | 1.每个图表都需要单独做字体、间距、位移的适配,比较麻烦 |
scale | 1.通过 scale 属性,根据屏幕大小,对图表进行整体的等比缩放 | 1.代码量少,适配简单 2.一次处理后不需要在各个图表中再去单独适配 | 1.因为是根据 ui 稿等比缩放,当大屏跟 ui 稿的比例不一样时,会出现周边留白情况2.当缩放比例过大时候,字体会有一点点模糊,就一点点 3.当缩放比例过大时候,事件热区会偏移。 |
rem | 1.获得 rem 的基准值 2.动态的计算html根元素的font-size 3.图表中通过 vw vh 或者 百分比动态计算字体、间距、位移等 | 1.布局的自适应代码量少,适配简单 | 1.因为是根据 ui 稿等比缩放,当大屏跟 ui 稿的比例不一样时,会出现周边留白情况 2.图表需要单个做字体、间距、位移的适配 |
以上 3 种方案在实际应用中该怎么选择视具体情况而定,也有看到大家说自适应在地图的适配中会有一些兼容问题。
如果想简单,客户能同意留白,选用 scale 即可
如果需要兼容不同比例的大屏,并且想在不同比例中都有比较好的效果,图表占满屏幕,类似于移动端的响应式,可以采用 vw vh 的方案
至于 rem,个人觉得就是 scale 和 vw vh 的综合,最终的效果跟 scale 差不多
接下来介绍下三种方案的具体实现,方案中的代码都以 vue2 和 ts 搭建的 vue 项目为例。
2、方案一:vw vh
2.1 实现效果
2.2 实现思路
按照设计稿的尺寸,将px按比例计算转为vw和vh,转换公式如下
假设设计稿尺寸为 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.3 实现代码
2.3.1 css 方案
2.3.1.1 sass
- util.scss
// 使用 scss 的 math 函数,https://sass-lang.com/documentation/breaking-changes/slash-div @use "sass:math"; // 默认设计稿的宽度 $designWidth: 1920; // 默认设计稿的高度 $designHeight: 1080; // px 转为 vw 的函数 @function vw($px) { @return math.div($px, $designWidth) * 100vw; } // px 转为 vh 的函数 @function vh($px) { @return math.div($px, $designHeight) * 100vh; }
- 路径配置
在vue.config.js里配置一下utils.scss的路径,就可以全局使用了
const path = require("path"); function resolve(dir) { return path.join(__dirname, dir); } module.exports = { publicPath: "", configureWebpack: { name: "app name", resolve: { alias: { "@": resolve("src"), }, }, }, css: { // 全局配置 utils.scs,详细配置参考 vue-cli 官网 loaderOptions: { sass: { prependData: `@import "@/styles/utils.scss";`, }, }, }, };
- 在 .vue 中使用
<template> <div class="box"> </div> </template> <script> export default{ name: "Box", } </script> <style lang="scss" scoped="scoped"> /* 直接使用 vw 和 vh 函数,将像素值传进去,得到的就是具体的 vw vh 单位 */ .box{ width: vw(300); height: vh(100); font-size: vh(16); background-color: black; margin-left: vw(10); margin-top: vh(10); border: vh(2) solid red; } </style>
2.3.1.2 less
- utils.less
@charset "utf-8"; // 默认设计稿的宽度 @designWidth: 1920; // 默认设计稿的高度 @designHeight: 1080; .px2vw(@name, @px) { @{name}: (@px / @designWidth) * 100vw; } .px2vh(@name, @px) { @{name}: (@px / @designHeight) * 100vh; } .px2font(@px) { font-size: (@px / @designWidth) * 100vw; }
- 路径配置
在vue.config.js里配置一下utils.less
const path = require("path"); function resolve(dir) { return path.join(__dirname, dir); } module.exports = { publicPath: "", configureWebpack: { name: "app name", resolve: { alias: { "@": resolve("src"), }, }, }, css: { // 全局配置utils.scss loaderOptions: { less: { additionalData: `@import "@/styles/utils.less";`, }, }, }, };
- 在 .vue 文件中使用
<template> <div class="box"> </div> </template> <script> export default{ name: "Box", } </script> <style lang="less" scoped="scoped"> /* 直接使用 vw 和 vh 函数,将像素值传进去,得到的就是具体的 vw vh单位 */ .box{ .px2vw(width, 300); .px2vh(height, 100); .px2font(16); .px2vw(margin-left, 300); .px2vh(margin-top, 100); background-color: black; } </style>
2.3.2 js方案
// 定义设计稿的宽高 const designWidth = 1920; const designHeight = 1080; // px转vw export const px2vw = (_px) => { return (_px * 100.0) / designWidth + 'vw'; }; export const px2vh = (_px) => { return (_px * 100.0) / designHeight + 'vh'; }; export const px2font = (_px) => { return (_px * 100.0) / designWidth + 'vw'; };
2.3.3 图表字体、间距、位移等尺寸自适应
echarts 的字体大小只支持具体数值(像素),不能用百分比或者 vw 等尺寸,一般字体不会去做自适应,当宽高比跟 ui 稿比例出入太大时,会出现文字跟图表重叠的情况。
这里我们就需要封装一个工具函数,来处理图表中文字自适应了👇
- 默认情况下,这里以你的设计稿是 1920*1080 为例,即网页宽度是 1920px (做之前一定问清楚 ui 设计稿的尺寸)
- 把这个函数写在一个单独的工具文件dataUtil.js里面,在需要的时候调用
- 其原理是计算出当前屏幕宽度和默认设计宽度的比值,将原始的尺寸乘以该值
- 另外,其它 echarts 的配置项,比如间距、定位、边距也可以用该函数
- 编写 dataUtil.js 工具函数。
// 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)); }
- 将函数挂载到原型上
import {fitChartSize} from '@src/utils/dataUtil.js' Vue.prototype.fitChartFont = fitChartSize;
- 在.vue文件中直接使用this.fitChartSize()调用
<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>
3、scale
通过 css 的 scale 属性,根据屏幕大小,对图表进行整体的等比缩放,从而达到自适应效果
3.1 实现效果
当屏幕尺寸比例大于 16:9 时,页面左右留白,上下占满并居中,显示比例保持 16:9
3.2 实现思路
当屏幕宽高比 < 设计稿宽高比,我们需要缩放的比例是屏幕宽度 / 设计稿宽度
当屏幕宽高比 > 设计稿宽高比,我们需要缩放的比例是屏幕高度 / 设计稿高度
3.3 实现代码
<div className="screen-wrapper"> <div className="screen" id="screen"> </div> </div> <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> /* 除了设计稿的宽高是根据您自己的设计稿决定以外,其他复制粘贴就完事 */ .screen-root { height: 100%; width: 100%; .screen { display: inline-block; width: 1920px; //设计稿的宽度 height: 960px; //设计稿的高度 transform-origin: 0 0; position: absolute; left: 50%; top: 50%; } }
4、rem方案
4.1 实现思路
关于 rem
rem(font size of the root element),是 css3 中新增的一个大小单位,即相对于根元素 font-size 值的大小。
自适应思路
动态的计算出页面的 fontsize 从而改变 rem 的大小。
- 拿 1920 * 1080 的标准屏幕大小为例,将屏幕分为10份,先计算rem 的基准值: 1920 / 10 = 192;
- 把所有元素的长、宽、位置、字体大小等原来的 px 单位全部转换成 rem;
- 网页加载后,用 js 去计算当前浏览器的宽度,并设置 html 的 font-size 为 (当前浏览器窗口宽度 / 10) 。
这样的话 10rem 就刚好等于浏览器窗口的宽度,也就可以保证 100% 宽度,等比例缩放设计稿的页面了。
因此 rem + vw vh 方案要解决三件事
- 获得 rem 的基准值;
- 页面内写一段 js 代码,动态的计算html根元素的font-size;
- 屏幕变化后,图表自动调整和图表字体、间距、位移等的自适应。
4.2 实现代码
- 获得 rem 的基准值
// 1.首先安装 @njleonzhang/postcss-px-to-rem 这个包 npm i @njleonzhang/postcss-px-to-rem -D 2. 在项目根目录新建.postcssrc.js配置文件 module.exports = { plugins: { autoprefixer: {}, "@njleonzhang/postcss-px-to-rem": { unitToConvert: 'px', // (String) 要转换的单位,默认是 px。 widthOfDesignLayout: 1920, // (Number) 设计布局的宽度。对于pc仪表盘,一般是 1920. unitPrecision: 3, // (Number) 允许 rem 单位增长到的十进制数字. selectorBlackList: ['.ignore', '.hairlines'], // (Array) 要忽略并保留为 px 的选择器. minPixelValue: 1, // (Number) 设置要替换的最小像素值. mediaQuery: false // (Boolean) 允许在媒体查询中转换 px. } } } 3.配置完成后,页面内的 px 就会被转换成 rem 了
- 动态的计算html根元素的font-size
1.在工具函数文件中新建一个 rem.js 文件,用于动态计算 font-size (function init(screenRatioByDesign = 16 / 9) { let docEle = document.documentElement function setHtmlFontSize() { var screenRatio = docEle.clientWidth / docEle.clientHeight; var fontSize = ( screenRatio > screenRatioByDesign ? (screenRatioByDesign / screenRatio) : 1 ) * docEle.clientWidth / 10; docEle.style.fontSize = fontSize.toFixed(3) + "px"; console.log(docEle.style.fontSize); } setHtmlFontSize() window.addEventListener('resize', setHtmlFontSize) })() 2.在入口文件 main.js 中引入 rem.js 文件 import './utils/rem.js';
- 屏幕变化,图表自适应
屏幕变化后,图表自动调整字体、间距、位移等。
5、扩展方案
以下给大家推荐三个方案,只需要简单的几行代码配置,可以完全解决大屏开发中的适配问题,让你效率翻倍。
5.1autofit.js
autofit.js 基于比例缩放原理,通过动态调整容器的宽度和高度来实现全屏填充,避免元素的挤压或拉伸。
autofit.js 提供了一种简单而有效的方法来实现网页的自适应设计,尤其适合需要在不同分辨率和屏幕尺寸下保持布局一致性的应用场景。
- 安装:
npm i autofit.js
- 配置:
import autofit from 'autofit.js'; onMounted(() => { autofit.init({ el: '#page', dw: 375, dh: 667 }) }) * - 传入对象,对象中的属性如下: * - el(可选):渲染的元素,默认是 "body" * - dw(可选):设计稿的宽度,默认是 1920 * - dh(可选):设计稿的高度,默认是 1080 * - resize(可选):是否监听resize事件,默认是 true * - ignore(可选):忽略缩放的元素(该元素将反向缩放),参数见readme.md * - transition(可选):过渡时间,默认是 0 * - delay(可选):延迟,默认是 0
源码地址
Github 地址:https://github.com/995231030/autofit.js
5.2 v-scale-screen
大屏自适应容器组件,可用于大屏项目开发,实现屏幕自适应,可根据宽度自适应,高度自适应,和宽高等比例自适应,全屏自适应(会存在拉伸问题),如果是 React 开发者,可以使用 r-scale-screen。
- 安装:
npm install v-scale-screen # or yarn add v-scale-screen
- 配置:
<template> <v-scale-screen width="1920" height="1080"> <div> <v-chart>....</v-chart> <v-chart>....</v-chart> <v-chart>....</v-chart> <v-chart>....</v-chart> <v-chart>....</v-chart> </div> </v-scale-screen> </template> <script> import { defineComponent } from 'vue' import VScaleScreen from 'v-scale-screen' export default defineComponent({ name: 'Test', components: { VScaleScreen } }) </script>
github 地址:https://github.com/Alfred-Skyblue/v-scale-screen
5.3 FitScreen
一种基于缩放的大屏自适应解决方案的基本方法,一切都是基于设计草图的像素尺寸,通过缩放进行适配,一切都变得如此简单。
支持 vue2、vue3 以及 react,可以适用于任何框架,只要一点点代码。
- 安装:
npm install @fit-screen/vue # or yarn add @fit-screen/vue # or pnpm install @fit-screen/vue
- 配置:
<script setup> import FitScreen from '@fit-screen/vue' </script> <template> <FitScreen :width="1920" :height="1080" mode="fit"> <div> <a href="https://vitejs.dev" target="_blank"> <img src="/vite.svg" class="logo" alt="Vite logo"> </a> <a href="https://vuejs.org/" target="_blank"> <img src="./assets/vue.svg" class="logo vue" alt="Vue logo"> </a> </div> <HelloWorld msg="Vite + Vue" /> </FitScreen> </template>
github 地址:https://github.com/jp-liu/fit-screen
最后,如果大家有更好的适配方案,欢迎在评论区留言一起学习交流!👏