-
问题描述:我在js文件的刚开始的位置,引入了echarts.js文件。我是想要在后期请求回来返回的echarts的options数据获取后,进行echarts初始化和选项配置。此时我新建一个DOM元素,并且给这个DOM元素绑定了一个类名,使用JSON.parse(options)将json数据转化为对象后,赋值给这个DOM的配置项中。此时发生了一个问题。具体代码如下:
if (chart.echarts_options) { try { // setTimeout(()=>{ const chartEcharts = document.createElement('div'); chartEcharts.className = 'charts'; chartsContainer.appendChild(chartEcharts); const option = JSON.parse(chart.echarts_options); const myChart = echarts.init(chartEcharts); myChart.setOption(option); console.log(888, echarts); // },0) } catch (e) { console.error("Error initializing ECharts:", e); } }
1.当我注释setTimeout的时候,我的echarts图形加载不出来,只剩下那个DOM元素的背景。
2.当我添加setTimeout后,发现既是是0,图形也会加载出来。
解决方案:
我很困惑,这个两个应该没有区别吧,但是这样是现象发生,那我们就找找这样的区别。
-
当你创建一个新的 DOM 元素并将其插入到页面中时,浏览器并不会立即进行渲染。这意味着在你调用
echarts.init
时,浏览器还没有更新 DOM,导致echarts.init
无法获取到正确的尺寸和样式信息。 -
浏览器渲染机制
- DOM 操作:当你执行
appendChild
操作时,浏览器会将这个操作记录下来,但不会立即重新绘制页面。 - 微任务和宏任务:浏览器在主线程中执行任务时,分为微任务和宏任务。当你使用
setTimeout(..., 0)
时,这个回调会被推到宏任务队列中,确保在当前的所有同步代码和微任务执行完毕后再执行。这给了浏览器时间来处理 DOM 的变更并更新渲染。 - 强制同步布局:某些 DOM 属性(如
offsetWidth
或getBoundingClientRect
)会触发浏览器的同步布局。通过读取这些属性,可以强制浏览器执行布局,从而确保后续代码可以基于最新的布局信息运行。
- DOM 操作:当你执行
-
两者使用和不使用的区别就有了。(区别主要在于浏览器处理 DOM 操作和渲染更新的顺序。)
- 当你不使用
setTimeout
时,以下代码将同步执行。- 创建并添加元素:
chartEcharts
元素被创建并添加到chartsContainer
中。 - 立即初始化 ECharts:紧接着,
echarts.init
被调用,此时浏览器可能还没有重新计算并应用新的布局和样式。因此,chartEcharts
的宽度和高度可能还没有正确应用。
- 创建并添加元素:
- 当你使用
setTimeout(..., 0)
时:- 创建并添加元素:
chartEcharts
元素被创建并添加到chartsContainer
中。 - 等待下一次事件循环:
setTimeout(..., 0)
将代码推到事件队列的末尾,确保在当前调用栈中所有代码执行完毕之后再执行回调。这给了浏览器时间来处理 DOM 的变更,并重新计算布局和样式。 - 初始化 ECharts:在下一次事件循环中,浏览器已经应用了新的布局和样式,因此
chartEcharts
的宽度和高度可以正确获取到。
- 创建并添加元素:
根本原因
浏览器在执行 JavaScript 时,不会立即重新计算和应用所有的 DOM 变更,而是会在当前执行上下文结束后批量处理。这种机制提高了性能,但也意味着在某些情况下,需要等待浏览器完成这些更新。
更高效的解决方案
使用
requestAnimationFrame
比setTimeout(..., 0)
更加高效和可靠,因为它会在下一次重绘之前执行回调,确保 DOM 已经更新。document.addEventListener('DOMContentLoaded', function() { const chart = { echarts_options: `{ "backgroundColor": "#FFFFFF", "animation": true, "title": { "text": "ECharts 示例图表" }, "tooltip": { "trigger": "axis" }, "xAxis": { "type": "category", "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] }, "yAxis": { "type": "value" }, "series": [{ "name": "示例数据", "type": "line", "data": [820, 932, 901, 934, 1290, 1330, 1320] }] }` }; if (chart.echarts_options) { try { const chartsContainer = document.getElementById('chartsContainer'); const chartEcharts = document.createElement('div'); chartEcharts.className = 'charts'; chartsContainer.appendChild(chartEcharts); // 使用 requestAnimationFrame 确保 DOM 已更新 requestAnimationFrame(() => { const option = JSON.parse(chart.echarts_options); const myChart = echarts.init(chartEcharts); myChart.setOption(option); }); } catch (e) { console.error("Error initializing ECharts:", e); } } });
解释:
requestAnimationFrame
:在下一次重绘之前执行回调,确保此时 DOM 已经更新。- 高效:比
setTimeout(..., 0)
更高效,因为它专门设计用于处理帧更新。
通过这种方式,你可以确保 ECharts 在正确的时间点初始化,避免因为 DOM 尚未更新导致的问题。
- 当你不使用