前言

当接到可视化大屏需求时,你是否会有以下疑问
怎么做一款定制化的数据大屏?
开发可视化数据大屏怎么做自适应?
vm vh、rem、scale 究竟哪种比较好?
时刻不够,有没有偷闲的方法?

一次搞懂数据大屏适配计划 (vw vh、rem、scale)
最近在公司开发了一个可视化大屏,开发定制化大屏,咱们或许都一个感受,开发大屏主要是两方面的工作:

  • 大屏之关键-前期的自适应适配
  • 依据 ui 稿制作图表,调细节

而处理了适配问题后,后边就只是一个慢工出细活,耗时刻的事情了。

适配计划分析

看了网上的各种计划,目前咱们选用的大概有 3 种

计划 完成方法 优点 缺陷
vm vh 1.依照规划稿的尺度,将px按份额核算转为vwvh 1.能够动态核算图表的宽高,字体等,灵活性较高
2.当屏幕份额跟 ui 稿不一致时,不会呈现两边留白状况
1.每个图表都需求独自做字体、距离、位移的适配,比较费事
scale 1.经过 scale 特点,依据屏幕巨细,对图表进行全体的等比缩放 1.代码量少,适配简略
2.一次处理后不需求在各个图表中再去独自适配
1.由于是依据 ui 稿等比缩放,当大屏跟 ui 稿的份额不一样时,会呈现周边留白状况
2.当缩放份额过大时分,字体会有一点点含糊,就一点点
3.当缩放份额过大时分,事件热区会偏移。
rem + vm vh 1.取得 rem 的基准值
2.动态的核算html根元素的font-size
3.图表中经过 vm vh 动态核算字体、距离、位移等
1.布局的自适应代码量少,适配简略 1.由于是依据 ui 稿等比缩放,当大屏跟 ui 稿的份额不一样时,会呈现周边留白状况
2.图表需求单个做字体、距离、位移的适配

以上 3 种计划在实际运用中该怎样选择视详细状况而定,也有看到咱们说自适应在地图的适配中会有一些兼容问题,我这边还没有实践过。

  • 假如想简略,客户能同意留白,选用 scale 即可
  • 假如需求兼容不同份额的大屏,并且想在不同份额中都有比较好的作用,图表占满屏幕,类似于移动端的呼应式,能够选用 vm vh 的计划
  • 至于 rem,个人觉得便是 scale 和 vm vh 的归纳,终究的作用跟 scale 差不多

接下来介绍下三种计划的详细完成,计划中的代码都以 vue2.0 和 vue-cli3 搭建的 vue 项目为例,由于是 demo,图表的一些细节就没有过多细致的调整了

计划一:vw vh

上作用

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

当屏幕的尺度份额刚好是 16:9 时

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

当屏幕的尺度份额大于 16:9 时

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

当屏幕的尺度份额小于 16:9 时

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

完成思路

依照规划稿的尺度,将px按份额核算转为vwvh,转化公式如下

假定规划稿尺度为 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 作为宽高的,就会主动适应不同分辨率的屏幕

话不多说,上代码

css 计划 – 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的途径,就能够大局运用了

vue.config.js

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>
css 计划 – 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>
界说 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';
};
屏幕改动后,图表主动调整

这种运用方法有个坏处,便是屏幕尺度发生改动后,需求手动改写一下才干完成自适应调整

为了处理这个问题,你需求在各个图表中监听页面尺度改动,从头调整图表,在 vue 项目中,也能够借助element-resize-detector,最好封装个 resize 的指令,在各图表中就只要运用该指令就能够了,究竟作为程序员,能偷闲就偷闲

  1. 装置 element-resize-detector

npm install element-resize-detector –save

  1. 引进东西包在组件中运用或者在独自的 js 中运用

import resizeDetector from ‘element-resize-detector’

  1. 封装 directive
// 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 });
  1. main.js 中引进
import '@/directive/directive';
  1. html 代码
<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 稿份额收支太大时,会呈现文字跟图表堆叠的状况

一次搞懂数据大屏适配计划 (vw vh、rem、scale)
这儿咱们就需求封装一个东西函数,来处理图表中文字自适应了

  • 默许状况下,这儿以你的规划稿是 1920*1080 为例,即网页宽度是 1920px (做之前必定问清楚 ui 规划稿的尺度)

  • 把这个函数写在一个独自的东西文件dataUtil.js里边,在需求的时分调用

  • 其原理是核算出当时屏幕宽度和默许规划宽度的比值,将原始的尺度乘以该值

  • 另外,其它 echarts 的装备项,比方距离、定位、边距也能够用该函数

  1. 编写 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));
}
  1. 将函数挂载到原型上
import {fitChartSize} from '@src/utils/dataUtil.js'
Vue.prototype.fitChartFont = fitChartSize;
  1. 这样你能够在.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>

计划二:scale

经过 css 的 scale 特点,依据屏幕巨细,对图表进行全体的等比缩放,从而到达自适应作用

上作用

当屏幕的尺度份额刚好是 16:9 时,页面能刚好全屏展现,内容占满显现器

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

当屏幕的尺度份额小于 16:9 时,页面上下留白,左右占满并上下居中,显现份额坚持 16:9

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

当屏幕尺度份额大于 16:9 时,页面左右留白,上下占满并居中,显现份额坚持 16:9

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

话不多说,上代码

html 部分

<div className="screen-wrapper">
    <div className="screen" id="screen">
    </div>
 </div>

js 部分

<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部分

/*
  除了规划稿的宽高是依据您自己的规划稿决议以外,其他复制粘贴就完事
*/  
.screen-root {
    height: 100%;
    width: 100%;
    .screen {
        display: inline-block;
        width: 1920px;  //规划稿的宽度
        height: 960px;  //规划稿的高度
        transform-origin: 0 0;
        position: absolute;
        left: 50%;
        top: -50%;
    }
}

完成思路

怎么缩放

屏幕宽高比 < 规划稿宽高比,咱们需求缩放的份额是屏幕宽度 / 规划稿宽度
屏幕宽高比 > 规划稿宽高比,咱们需求缩放的份额是屏幕高度 / 规划稿高度

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%

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

接下来使用绝对定位将图表定位到中心方位

position: absolute;
left: 50%;
top: 50%;

偷闲方法-插件

v-scale-screen是运用 css 特点 transform 完成缩放作用的一个大屏自适应组件,经过 scale 进行等份额核算,到达等份额缩放的作用,同时也支撑铺满全屏,宽度等比,高度等比,等自适应计划,详细可查大屏自适应终极处理计划

计划三:rem + vw wh

上作用

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

当屏幕的尺度份额刚好是 16:9 时,页面能刚好全屏展现,内容占满显现器

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

当屏幕的尺度份额小于 16:9 时,页面上下留白,左右占满并上下居中,显现份额坚持 16:9

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

当屏幕尺度份额大于 16:9 时,页面左右留白,上下占满并居中,显现份额坚持 16:9

一次搞懂数据大屏适配计划 (vw vh、rem、scale)

完成思路

关于 rem
rem(font size of the root element),是 css3 中新增的一个巨细单位,即相关于根元素 font-size 值的巨细。
自适应思路
动态的核算出页面的 fontsize 从而改动 rem 的巨细。

  1. 拿 1920 * 1080 的规范屏幕巨细为例,将屏幕分为10份,先核算rem 的基准值: 1920 / 10 = 192;
  2. 把所有元素的长、宽、方位、字体巨细等本来的 px 单位悉数转化成 rem;
  3. 网页加载后,用 js 去核算当时浏览器的宽度,并设置 html 的 font-size 为 (当时浏览器窗口宽度 / 10) 。
    这样的话 10rem 就刚好等于浏览器窗口的宽度,也就能够确保 100% 宽度,等份额缩放规划稿的页面了。
    一次搞懂数据大屏适配计划 (vw vh、rem、scale)

因而 rem + vm vh 计划要处理三件事

  1. 取得 rem 的基准值;
  2. 页面内写一段 js 代码,动态的核算html根元素的font-size
  3. 屏幕改动后,图表主动调整和图表字体、距离、位移等的自适应。

完成计划

第一点:取得 rem 的基准值

  1. 首要装置@njleonzhang/postcss-px-to-rem这个包
npm i @njleonzhang/postcss-px-to-rem -D
  1. 在项目根目录新建.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.
   }
  }
}
  1. 装备完成后,页面内的 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)
})()
  1. 在入口文件 main.js 中引进 rem.js 文件
import './utils/rem.js';

至此,页面就现已能够完成 16:9 自适应了。

第三点:屏幕改动,图表自适应
屏幕改动后,图表主动调整字体、距离、位移等,此处参阅上面 vm vh 的完成方法即可,在此就不重复赘述了

参阅资料

  • 引荐一个echarts 的案列网站,需求什么直接图表直接在上面去找,能够省去许多查 echarts 装备的时刻
    全网echarts案例资源大总结和echarts的高效运用技巧(细节版)

  • scale 计划参阅: 数据大屏最简略自适应计划,无需适配rem单位

  • vm vh 计划参阅: Vue+Echarts企业级大屏项目适配计划

  • rem 计划参阅:数据大屏rem适配计划