这几天公司里边有一个项目,叫做日控制台,该项目是在webview下的一个webapp,使用vue构建,项目中要求使用许多自定义的图表。考察了许多图表组件之后,发现echarts是所有表库中,最灵活,特效最好看的一种。
一、构建基础公共组件
1. 实现基础功能
在echart官网上搜索到,如何使用
# 1. 获取一个用于挂在 echarts 的 DOM 元素
let $echartsDOM = document.getElementById('echarts-dom')
# 2. 初始化
let myEcharts = echarts.init($echartsDOM)
# 3. 设置配置项
let option = {...}
# 4. 为 echarts 指定配置
myEcharts.setOption(option)
使用echart的步骤也就这几部,就是先获取到承载echart实例的dom实例,然后调用init()方法初始化图标实例,最后调用setOption()方法传入配置项
这几步都要在vue的mounted方法下实现.
mounted() {
let $echartsDOM = document.getElementById('echarts-dom')
let myEcharts = echarts.init($echartsDOM)
let option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data: ['销量']
},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
}
myEcharts.setOption(option)
}
}
注:在 Vue 中,首先我们需要使用 import echarts from 'echarts' 以引入 echarts。
二、组件化
思路很简单,就是将业务上用到的图表,比如柱状图、折线图,通通封装成组件,然后再main app文件中调用,通过分析,可以通过传props,来改变setOption()方法中的对象,达到封装不同图表组件的目的。
<ocEcharts class="echarts-container" :options="ocoptions" />
将之前的options转移到main app中的data对象之中。
注:echarts图表要求承载的容器具有固定的宽高才能正常显示
.echarts-container{
width: 100%;
height: 20rem;
}
1. 组件优化-props的series校验
如果在传入组件的props中传入了空对象,就会发现,图表会抛出一个错误,即
Error: Option should contains series.
原因就是传入的 option 配置对象不含有 series 键,所以,默认值处理是需要存在的,即当调用方传入的对象为空或不存在 series 配置时,应在页面上显示一些提示( 对用户友好的提示,而不是对编程人员 ),即避免因报错而造成空白的情况。
此外,当我们像之前那样给 option 这一参数进行类型限制后,倘若调用方传入非对象类型,Vue 会直接抛出错误——这一结果也不是我们想要的。我们应该取消类型限制,并在 option 发生变化时进行依次以下判断:
// 1. 是否为对象;
export function isObject(option) {
return Object.prototype.isPrototypeOf(option)
}
// 2. 是否为空对象;
export function isEmptyObject(option) {
return Object.keys(option).length === 0
}
// 3. 是否包含 series 键;
export function hasSeriesKey(option) {
return !!option['series']
}
// 4. series 是否为数组;
export function isSeriesArray(option) {
return Array.isArray(option['series'])
}
// 5. series 数组是否为空。
export function isSeriesEmpty(option) {
return option['series'].length === 0
}
export function isValidOption(option) {
return isObject(option) && !isEmptyObject(option)
&& hasSeriesKey(option)
&& isSeriesArray(option) && !isSeriesEmpty(option)
}
然后在组件中引入最后的isValidOption方法作为判断,我们先使用一个watch监听options的变化
watch: {
options(options){
this.checkAndSetOption()
}
},
methods: {
checkAndSetOption(){
let options = this.options
if(isValidOption(options)){
this.myEcharts.setOption(options)
this.isOptionAbnormal = false
}else{
this.isOptionAbnormal = true
}
}
}
这里的checkAndSetOption方法是判断传入的option是否合法,如果合法,就执行setOption,isOptionAbnormal变量是监控页面是否显示options非法的flag。
同样的dom样式上的改变,也要使用v-show来判断isOptionAbnormal是否要渲染图表和渲染错误信息。
<div>
<div class="shadow" v-show="isOptionAbnormal">数据为空</div>
<div class="oc_echarts_container">
<div class="echarts" id="echarts-dom" v-show="!isOptionAbnormal" />
</div>
</div>
.oc_echarts_container, .echarts {
width: 100%;
height: 100%;
}
.shadow {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
color: #8590a6;
}
2. 增强组件功能 - 数据加载提示
<div class="loading" v-show="isLoading">
数据加载中...
</div>
在组件内部需要一个外部的props,isLoading,但是这里需要注意,在 Vue 中,v-show 使用 display 控制组件的显隐。而当 echart init 的时候,如果其挂载 DOM 的 v-show 处于 false 状态,则其 init 的对象宽高都是 0。即使之后 v-show 状态改变,由于 mounted 生命周期不会再次触发,从而使得 echarts 显示不正常。所以需要使用css的visibility来控制显隐。
computed: {
isChartVisible() {
return !this.isLoading && !this.isOptionAbnormal
}
},
<div class="oc_echarts_container">
<div class="echarts" id="echarts-dom" :style="{visibility: isChartVisible ? 'visible' : 'hidden'}" />
</div>
3. 组件复用-随机ID
echarts 进行 init 挂载时使用的是 DOM 元素的 ID。而在组件中,我们设置的 ID 是固定的( 注意与 scoped css 进行区分 )。如果多个组件的 ID 是相同的,只有一个组件会被 echarts 挂载。
所以我们要设定一个随机的randomId,赋值到承载echarts图表的dom元素的id中
<div class="oc_echarts_container">
<div class="echarts" :id="randomId" :style="{visibility: isChartVisible ? 'visible' : 'hidden'}" />
</div>
三、延迟加载
延迟加载是组件的一个优化,在业务的开发中可以看到,一个页面往往有着许多的图表,图标伴随着许多异步请求和canvas渲染,如果一次性渲染所有的图表会导致许多的性能问题。这里想到的一个解决方案就是延迟加载。
用通俗的话讲就是,页面滚动到哪张图表就去渲染哪一张图表。
完成这一功能需要以下步骤:
- 监听页面滚动事件;
- 滚动事件中获取 echarts 的位置;
- 在页面当前位置达到 echarts 位置的时候进行 echarts 的初始化。
1. 监听页面滚动
如果要监听页面滚动,需要用到dom的监听器,addEventListener('scroll', [funciton])。这样就能为每一个组件附上监听事件。
2. 获取当前滚动下边界和组件的上边界
这一个步骤需要封装成一个函数,checkPosition()
checkPosition() {
let windowHeight = document.documentElement.clientHeight||window.innerHeight
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
let windowBottom = scrollTop + windowHeight
const selfTop = _.get(this.$refs, 'selfEcharts.offsetTop', 0);
if(windowBottom >= selfTop) {
this.isPositionReady = true
this.checkAndSetOption()
window.removeEventListener('scroll', this.scrollEvent)
}
},
- windowBottom(滑动的下边界) = scrollTop(屏幕当前滑动的距离) + windowHeight(窗口的高度)
- selfTop(当前组件的顶部位置)
当第一个变量大于第二个变量时,就认为滑动到了该图表组件,就开开启加载
3. 初始化
data() {
return {
myEcharts: null,
isOptionAbnormal: false,
randomId: 'echarts-dom' + Date.now() + Math.random(),
scrollEvent: _.throttle(this.checkPosition, 500), // 滑动事件
isPositionReady: false, //控制数据异步与页面滚动先后顺序的flag
}
},
mounted() {
let $echartsDOM = document.getElementById(this.randomId)
if(!$echartsDOM) return
this.myEcharts = echarts.init($echartsDOM)
// 第一次未滑动的时候
this.checkPosition()
//滑动之后的监听
window.addEventListener('scroll', this.scrollEvent)
},
checkPosition方法不仅要在scroll监听事件中调用,在组件第一次渲染的时候也要调用一次进行初始化,不然,组件无法正常渲染图表。
4.节流
组件代码经过测试之后发现,如果滚动过于快速,会不停的调用checkPosition(),关键是这个方法会不停的去setOption(),所以以上代码均采用了lodash的throttle节流方法,在500毫秒内只允许调用一次。
5. 解绑监听事件
解绑事件,组件中有设定监听器,如果该图表已经被加载了,那么这个监听器就没有作用了。
window.removeEventListener('scroll', this.scrollEvent)
这段代码就是解绑监听器,多多少少会优化一下速度吧。因为增加监听和解绑监听的时间函数要求一致,所以在data中新增了scrollEvent,顺便把节流函数一起加了上去。
6. 请求异步控制setOption
由于用于渲染 echarts 的数据常常是异步获取的,也就是说,option 可能会在异步调用结束之后更新,从而触发 option 的 watch,进而导致 this.checkOption() 执行,最终使得 setOption 在页面没有滚动到合适位置时就触发了。
为了解决这个问题,我们应该让 setOption 的过程受制于一个标识位,而该标识位会在页面滚动到合适位置时置为 true,从而杜绝由于 option 更新、触发 watch 而导致的漏洞。
首先,我们要添加一个新的 data,取名为为 isPositionReady,然后,在 checkAndSetOption() 中加入对该标识位的判断:最后,在位置检测方法 checkPosition() 中,当达到合适位置时,将该标识位置为 true
checkPosition() {
// ....
if(windowBottom >= selfTop) {
this.isPositionReady = true
// ....
}
},
checkAndSetOption() {
// ....
if(!this.isPositionReady) return
//....
}
四、echarts重绘
这里的重绘指的是 ehcarts 中的 resize() 方法。用于在某些时刻进行 echarts 的调整,包括:
- 组件宽度设置为百分比,浏览器宽度发生变化时;
- 页面收缩元素状态改变,如侧边栏收缩导致内容区宽度变化;
1.页面宽度改变
继续增加监听器就能完成
window.addEventListener('resize', _.throttle(() => {
this.myEcharts.resize()
console.log('---')
}, 500))