手摸手教你封装echarts自适应大小组件(节流版)

4,131 阅读1分钟

源代码

代码地址

需要效果如下(无节流版效果)

我们可以看到没有节流会有一点卡顿 Z475T0S74G.gif

思路

1、把这三个图表抽离出一个公共组件
2、在组件内进行监听窗口 window.addEventListener('resize', fn)
3、最后组件直接响应图表视图chart.resize()

笔者这里用的是vue,所以以vue为标准

1、创建公共组件chartComponent.vue

组件props接收 chartId, echarts绑定的对象domId,
组件props接收 option, echarts的options属性,
图表渲染完成事件参考之前写的一篇

创建图表公用组件chartComponent.vue

  • javascript
<script>
import * as echarts from 'echarts'
export default {
  name: 'chartComponent',
  props: {
    chartId: { type: String, required: true },//图表id
    option: { type: Object, default: () => {} }, //图表属性
    loading: { type: Boolean, default: false }// 可不写loading状态
  },
  data() {
    return {
      myChart: null,
      isFinished: false
    }
  },
  methods: {
    drawChart() {
      this.myChart.setOption(this.option, true)
    },
    chartResize() {
      this.myChart.resize()
      console.log('重置大小')
    }
  },
  mounted() {
    if (!this.myChart) {
      this.myChart = echarts.init(this.$refs[this.chartId])
    }
    //监听窗口大小变化
    window.addEventListener('resize', this.chartResize)
    this.drawChart()

    this.myChart.on('finished', () => {
      if (!this.isFinished) {
        console.log('finished')
        this.isFinished = true
        this.myChart.resize()
      }
    })
  },
  beforeDestroy() {
    //销毁window监听对象
    window.removeEventListener('resize', this.chartResize)
    if (this.myChart) this.myChart.dispose()
    this.myChart = null
  },
  watch: {
    //数据可能是异步的,用watch接听接收
    option: {
      handler(newVal) {
        const options = newVal
        this.drawChart(options)
      },
      deep: true
    }
  }
}
</script>
  • html,css
<template>
  <div class="chart-box" :ref="chartId"></div>
</template>

<style lang="scss" scoped>
.chart-box {
  width: 100% !important;
  height: 100% !important;
}
</style>

2、使用

  • 直接引入使用就好
  <chartComponent chartId="chart_a" :option="option"></chartComponent>

细心的朋友发现没有节流,好了,我们在chartResize上增加节流, 没有节流会有明显的卡顿

3、增加节流

1、引入节流方法(可以自己网上找,也可以用lodash的节流),这里就不写节流的具体内容
2、在chartResize方法上绑定节流

  • chartCompont中引入throttle方法
import { throttle } from '../util/tool'

//为上面的chartResize方法增加节流
methods: {
    ...
    //绑定上节流方法
    chartResize: throttle(function () {
      this.myChart.resize()
      console.log('重置大小')
    }, 2000)
}

4、效果 (节流版本,但是有问题)

vYcksxS3F7.gif

???什么东西,为什么只有最后一个图表重置大小了,其它没变化??

5、分析原因

节流确实生效了,问题是什么?
因为在一个页面内使用了多次这个组件,由于节流是异步的, 暂时只能记录最后一个组件对象
所以只会响应resize最后一个

6、解决方法

其实我们更优的方法,应该把监听窗口事件(window.addEventListener('resize', fn))抽离到最外层,而不是在组件内

我的思路-使用vuex

1、在全局中监听window.addEventListener('resize', fn),套上节流。
2、把监听到的窗口大小存在vuex内 3、在我们的chartCompnent.vue组件中监听vuex的窗口大小

7、最终效果(带节流)

加了节流之后,会顺畅很多

walSebLwxu.gif

8、部分优化代码,vuex,onResize提取

vuex-window模块

新建vuex的window模块,用来存放页面的宽高。

const window = {
  state: {
    //页面的宽高
    innerWH: {}
  },

  mutations: {
    SET_INNER_WH(state, val) {
      //state.innerWH = { ...Object.assign(state.innerWH, val) }
      state.innerWH = val
    }
  },

  actions: {
    setInnerWH({ commit }, val) {
      commit('SET_INNER_WH', val)
    }
  }
}

export default window

main.js内引入的监听onResize.js文件

全局监听窗口大小,并且存入vuex onResize.js

import { throttle } from './tool'//节流方法

//vuex对象
import store from '@/store/index'

const windowFn = throttle(
  function () {
    let innerWH = {
      innerWidth: window.innerWidth,
      innerHeight: window.innerHeight
    }
    store.dispatch('setInnerWH', innerWH)
  },
  1000,
  { leading: false }
)

//全局监听窗口大小变化事件
window.addEventListener('resize', windowFn)

组件内的监听vuex内数据-chartComponent

chartComponent

//监听vuex内的窗口大小
watch: {
    innerWH: {
      handler() {
        console.log('窗口resize')
        this.myChart.resize()
      },
      deep: true
    }
}

9、总结思路

全局监听窗口大小, 把窗口大小存在vuex内 ,节流在这里加上。
echarts组件中监听vuex内的窗口大小,从而响应图表大小
尽量不要使用flex,grid布局,会使resize()失效,之前在github上的提问


10、补充-新增模拟接口loading时加载状态

chart_3效果, 模拟接口刷新属性的Loading状态 利用chart.on('finished', () => {}) 来控制关闭loading

SwgWGp4c2w.gif

  • chart_3.vue
<!--html-->
<chartComponent
      :loading="loading"
      chartId="chart_c"
      :option="option"
      @closeLoading="closeLoading" <!--接收子组件关闭loading事件-->
      ref="chartComponent"
    ></chartComponent>
//js
methods: {
    initChart() {
        this.loading = true
        //ajax.. 调用接口,拿到数据,这里用setTimout模拟
        setTimeout(()=>{
            this.option = {...}
        }, 1000)
    }
    closeLoading() {
        console.log(85, '关闭loading')
        this.loading = false
    },
}
  • chartComponent.vue
    //finished事件,麻烦,Hover,click事件都会触发,但是也是真的图表完成事件
    this.myChart.on('finished', () => {
      /**********************只有在Loading状态的时候才能关闭***********************/
      if (this.loading) {
        //如果还是加载状态,关闭Loading
        this.$emit('closeLoading')
      }
      /**********************只有在第一次的时候重置大小***********************/
      if (!this.isFirstFinished) {
        this.isFirstFinished = true
        setTimeout(() => {
          this.myChart.resize()
        })
      }
    })

11、补充-字体也随窗口变化

之前有人反映图表内的字体不会随窗口变化而变化
解决办法,把echarts版本升级到5
echarts属性设置fontSize单位为rem (echarts5之前不支持rem单位)

  • 例子
legend: {
  textStyle: {
    fontSize: '.8rem',//单位设置rem就行了
    color: '#333',
    itemGap: '1.5rem' 
  },
  data: legendData
},

12、2023-11-22 更新

vue3中更加简单了。。果然时代在变化。我们不用监听窗口大小, 直接监听容器大小
useElementSize 是vue3的官方hooks

/** ********************监听元素大小***********************/
const chartBoxRef = ref(null)
const { width, height } = useElementSize(chartBoxRef)

watch([() => width.value, () => height.value], () => {
  echart.resize()
})