作一个具备异步加载特性的 echarts-vue 组件

在 vue 项目使用 echarts 的场景中,如下三点不容忽视:1. 可视化的数据每每是异步加载的;2. 若一个页面存在大量的图表( 尤为当存在关系图和地图时 ),每每会致使该页面的渲染速度很慢并可能在几秒内卡死,产生极差的用户体验。3. 引入 echarts 组件致使编译后的文件过大从而使得首次访问的加载极慢。关于第三点,你们能够参考以前的撰文 优化 Vue 项目编译文件大小。如下针对上述前两点,给出数据异步、延迟渲染的 echarts vue 组件的设计和实现方式,并对实现之中可能存在的问题进行介绍。
组件代码能够访问 Github 查看。

1. 抽离 echarts 公共部分造成基础组件

1.1 调研公共部分

首先,咱们须要把 echarts 使用中公共的部分抽离出来,造成基础组件。javascript

让咱们在 官网 - 5 分钟上手 ECharts 教程中找到使用 echarts 的步骤:css

# 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。html

由上可知,在 echarts 使用中,除第三步设置配置项之外,其余的步骤都是重复的,便可以抽离出来放入组件中统一实现。vue

注:其实 option 配置中也存在能够抽离的部分,好比咱们能够将 echarts 的颜色、散点大小、折线粗细等提取出来统一赋值,以保证 echarts 风格的统一。但因为不一样类型的 ehcarts 图的颜色配置方式不一样,于是实现起来相对繁琐,这里不进行说明,有兴趣的同窗能够自行尝试。java

1.2 实现 echarts 功能

首先咱们书写一个简单 i-ehcart.vue,其中,配置项直接复制于官网的教程示例。git

<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 的宽高:github

<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 交由调用方配置:json

# 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 配置抽离到组件调用方,并经过「传参」的方式进行调用:segmentfault

<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
            }
        }
    }
}

这里在书写代码时,有如下几点须要注意:

  1. 咱们对 DOM 元素获取结果作了校验,即当 option 不符合要求时,ID 为 echarts-dom 的 DOM 元素是不存在的,此时 document.getElementById() 的返回结果为空,不能直接使用 echarts.init(),不然会抛出错误:Error: Initialize failed: invalid dom
  2. 在 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 也没有效果了。

这里有两个方案能够解决:

  1. 重构 checkAndSetOption() 函数,使其可以在 option 改变检测时,对页面中是否存在挂载元素也进行检测,当不存在时,从新进行 echarts.init() 并赋值 myEcharts。即考虑到 option 由「合法到合法」的改变,与「非法到合法」的改变是不一样的这一状况;
  2. 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 进行按需加载。

完成这一功能须要如下步骤:

  1. 监听页面滚动事件;
  2. 滚动事件中获取 echarts 的位置;
  3. 在页面当前位置达到 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 元素的问题,这里有两种方式:

  1. 借助 ID,经过 document.getElementById() 获取;
  2. 采用 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 功能优化

你们能够注意到,以上代码存在两个能够优化的部分:

  1. 窗口滚动的检测频率太高,当存在多个 echarts 时,可能形成性能消耗;
  2. 当窗口滚动到合适位置触发渲染后,滚动检测对于该组件而言就没有意义了,这时应该将该事件解除绑定。

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 的调整,包括:

  1. 组件宽度设置为百分比,浏览器宽度发生变化时;
  2. 页面收缩元素状态改变,如侧边栏收缩致使内容区宽度变化;

3.1 页面宽度改变事件

echarts 并不会主动地随着浏览器宽度的改变而调整,须要咱们在页面改变时间中主动触发。实现的方式也很简单,只要按照以前的思路监听 window resize 事件便可。( 注意,这里一样要考虑控制监听频率的问题 ):

window.addEventListener('resize', _.throttle(() => {
    this.myEcharts.resize()
    console.log('---')
}, 500))

3.2 主动重绘

对于一些场景,如含有侧边栏的页面而言,侧边栏收缩时,也须要对 echarts 进行 resize 调整。而此时,浏览器宽高一般是不会变化的。

于是咱们须要有一个机制,可以让组件调用方主动触发以使组件进行 resize。因为当前版本的 Vue 是不能直接调用组件的方法的,想要作到这一点,咱们可使用如下两种方法:

  1. 使用时间戳;
  2. 使用随机数

采用时间戳或随机数赋值组件的属性,在组件调用方检测到侧边栏一类组件状态改变等须要 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
    
    ...
},

参考

  1. js如何判断一个变量等于空 - segmentfault
  2. vue中如何首次赋值不触发watch? - segmentfault
  3. echart 注意事项-初始化和销毁 - segmentfault
  4. echarts 官方教程
  5. 在 vue 中获取 dom 元素 - CSDN
  6. 用Javascript获取页面元素的位置 - 阮一峰的网络日志
  7. JS添加事件和解绑事件:addEventListener()与removeEventListener() - CSDN
  8. addEventListener的第三个参数
  9. js网页滚动条滚动事件实例分析
  10. Vue2 window addEventListener scroll doesnt fire? - stackoverflow
  11. Overflow:scroll not working - stackoverflow
  12. onscroll事件没有响应的缘由以及vue.js中添加onscroll事件监听的方法 - 博客园
相关文章
相关标签/搜索