作一個具備異步加載特性的 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事件監聽的方法 - 博客園
相關文章
相關標籤/搜索