在 vue 项目使用 echarts 的场景中,以下三点不容忽视:1. 可视化的数据往往是异步加载的;2. 若一个页面存在大量的图表( 尤其当存在关系图和地图时 ),往往会导致该页面的渲染速度很慢并可能在几秒内卡死,产生极差的用户体验。3. 引入 echarts 组件导致编译后的文件过大从而使得首次访问的加载极慢。关于第三点,大家可以参考之前的撰文 优化 Vue 项目编译文件大小。以下针对上述前两点,给出数据异步、延迟渲染的 echarts vue 组件的设计和实现方式,并对实现之中可能存在的问题进行介绍。
组件代码可以访问 Github 查看。
1. 抽离 echarts 公共部分形成基础组件
1.1 调研公共部分
首先,我们需要把 echarts 使用中公共的部分抽离出来,形成基础组件。
让我们在 官网 - 5 分钟上手 ECharts 教程中找到使用 echarts 的步骤:
# 1. 获取一个用于挂在 echarts 的 DOM 元素
let $echartsDOM = document.getElementById('echarts-dom')
# 2. 初始化
let myEcharts = echarts.init($echartsDOM)
# 3. 设置配置项
let option = {...}
# 4. 为 echarts 指定配置
myEcharts.setOption(option)
注:在 Vue 中,首先我们需要使用 import echarts from 'echarts'
以引入 echarts。
由上可知,在 echarts 使用中,除第三步设置配置项以外,其他的步骤都是重复的,即可以抽离出来放入组件中统一实现。
注:其实 option 配置中也存在可以抽离的部分,比如我们可以将 echarts 的颜色、散点大小、折线粗细等提取出来统一赋值,以保证 echarts 风格的统一。但由于不同类型的 ehcarts 图的颜色配置方式不同,因而实现起来相对繁琐,这里不进行说明,有兴趣的同学可以自行尝试。
1.2 实现 echarts 功能
首先我们书写一个简单 i-ehcart.vue
,其中,配置项直接复制于官网的教程示例。
<style scoped>
.echarts {
width: 100%;
height: 100%;
}
</style>
<template>
<div>
<div class="echarts" id="echarts-dom"></div>
</div>
</template>
<script>
import echarts from 'echarts'
export default {
name: 'echarts',
data() {
return {}
},
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)
}
}
</script>
然后在 App.vue
中引入这一组件,并设置 echarts 的宽高:
<style>
.echarts-container{
width: 100%;
height: 20rem;
}
</style>
<template>
<div id="app">
<i-echart class="echarts-container"></i-echart>
</div>
</template>
<script>
import iEchart from './components/i-echart'
export default {
name: 'app',
components: {
iEchart
}
}
</script>
刷新页面后,即可看到柱状图。
1.3 组件化
由于我们需要抽离 option 部分,最好的方式是将其作为组件的属性,即 props
交由调用方配置:
# i-echart.vue
import echarts from 'echarts'
export default {
name: 'echarts',
props: {
option: {
type: Object,
default(){
return {}
}
}
},
data() {
return {}
},
mounted() {
let $echartsDOM = document.getElementById('echarts-dom')
let myEcharts = echarts.init($echartsDOM)
let option = this.option
myEcharts.setOption(option)
}
}
1.4 调用组件
然后我们可以将 option 配置抽离到组件调用方,并通过「传参」的方式进行调用:
<i-echart :option="option" class="echarts-container"></i-echart>
1.5 提高组件强壮型
之前我们注意到,在 option 参数中,我们给出了默认值 {}
,即空对象。这样做其实是有问题的,即在 echarts 中,如果传入的 option 配置对象不含有 series
键,就会抛出错误:
Error: Option should contains series.
默认值处理是需要存在的,即当调用方传入的对象为空或不存在 series
配置时,应在页面上显示一些提示( 对用户友好的提示,而不是对编程人员 ),即避免因报错而造成空白的情况。
此外,当我们像之前那样给 option 这一参数进行类型限制后,倘若调用方传入非对象类型,Vue 会直接抛出错误——这一结果也不是我们想要的。我们应该取消类型限制,并在 option 发生变化时进行依次以下判断:
1. 是否为对象;
2. 是否为空对象;
3. 是否包含 series 键;
4. series 是否为数组;
5. series 数组是否为空。
代码实现如下:
function isValidOption(option){
return isObject(option) && !isEmptyObject(option)
&& hasSeriesKey(option)
&& isSeriesArray(option) && !isSeriesEmpty(option)
}
function isObject(option) {
return Object.prototype.isPrototypeOf(option)
}
function isEmptyObject(option){
return Object.keys(option).length === 0
}
function hasSeriesKey(option){
return !!option['series']
}
function isSeriesArray(option) {
return Array.isArray(option['series'])
}
function isSeriesEmpty(option){
return option['series'].length === 0
}
注:实际上,当判断出 option 为对象后,可以直接进行第三步的判断。
然后,当判断 option 符合上述三种情况时,在页面上显示如「数据为空」之类的提示:
import echarts from 'echarts'
export default {
name: 'echarts',
props: {
option: {
default(){
return {}
}
}
},
data() {
return {
myEcharts: null,
isOptionAbnormal: false
}
},
mounted() {
let $echartsDOM = document.getElementById('echarts-dom')
if(!$echartsDOM) return
let myEcharts = echarts.init($echartsDOM)
this.myEcharts = myEcharts
this.checkAndSetOption()
},
watch: {
option(option){
this.checkAndSetOption()
}
},
methods: {
checkAndSetOption(){
let option = this.option
if(isValidOption(option)){
this.myEcharts.setOption(option)
this.isOptionAbnormal = false
}else{
this.isOptionAbnormal = true
}
}
}
}
这里在书写代码时,有以下几点需要注意:
- 我们对 DOM 元素获取结果做了校验,即当 option 不符合要求时,ID 为 echarts-dom 的 DOM 元素是不存在的,此时
document.getElementById()
的返回结果为空,不能直接使用echarts.init()
,否则会抛出错误:Error: Initialize failed: invalid dom
; - 在 Vue 中,初始化的值不会被 watch 钩子捕捉,从而导致组件被调用方调用并赋予 option 参数时不会进入校验。虽然可以使用
immediate: true
使得watch
钩子能够在属性初始化赋值时被触发,但这样做是不合适的。因为这样设置之后,在 option 初始化从而触发 watch 时,用于挂载 echarts 的 DOM 元素还未存在于页面中,从而导致出现TypeError: Cannot read property 'setOption' of null
的错误。我们要重点注意 echarts 作用的生命周期,这一点后续还会涉及。
1.6 增强组件功能 - 数据不合法提示
从上面的代码中可以注意到,我们使用 isOptionAbnormal
标识了传入的 option
值是否符合规定。基于这一标识,我们可以对 echarts 组件进行优化,当 option 不合法或数据为空时给出提示信息而不是显示空白甚至报错。
首先,我们修改原组件 i-echart.vue
代码,增加 shadow
层:
<div>
<div class="shadow" v-if="isOptionAbnormal">
数据为空
</div>
<div class="echarts" v-if="!isOptionAbnormal" id="echarts-dom"></div>
</div>
并为其增加样式:
.shadow {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
color: #8590a6;
}
可当我们把 option 修改为 null 后,展示的样式没有按照预期。「数据为空」的字样被挤到一旁。
通过审查元素,我们猜测是由于 echarts 实例生成的 svg 并没有因为 v-if 而消失( 或是 Vue 本身的处理机制 ),而是上移到了兄弟节点。
可见我们需要在 echarts 的挂载元素之上再加一层容器 DOM:
<div>
<div class="shadow" v-if="isOptionAbnormal">
数据为空
</div>
<div class="wrap-container">
<div class="echarts" v-if="!isOptionAbnormal" id="echarts-dom"></div>
</div>
</div>
同时对样式进行修改:
.wrap-container,
.echarts {
width: 100%;
height: 100%;
}
.shadow {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
color: #8590a6;
}
这样一来,当 option 不合法时,提示文本确实会出现在合适的位置,但新的问题也出现了:当 option 值由不合法值变为合法值时,echarts 并没有被渲染。
这是由于我们在 option 检测的过程中,只是进行了 setOption
,而由于我们使用的 v-if
会在 option 不合法时直接删除 DOM 元素,使得 myEcharts
即 DOM 挂载对象消失,自然 setOption
也没有效果了。
这里有两个方案可以解决:
- 重构
checkAndSetOption()
函数,使其能够在 option 改变检测时,对页面中是否存在挂载元素也进行检测,当不存在时,重新进行echarts.init()
并赋值myEcharts
。即考虑到 option 由「合法到合法」的改变,与「非法到合法」的改变是不同的这一情况; - 将
v-if
改变为v-show
,并将 echarts 挂载元素与提示信息框的布局改为 absolute。
就二者而言,后者显然更易操作,也是我们所采取的方法。
首先,我们把 v-if 修改为 v-show,并为根元素添加类以用于调节样式:
<div class="main-container">
<div class="shadow" v-show="isOptionAbnormal">
数据为空
</div>
<div class="wrap-container" v-show="!isOptionAbnormal">
<div class="echarts" id="echarts-dom"></div>
</div>
</div>
然后进行样式调整:
.main-container{
position: relative;
}
.wrap-container,
.shadow{
position: absolute;
}
.wrap-container,
.echarts {
width: 100%;
height: 100%;
}
.shadow {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
color: #8590a6;
}
然后,我们再将 option 由不合法到合法进行修改时,便不会出现无法渲染的情况了。
1.7 增强组件功能 - 数据加载提示
在实际场景中,用于渲染的数据常常是异步获取的,在异步加载数据之中,我们可能需要在页面中显示如「正在加载...」的字样来表示加载过程正在进行以提高用户体验。而加载过程就组件而言是无法直接获取的,即需要组件调用方通过某种方式进行控制。
所以,我们需要使用某一参数用于进行加载信息的显示。与之前不合法提示信息的操作方式相同,我们使用绝对定位的元素和 isLoading
属性进行处理:
首先,我们添加 isLoading 属性:
props: {
option: {
default() {
return {}
}
},
isLoading: {
type: Boolean,
default: false
}
},
然后修改 HTML 代码:
<div class="main-container">
<div class="loading" v-show="isLoading">
数据加载中...
</div>
<div class="shadow" v-show="!isLoading && isOptionAbnormal">
数据为空
</div>
<div class="wrap-container" v-show="!isLoading && !isOptionAbnormal">
<div class="echarts" id="echarts-dom"></div>
</div>
</div>
并修改样式:
.main-container{
position: relative;
}
.wrap-container,
.loading,
.shadow{
position: absolute;
}
.wrap-container,
.echarts {
width: 100%;
height: 100%;
}
.shadow,
.loading{
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 1rem;
color: #8590a6;
}
然后,我们便可以在组件调用方中,使用 is-loading
来控制了:
<i-echart :option="option" :is-loading="true" class="echarts-container"></i-echart>
1.8 组件复用问题
组件的最大用处是复用,但当我们将之前写的组件进行复用时,会发现出现了问题:
<i-echart :option="option" class="echarts-container"></i-echart>
<i-echart :option="option" class="echarts-container"></i-echart>
此时,我们发现页面中并没有出现两个 echarts 图,而是只有第一个。通过浏览器审查元素,我们可以发现,只有第一个组件被正确地挂载了。这是为什么呢?
这是因为 echarts 进行 init 挂载时使用的是 DOM 元素的 ID。而在组件中,我们设置的 ID 是固定的( 注意与 scoped css 进行区分 )。即多个组件的 ID 是相同的,故而只有一个组件会被 echarts 挂载。
那么该如何解决这个问题呢?方法也很简单,只要保持每个元素获得唯一的 ID 就可以了。而对于唯一 ID,我们可以通过时间戳和随机数来实现。
修改组件代码,为组件挂载的 DOM 设置随机的 ID:
首先,我们设置一个随机 ID:
data() {
return {
randomId: 'echarts-dom' + Date.now() + Math.random()
}
},
并将其 echarts 元素的 ID 修改为该值:
<div class="echarts" :id="randomId"></div>
然后将 mounted 生命周期中的 DOM 组件 ID 修改为我们随机生成的值:
mounted() {
let $echartsDOM = document.getElementById(this.randomId)
...
}
此时,我们才真正完成了基础组件的构建。
2. 延迟加载
这里指的延迟加载,是 echarts 的渲染只在页面滚动到特定高度的时候才会进行。
由于 echarts 组件渲染需要性能( 尤其是地图、关系图 ),对于存在大量 echarts 的页面,如果在页面加载时全部进行渲染,可能会导致页面卡顿而降低用户体验。因而,我们需要对 echarts 进行按需加载。
完成这一功能需要以下步骤:
- 监听页面滚动事件;
- 滚动事件中获取 echarts 的位置;
- 在页面当前位置达到 echarts 位置的时候进行 echarts 的初始化。
下面我们就逐步完成这些功能。在此之前,我们需要添加一个高度足够的占位 DOM,以检测效果:
<div style="height: 50rem;"></div>
2.1 监听页面滚动
我们可以使用 window.onscroll = function(){}
来监听页面的滚动,但这种方式只能同时作用于一个组件。想要在所有组件中生效,我们需要使用 window.addEventListener('scroll', function(){})
。注意,绑定的生命周期为 mounted
:
mounted: {
window.addEventListener('scroll', () => {
console.log(this.randomId)
})
...
}
注意,这里使用了箭头函数以维持 this
的指向。
接下来,我们要使用以下方法获取浏览器下边界的绝对位置,用以与之后 DOM 元素的上边界进行对比以判断当前是否应该进行渲染:
window.addEventListener('scroll', () => {
let windowHeight = document.documentElement.clientHeight||window.innerHeight
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
let windowBottom = +scrollTop + +windowHeight
console.log(windowBottom)
})
2.2 获取组件当前位置
接下来要获取组件的位置。在这之前,我们要首先解决获取组件 DOM 元素的问题,这里有两种方式:
- 借助 ID,通过
document.getElementById()
获取; - 采用 Vue 中的
$ref
获取。
这里我们使用第二种方式。
首先,我们在组件上加入 ref 属性:
<div class="main-container" ref="selfEcharts">
...
</div>
然后,通过以下方式,获取组件本身:
this.$refs.selfEcharts
可以看到,与 ID 不同,ref 是组件内唯一的( 而不是全局唯一 )。
之后,我们通过以下方式获取组件的上边缘位置:
this.$refs.selfEcharts.offsetTop
注:这里也可以使用 lodash
的 _.get()
来获取 offset
值,以避免 Cannot read property of undefined
的错误:
_.get(this.$refs, 'selfEcharts.offsetTop', 0)
2.3 控制 setOption 时机
基于以上代码,我们可以通过对比浏览器下边缘及组件的位置,从而控制 setOption 的时机,以达到延迟加载的效果。
我们把之前的 this.checkAndSetOption()
放入高度判断中:
window.addEventListener('scroll', () => {
...
if(windowBottom >= selfTop){
this.checkAndSetOption()
}
})
注:为了更明显地检测效果,我们可以在 checkAndSetOption()
上加上 setTimeout
。
2.4 功能优化
大家可以注意到,以上代码存在两个可以优化的部分:
- 窗口滚动的检测频率过高,当存在多个 echarts 时,可能造成性能消耗;
- 当窗口滚动到合适位置触发渲染后,滚动检测对于该组件而言就没有意义了,这时应该将该事件解除绑定。
2.4.1 使用 throttle 控制触发频率
这里我们引入 lodash,并使用 throttle 来控制滚动监测的触发频率:
首先引入 lodash:
import _ from 'lodash'
然后限制触发间隔为 500 ms:
window.addEventListener('scroll', _.throttle(() => {
let windowHeight = document.documentElement.clientHeight||window.innerHeight
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
let windowBottom = +scrollTop + +windowHeight
let selfTop = _.get(this.$refs, 'selfEcharts.offsetTop', 0)
if(windowBottom >= selfTop){
this.checkAndSetOption()
}
}, 500))
2.4.2 解绑事件
若想用 document.removeEventListener()
解绑事件,首先我们要抽离事件本身,将匿名函数转为实名函数。
首先,我们要将检测事件提取到 methods
之中:
methods: {
checkAndSetOption() {
let option = this.option
if (isValidOption(option)) {
this.myEcharts.setOption(option)
this.isOptionAbnormal = false
} else {
this.isOptionAbnormal = true
}
}
}
为了保证 addEventListener 和 removeEventListener 时操作的是同一个函数,这里我们使用 data
添加实名函数:
data() {
return {
scrollEvent: _.throttle(this.checkPosition, 500)
}
}
然后在事件绑定中使用这一实名函数:
window.addEventListener('scroll', this.scrollEvent)
之后在检测到窗口滚动到合适高度的时候进行事件解绑:
checkPosition() {
...
if (windowBottom >= selfTop) {
this.checkAndSetOption()
window.removeEventListener('scroll', this.scrollEvent)
}
},
2.4 数据异步与页面滚动先后顺序的问题
当我们回顾自己的代码,可以发现,在实际应用中,其实是存在问题的。
由于用于渲染 echarts 的数据常常是异步获取的,也就是说,option 可能会在异步调用结束之后更新,从而触发 option 的 watch,进而导致 this.checkOption()
执行,最终使得 setOption 在页面没有滚动到合适位置时就触发了。
为了解决这个问题,我们应该让 setOption
的过程受制于一个标识位,而该标识位会在页面滚动到合适位置时置为 true,从而杜绝由于 option 更新、触发 watch 而导致的漏洞。
首先,我们要添加一个新的 data,取名为为 isPositionReady
:
data: {
...
isPositionReady: false
}
然后,在 checkAndSetOption()
中加入对该标识位的判断:
checkAndSetOption() {
...
if(this.isPositionReady !== true) return
...
}
最后,在位置检测方法 checkPosition()
中,当达到合适位置时,将该标识位置为 true:
checkPosition() {
...
if (windowBottom >= selfTop) {
this.isPositionReady = true
...
}
}
此时,以上漏洞就被修补了。
2.5 初始化检测
事实上,以上组件中还有一个漏洞,让我们改变组件调用方的代码来发现它:
<div id="app">
<i-echart :option="option" class="echarts-container"></i-echart>
<div style="height: 50rem;"></div>
<i-echart :option="option" class="echarts-container"></i-echart>
<i-echart :option="option" class="echarts-container"></i-echart>
</div>
刷新页面,我们发现原本应该渲染的第一个 echarts 组件并没有展示出来。也就是说,通过我们之前的代码,所有 echarts 组件的渲染都必须由页面滚动事件触发。
而对于那些原本就处于页面靠上位置的组件而言,理应在页面加载后就立刻渲染而无需等待滚动。修补这个问题也很简单,只要在 mounted
生命周期中进行一次 checkPosition
检测即可:
mounted() {
...
this.checkPosition()
...
}
自此,一个具有延迟加载功能的 echarts 组件就完成了。接下来,我们需要对该组件进行进一步优化,以适应更多的场景需求。
3. echarts 重绘
这里的重绘指的是 ehcarts 中的 resize()
方法。用于在某些时刻进行 echarts 的调整,包括:
- 组件宽度设置为百分比,浏览器宽度发生变化时;
- 页面收缩元素状态改变,如侧边栏收缩导致内容区宽度变化;
3.1 页面宽度改变事件
echarts 并不会主动地随着浏览器宽度的改变而调整,需要我们在页面改变时间中主动触发。实现的方式也很简单,只要按照之前的思路监听 window resize
事件即可。( 注意,这里同样要考虑控制监听频率的问题 ):
window.addEventListener('resize', _.throttle(() => {
this.myEcharts.resize()
console.log('---')
}, 500))
3.2 主动重绘
对于一些场景,如含有侧边栏的页面而言,侧边栏收缩时,也需要对 echarts 进行 resize
调整。而此时,浏览器宽高通常是不会变化的。
因而我们需要有一个机制,能够让组件调用方主动触发以使组件进行 resize
。由于当前版本的 Vue 是不能直接调用组件的方法的,想要做到这一点,我们可以使用以下两种方法:
- 使用时间戳;
- 使用随机数
采用时间戳或随机数赋值组件的属性,在组件调用方检测到侧边栏一类组件状态改变等需要 echarts 组件主动触发 resize
时,重新生成随机数或重新获取时间戳。而在组件中,对属性的变化进行检测,即当属性变化时,执行 resize
:
添加用于触发主动重绘的属性:
props: {
resizeSignature: {
default: ''
}
}
添加对该属性的监听,并在变化时执行 resize
:
resizeSignature(){
this.myEcharts.resize()
}
此时,只要在调用方改变 resize-signature
即可使 echarts 主动调用 resize
。
4. echarts 点击事件回调
在一些场景中,我们可能需要对 echarts 的点击事件进行捕捉以进行下一步的处理( 如:数据下钻 )。
为了支持这一类场景,我们需要为 echarts 添加点击监听事件,并将该事件及其参数上抛至组件调用方。
绑定 echarts 点击事件:
mounted () {
...
let myEcharts = echarts.init($echartsDOM)
myEcharts.on('click', params => {
this.echartsClicked(params)
})
...
}
向上抛出事件及其参数:
methods: {
echartsClicked(params) {
this.$emit('echarts-clicked', params)
}
}
在组件调用方捕捉该事件和参数:
<i-echart :option="option" @echarts-clicked="echartsClicked" class="echarts-container"></i-echart>
methods:{
echartsClicked(params){
console.log(params)
}
}
X. 后续
X.1 堆叠图问题
对于 echarts 中使用 stack
配置的堆叠图,在堆叠图来回转换中,可能出现样式错误的问题,这是由于使用 setOption(option)
时只会更新相较之前 option 不同的部分。解决方法是:
echarts.setOption(option)
// 修改为:
echarts.setOption(option, true)
详情可参考:Github Issue:请问一个柱状图叠加数据刷新问题。
X.2 地图问题
在 echarts 中,对地图的使用还是比较频繁的。使用地图时,使用地图的 Json 数据进行注册时比较合适的方式。为此,组件中提供了 maps
属性,用于地图数据的注册,如:
<i-echart :option="option" :maps="maps"></i-echart>
<script>
...
// 'echarts/map/json/china.json'
let maps = [
{
name: 'china',
data: chinaJson
},
...
]
...
</script>
X.3 v-show 问题
在 Vue 中,v-show
使用 display
控制组件的显隐。而当 echart init 的时候,如果其挂载 DOM 的 v-show 处于 false 状态,则其 init 的对象宽高都是 0。即使之后 v-show 状态改变,由于 mounted
生命周期不会再次触发,从而使得 echarts 显示不正常。
为此,我们需要将 v-show 修改为对 visibility
这一 CSS 的改变:
:style="{visibility: isChartVisible ? 'visible' : 'hidden'}"
...
computed: {
isChartVisible(){
return !this.isLoading && !this.isOptionAbnormal
}
}
X.4 滚动事件在 overflow:xxx 中无法被监听的问题
当我们通过对某一组件设置 overflow 使得页面整体高度小于等于屏幕高度时,对 window 绑定的滚动事件就失效了:
<div id="#app" style="width:100%; height:100%; overflow:auto">
<div id="scroll" style="width:100%; height:100rem;"></div>
</div>
如上,此时 window 及其至 div#app 的子元素都是不会发生 scroll 事件的。如果我们想要监听滚动事件,只能将其绑定在 div#scroll 元素上:
document.querySelector('#scroll').addEventListener('scroll', function(){})
这也就意味着,对于这种场景,如果在 #scroll 中放置了许多我们之前完成的 vue-echarts 组件,由于无法正常监听滚动事件,那些不在首屏显现的图表之后也不能正常显示。
为了解决这一问题,我们需要为组件增加一个参数,使得我们可以传入能够被监听滚动事件的元素 ID,以便延迟加载效果正常起效:
/**
* 用于绑定滚动监听的 DOM 元素的 ID 值,不传递时会使用 window
*/
scrollDomId: {
default: null
}
然后我们需要改动三个地方:
首先,我们需要获取应该被监听滚动事件的元素:
computed: {
/**
* 获取可滚动的 DOM 元素
* @returns {Window}
*/
onScrollDOM () {
let scrollDom = window
if (this.scrollDomId !== null) {
let tempDom = document.querySelector('#' + this.scrollDomId)
if (tempDom !== null) {
scrollDom = tempDom
}
}
return scrollDom
},
...
}
修改滚动监听的绑定:
/**
* 对滚动事件进行监控
*/
this.onScrollDOM.addEventListener('scroll', this.scrollEvent)
修改位置检测中 scrollTop
值的获取逻辑:
checkPosition () {
...
let scrollTop = this.onScrollDOM.scrollTop || document.documentElement.scrollTop || document.body.scrollTop
...
},
参考
- js如何判断一个变量等于空 - segmentfault
- vue中如何首次赋值不触发watch? - segmentfault
- echart 注意事项-初始化和销毁 - segmentfault
- echarts 官方教程
- 在 vue 中获取 dom 元素 - CSDN
- 用Javascript获取页面元素的位置 - 阮一峰的网络日志
- JS添加事件和解绑事件:addEventListener()与removeEventListener() - CSDN
- addEventListener的第三个参数
- js网页滚动条滚动事件实例分析
- Vue2 window addEventListener scroll doesnt fire? - stackoverflow
- Overflow:scroll not working - stackoverflow
- onscroll事件没有响应的原因以及vue.js中添加onscroll事件监听的方法 - 博客园