背景:最近在开发基于Echarts的业务库,使用Vue.js进行开发的,为了方便使用,就把Echarts的初始化操作放在了组件的mounted钩子里,正常项目用的没啥问题,但是如果在使用乾坤微前端框架的时候,从子应用跳走再跳回来,就会警告说需要设置渲染面板的大小,需要注意的是,我都设置的百分比大小。百思不得其解,直到我遇到了MutationObserver和ResizeObserver
原因分析
绞尽脑汁,仅排查出直接原因,对于为啥造成这个直接原因,在此向网友求助,本人至今尚未研究透彻。
说一下直接原因,因为我在设置画板宽高的时候采用的是百分比的形式,但是真正到浏览器中进行显示的时候,他是需要实实在在的px数值的,所以当从主系统跳回子系统时,浏览器首先读到的是百分比数值,所以他一时无法渲染,他需要等到父组件的宽高确定了,再来计算画板的大小。而在这个过程中,图表组件的mounted已经执行,也就是说宽高还没确定就进行初始化了,所以造成了报错。
至于为何mounted后还没计算出来,这方面的知识稍有欠缺,还没研究透彻,期望能有高人指点。
至于这个报错的解决方案,我们可以分析,我们需要的是一个监听,监听啥时候画板的大小有了宽高,再去渲染图表,应该就能解决问题,于是就开始了下面两个API的调研。
API介绍
-
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()
看起来能够解决我们的问题,其实不然,有个需要注意的地方,这个接口他的作用是监听"修改",要有修改属性这个动作才能触发,更直白点的理解,要有赋值语句产生的变更才能触发这个回调。
再回到我们的需求,我们已经配置好宽高了,只是浏览器还没有做好准备,没有加载好,实际上属性从来没有发生过变化,所以并没有触发"修改"动作,自然也没有触发回调。实践后的现象也验证了这个分析。
-
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方法进行消除。