为什么Echarts设置百分比宽高有时会报错?

134 阅读4分钟

背景:最近在开发基于Echarts的业务库,使用Vue.js进行开发的,为了方便使用,就把Echarts的初始化操作放在了组件的mounted钩子里,正常项目用的没啥问题,但是如果在使用乾坤微前端框架的时候,从子应用跳走再跳回来,就会警告说需要设置渲染面板的大小,需要注意的是,我都设置的百分比大小。百思不得其解,直到我遇到了MutationObserver和ResizeObserver

原因分析

绞尽脑汁,仅排查出直接原因,对于为啥造成这个直接原因,在此向网友求助,本人至今尚未研究透彻。

说一下直接原因,因为我在设置画板宽高的时候采用的是百分比的形式,但是真正到浏览器中进行显示的时候,他是需要实实在在的px数值的,所以当从主系统跳回子系统时,浏览器首先读到的是百分比数值,所以他一时无法渲染,他需要等到父组件的宽高确定了,再来计算画板的大小。而在这个过程中,图表组件的mounted已经执行,也就是说宽高还没确定就进行初始化了,所以造成了报错。

至于为何mounted后还没计算出来,这方面的知识稍有欠缺,还没研究透彻,期望能有高人指点。

至于这个报错的解决方案,我们可以分析,我们需要的是一个监听,监听啥时候画板的大小有了宽高,再去渲染图表,应该就能解决问题,于是就开始了下面两个API的调研。

API介绍

  1. MutationObserver

    为啥先看这个呢,因为我第一时间想到的就是他,于是先研究了一下。

    MutationObserver接口,可以在DOM被修改时异步执行回调,他可以观察整个或者这一部分DOM树的变动,也可以观察具体某个元素的属性变化。这里我们看下基本用法,详细用法参考MDN

    // 创建监听器实例,并提供一个监听到变化后需要触发的回调
    let observer = new MutataionObserver(() => console.log('something changed'))
    // 与DOM绑定,并指定监听类型,开始监听
    observer.observe(document.body, { attributes: true })
    document.body.className = "changeClass"
    ...
    // 结束监听
    observer.disconnect()
    

    看起来能够解决我们的问题,其实不然,有个需要注意的地方,这个接口他的作用是监听"修改",要有修改属性这个动作才能触发,更直白点的理解,要有赋值语句产生的变更才能触发这个回调。

    再回到我们的需求,我们已经配置好宽高了,只是浏览器还没有做好准备,没有加载好,实际上属性从来没有发生过变化,所以并没有触发"修改"动作,自然也没有触发回调。实践后的现象也验证了这个分析。

  2. ResizeObserver 再来看下这个API,他解决了这个问题。

    ResizeObserver接口的作用是监听盒模型边界尺寸的变化,注意这里的"变化",渲染阶段最初没有设置css的时候,盒模型的宽高是0,只有在加上css设置,百分比计算完并且配置上了,这时盒模型的大小发生了变化,变成了我们想要的大小。这就完美的解决了我们的问题,当盒模型边界发生变化后,触发回调,在回调中初始化画布即可。

    下面看下基础用法,详细用法请参考MDN

    let isShow = false
    let initChart = null
    // 创建一个 ResizeObserver 实例
    const targetElement = document.querySelector("#customdiv")
    const resizeObserver = new ResizeObserver(entries => {
        window.requestAnimationFrame(() => {
            // 一个ResizeObserver对象可以观察多个元素,所以返回来的是个数组
            for (const entry of entries) {
                // 监测到宽度变化后执行相关逻辑
                if (entry.contentRect.width > 0 && entry.contentRect.height > 0) {
                    // 检测到大于0的宽高说明百分比已生效,渲染一次即可
                    if (isShow === true) {
                        resizeObserver.disconnect()
                        return
                    }
                    isShow = true
                    initChart = echarts.init(document.getElementById("containerId"))
                    initChart.setOption(customOption)
                }
            }
        })
    })
    // 开始监测目标元素的宽度变化
    resizeObserver.observe(targetElement)
    ...
    // 记得销毁监听
    resizeObserver.disconnect()
    

总结

MutationObserver和ResizeObserver都是用来监听元素的接口,但是MutationObserver监听的是"修改"动作,而ResizeObserver监听的是"变化",两者均能同时监听多个元素,需要注意的是,监听方法不会自动进行垃圾回收,需要及时手动调用disconnect方法进行消除。