源代码
需要效果如下(无节流版效果)
我们可以看到没有节流会有一点卡顿
思路
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、效果 (节流版本,但是有问题)
???什么东西,为什么只有最后一个图表重置大小了,其它没变化??
5、分析原因
节流确实生效了,问题是什么?
因为在一个页面内使用了多次这个组件,由于节流是异步的, 暂时只能记录最后一个组件对象
所以只会响应resize最后一个
6、解决方法
其实我们更优的方法,应该把监听窗口事件(
window.addEventListener('resize', fn))抽离到最外层,而不是在组件内
我的思路-使用vuex
1、在全局中监听
window.addEventListener('resize', fn),套上节流。
2、把监听到的窗口大小存在vuex内 3、在我们的chartCompnent.vue组件中监听vuex的窗口大小
7、最终效果(带节流)
加了节流之后,会顺畅很多
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
- 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()
})