echarts适配方案

12,628 阅读2分钟

前言demo是使用@vue/cli 4.5.15 搭建的项目,此博文参照juejin.cn/post/700908… juejin.cn/post/696610… 进行了尝试和总结。

适配方案选择

vw和vh适配方案(非等比适配)

窗口适配

浏览器的完整窗口大小是100vw * 100vh1vw则就是浏览器窗口宽度的1%vh同理。此时我们通过计算就可以计算出DOM元素所占浏览器窗口宽高的比例。例如500px转换为vw则就是500*100/1920vw

Snipaste_2021-12-02_17-50-45

scree01

  • 使用vwvh适配方案以适配不同的显示器屏幕。

    //使用scss的math函数
    @use 'sass:math';
    
    // 默认设计稿的宽度
    $designWidth: 1920;
    // 默认设计稿的高度
    $designHeight: 1080;
    
    // px转vw的函数
    @function vw($px) {
      // 类似除法运算符($px/$designWidth) * 100vw
      @return math.div($px, $designWidth) * 100vw;
    }
    
    // px转vh的函数
    @function vh($px) {
      @return math.div($px, $designHeight) * 100vh;
    }
    
  • vue.config.js中配置utils.scss的路径即可全局使用。

    module.exports = {
      css: {
        // 全局配置utils.scss,详细配置参考vue-cli官网
        loaderOptions: {
          sass: {
            prependData: '@import "@/assets/styles/utils.scss";'
          }
        }
      }
    }
    
  • 在需要用到的地方使用width:vw(300)代替之前的width:300px

    .box {
      width: vw(300);
      height: vh(500);
      font-size: vh(16);
      background-color: rgb(132, 199, 77);
      margin-left: vw(10);
      margin-top: vh(10);
      border: vh(2) solid red;
    }
    

动态DOM元素适配

在某些情况下,dom元素是需要动态创建的,此时上述的方案就没有办法实现去给动态创建的元素添加样式。因此我们需要在创建元素的同时,动态的为其分配对应的css属性。

// 定义设计稿的尺寸
const designWidth = 1920
const designHeight = 1080

const styleUtil = {
  px2vw: function (_px) {
    return _px * 100 / designWidth + 'vw'
  },
  px2vh: function (_px) {
    return _px * 100 / designHeight + 'vh'
  }
}

使用

import styleUtil from '@/utils/styleUtil.js'

dom.style.width = styleUtil.px2vw(300)
dom.style.height = styleUtil.px2vh(300)
dom.style.background = '#ccc'

动画 (1)

等比缩放适配方案

动画.gif

基于transform的适配方案

<template>
    <div class="home" ref="home">
        <div class="left_container">
            <div class="left_top">
                <line-chart-page/>
            </div>
            <div class="left_center">
                <line-chart-page/>
            </div>
            <div class="left_bottom">
                <line-chart-page/>
            </div>
        </div>
        <div class="center_container">
            <div class="center_top">
                <Map/>
            </div>
            <div class="center_bottom">
                <bar-chart-page/>
            </div>
        </div>
        <div class="right_container">
            <div class="right_top">
                <line-chart-page/>

            </div>
            <div class="right_center">
                <line-chart-page/>

            </div>
            <div class="right_bottom">
                <line-chart-page/>
            </div>
        </div>
    </div>
</template>
<script>
    export default {
        mounted () {
            function setScale () {
                const designWidth = 1920// 设计稿的宽度,根据实际项目调整
                const designHeight = 1080// 设计稿的高度,根据实际项目调整
                // (窗口宽度 / 窗口高度) < (设计稿宽度 / 设计稿高度) ? (窗口宽度 / 设计稿高度) : (窗口高度 / 设计稿高度)
                const scale = document.documentElement.clientWidth / document.documentElement.clientHeight < designWidth / designHeight
                        ? (document.documentElement.clientWidth / designWidth)
                        : (document.documentElement.clientHeight / designHeight)
                document.querySelector('.home').style.transform = `scale(${scale})`
            }

            setScale()

            window.onresize = function () {
                setScale()
            }
        },
    }
</script>

基于rem实现等比缩放

  • 根据屏幕尺寸动态计算根元素htmlfontSize值。

    setFontSize()
    function setFontSize(){
        let designWidth = 1366; 
        let designHeight = 768; 
        var fontSize = 
            document.documentElement.clientWidth/document.documentElement.clientHeight < designWidth/designHeight ? 
            (document.documentElement.clientWidth / designWidth) * 12:
            (document.documentElement.clientHeight / designHeight) * 12;
        document.querySelector('html').style.fontSize = fontSize + 'px';
    }
    window.onresize = function () {
        setFontSize()
    };
    
  • 设置DOM元素样式

    $design_width: 1366;//设计稿的宽度,根据实际项目调整
    $design_height: 768;//设计稿的高度,根据实际项目调整
    
    @function px2rem($px) {
       $design_font_size: 12;
       @return ($px/$design_font_size) + rem;
    }
    
    div {
      width: px2rem(500);
      height: px2rem(500);
    }
    

Echart图表适配以及业务分装

业务分装

背景:

  • 在实际项目中,每种类型的图表的option配置都及其类似的配置项,我们可以将这些重复或者类似的配置抽离处理放置在一个单独的配置文件中。
  • 也具有相同的图表配置,只是数据发生了变化,因此没有必要一直去做着重复的工作。

封装后需要实现的效果:

  • 我们需要将业务数据和图表配置分离,在使用的时候只需要传递业务数据即可。
  • 在类型相同的图标中传递不同的自定义配置项即可实现不同的图标样式。
  • 无论传递什么数据均可正常显示图表。(需要对传递的数据做判断,类似undefinednull等等)

拆分思路

  • 将所有的图表组件放在components/Charts文件夹中。
  • 每种图表单独新建文件夹存放在components/Chart/下。
  • 每种图表的默认配置项存放在defaultOption.js中。
├── src
│ ├── components
│ │ ├── Charts
│ │ │ ├── BarChart
│ │ │ │ ├── defaultOption.js
│ │ │ │ ├── index.vue

图表适配

  • 关于屏幕的适配使用上述的方案即可解决。

  • 图表适配需要用到echarts官方的resize事件。

  • 图表字体的适配:根据项目需求而定,如果需要对图表的字体做出适应,可以使用如下的方法。

    /* Echarts图表字体、间距自适应 */
    export const fitChartSize = (size, defalteWidth = 1920) => {
      const clientWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
      if (!clientWidth) return size
      const scale = (clientWidth / defalteWidth)
      return Number((size * scale).toFixed(3))
    }
    

举例

  • 图例组件src/components/BarChart/index.vue

    <template>
      <!-- 空的业务数据 -->
      <h1 v-if="!isEmpty">没有数据</h1>
      <div v-else class="barChart" ref=barChart></div>
    </template>
    <script>
    import * as echarts from 'echarts/core'
    // 监控元素大小变化
    import ResizeListener from 'element-resize-detector'
    import { merge } from 'lodash'
    import baseOption from './defaultOption' // 默认配置
    export default {
      data: () => ({
        intanceChart: null // 图例组件
      }),
      mounted () {
        // 当绘制图标的DOM元素存在时
        if (this.$refs.barChart) {
          this.initializationChart() // 初始化图表
          window.addEventListener('resize', this.handleWindowResize) // 监听窗口尺寸
          this.addChartResizeListener() // 对echart尺寸监听
        }
      },
      props: {
        // 业务数据
        seriesData: {
          type: Array,
          required: true,
          default: () => []
        },
        // 需要特殊定制的图表配置项
        extraOption: {
          type: Object,
          default: () => ({})
        }
      },
      // 监听业务数据的变化
      watch: {
        // 监听业务数据的变化,如果没有数据就不绘制图标,展示普通DOM元素即可
        isEmpty: {
          handler (newVal) {
            if (newVal) {
              this.$nextTick(() => {
                this.initializationChart() // 初始化图标数据
                window.addEventListener('resize', this.handleWindowResize) // 监听窗口尺寸
                this.addChartResizeListener() // 对echart尺寸监听
              })
            }
          }
        },
        // 当业务数据发生变化的时候,更新图例
        seriesData: {
          deep: true,
          handler () {
            this.updateChartView()
          }
        },
        // 监听自定义配置项,适配字体
        extraOption: {
          deep: true,
          handler (newVal) {
            this.instanceChart.setOption(newVal) // 重新设置图例组件的大小
          }
        }
      },
      computed: {
        // 判断业务数据是否为空数据
        isEmpty () {
          return this.seriesData.length
        }
      },
      methods: {
        /* 01. 合并配置项数据,将默认配置项和自定以配置项和并为一个option */
        assembleDataToOption () {
          return merge(
            {},
            baseOption,
            {
              series: this.seriesData
            },
            this.extraOption
          )
        },
        // 02. 初始化图表组件
        initializationChart () {
          this.instanceChart = echarts.init(this.$refs.barChart) //  组件实例
          const fullOption = this.assembleDataToOption() // 合并后的option
          this.instanceChart.setOption(fullOption, true)
        },
        // 03. 更新echart视图
        updateChartView () {
          if (!this.$refs.barChart) return
          const fullOption = this.assembleDataToOption() // 合并后的option
          this.instanceChart.setOption(fullOption, true)
        },
        // 04.对chart元素尺寸进行监听,当发生变化时同步更新echart视图
        addChartResizeListener () {
          const instance = ResizeListener({
            strategy: 'scroll',
            callOnAdd: true
          })
          instance.listenTo(this.$el, () => {
            if (!this.instanceChart) return
            this.instanceChart.resize()
          })
        },
        // 05. 当窗口缩放的时候,自动适配echart
        handleWindowResize () {
          if (!this.$refs.barChart) return
          this.instanceChart.resize()
        }
      }
    }
    </script>
    <style scoped>
    .barChart {
      width: 100%;
      height: 100%;
    }
    </style>
    
  • 使用图例

    <template>
      <div class="chart">
        <bar-chart :seriesData="seriesData" :extraOption="extraOption"/>
      </div>
    </template>
    
    <script>
    import barChart from '@/components/BarChart'
    import { fitChartSize } from '@/utils/echartUtils'
    // 业务数据
    const seriesData = [
      { data: [320, 332, 301, 334, 390, 330, 320] },
      { data: [120, 132, 101, 134, 90, 230, 210] },
      { data: [220, 182, 191, 234, 290, 330, 310] },
      { data: [150, 232, 201, 154, 190, 330, 410] },
      { data: [862, 1018, 964, 1026, 1679, 1600, 1570] },
      { data: [620, 732, 701, 734, 1090, 1130, 1120] },
      { data: [120, 132, 101, 134, 290, 230, 220] },
      { data: [60, 72, 71, 74, 190, 130, 110] },
      { data: [62, 82, 91, 84, 109, 110, 120] }
    ]
    export default {
      name: 'BarChart',
      components: { barChart },
      data () {
        return { extraOption: {}, seriesData }
      },
      created () {
        this.extraOption = this.extraEchartOption()
      },
      mounted () {
        // 在监听到窗口大小发生变化时,重新对自定义配置赋值,实现字体的适应
        window.addEventListener('resize', () => {
          this.extraOption = this.extraEchartOption()
        })
      },
      methods: {
        extraEchartOption () {
          return {
            tooltip: {
              textStyle: {
                fontSize: fitChartSize(12)
              }
            },
            legend: {
              itemWidth: fitChartSize(20),
              itemHeight: fitChartSize(15),
              textStyle: {
                fontSize: fitChartSize(12),
                width: fitChartSize(12)
              }
            },
            grid: {
              left: fitChartSize(3),
              right: fitChartSize(3),
              bottom: fitChartSize(3)
            },
            xAxis: [
              {
                axisLabel: {
                  fontSize: fitChartSize(12)
                }
              }
            ],
            yAxis: [
              {
    
                axisLabel: {
                  fontSize: fitChartSize(12)
                }
              }
            ]
          }
        }
      }
    }
    </script>
    
    <style scoped lang="scss">
    .chart {
      width: 100%;
      height: 100%;
    }
    </style>
    

动画 (1)

还是有点小卡顿,希望有看到的大佬能够提点解决意见

示例仓库地址gitee.com/coderlzw/ec…