摘自杰瑞 · 赫尔曼的《你好,多莉》歌曲: 一天不学就没手感了,三天不学就不爱学了,两礼拜不学之前学的全忘了. ——B站 Zhan

js加载文件,使用echarts

内容目录
  1. 问题描述:我在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,图形也会加载出来。

解决方案:

我很困惑,这个两个应该没有区别吧,但是这样是现象发生,那我们就找找这样的区别。

  1. 当你创建一个新的 DOM 元素并将其插入到页面中时,浏览器并不会立即进行渲染。这意味着在你调用 echarts.init 时,浏览器还没有更新 DOM,导致 echarts.init 无法获取到正确的尺寸和样式信息。

  2. 浏览器渲染机制

    1. DOM 操作:当你执行 appendChild 操作时,浏览器会将这个操作记录下来,但不会立即重新绘制页面。
    2. 微任务和宏任务:浏览器在主线程中执行任务时,分为微任务和宏任务。当你使用 setTimeout(..., 0) 时,这个回调会被推到宏任务队列中,确保在当前的所有同步代码和微任务执行完毕后再执行。这给了浏览器时间来处理 DOM 的变更并更新渲染。
    3. 强制同步布局:某些 DOM 属性(如 offsetWidthgetBoundingClientRect)会触发浏览器的同步布局。通过读取这些属性,可以强制浏览器执行布局,从而确保后续代码可以基于最新的布局信息运行。
  3. 两者使用和不使用的区别就有了。(区别主要在于浏览器处理 DOM 操作和渲染更新的顺序。)

    1. 当你不使用 setTimeout 时,以下代码将同步执行。
      1. 创建并添加元素chartEcharts 元素被创建并添加到 chartsContainer 中。
      2. 立即初始化 ECharts:紧接着,echarts.init 被调用,此时浏览器可能还没有重新计算并应用新的布局和样式。因此,chartEcharts 的宽度和高度可能还没有正确应用。
    2. 当你使用 setTimeout(..., 0) 时:
      1. 创建并添加元素chartEcharts 元素被创建并添加到 chartsContainer 中。
      2. 等待下一次事件循环setTimeout(..., 0) 将代码推到事件队列的末尾,确保在当前调用栈中所有代码执行完毕之后再执行回调。这给了浏览器时间来处理 DOM 的变更,并重新计算布局和样式。
      3. 初始化 ECharts:在下一次事件循环中,浏览器已经应用了新的布局和样式,因此 chartEcharts 的宽度和高度可以正确获取到。

    根本原因

    浏览器在执行 JavaScript 时,不会立即重新计算和应用所有的 DOM 变更,而是会在当前执行上下文结束后批量处理。这种机制提高了性能,但也意味着在某些情况下,需要等待浏览器完成这些更新。

    更高效的解决方案

    使用 requestAnimationFramesetTimeout(..., 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);
           }
       }
    });
    

    解释:

    1. requestAnimationFrame:在下一次重绘之前执行回调,确保此时 DOM 已经更新。
    2. 高效:比 setTimeout(..., 0) 更高效,因为它专门设计用于处理帧更新。

    通过这种方式,你可以确保 ECharts 在正确的时间点初始化,避免因为 DOM 尚未更新导致的问题。

发表评论