echarts自适应div resize问题

2 阅读3分钟

一、问题本质:容器尺寸变化时图表未重绘

ECharts初始化时会根据容器的初始尺寸渲染图表,当容器尺寸因窗口缩放、响应式布局或DOM操作变化时,需手动触发图表重绘以适配新尺寸。否则会出现以下问题:

  • 图表内容溢出容器或留白;
  • 布局错乱(如坐标轴标签重叠);
  • 交互区域与视觉元素错位。

二、解决方案:监听容器变化并触发重绘

1. 监听窗口resize事件(基础方案)

// 初始化图表
const chart = echarts.init(document.getElementById('chart-container'));
chart.setOption(option);

// 监听窗口大小变化,调用resize方法
window.addEventListener('resize', () => {
  chart.resize();
});

// 组件销毁时移除监听(避免内存泄漏)
window.removeEventListener('resize', () => {
  chart.resize();
});
  • 适用场景:容器随窗口尺寸同步变化(如占满整个视口)。
  • 局限性:无法响应容器因DOM操作或CSS变化导致的尺寸调整(如侧边栏展开/收起)。

2. 使用ResizeObserver API(现代方案)

const chartContainer = document.getElementById('chart-container');
const chart = echarts.init(chartContainer);

// 创建ResizeObserver实例监听容器变化
const observer = new ResizeObserver(entries => {
  for (let entry of entries) {
    chart.resize();
  }
});

// 开始监听
observer.observe(chartContainer);

// 组件销毁时停止监听
observer.disconnect();
  • 优势:直接监听目标容器,精度高,性能优于轮询;
  • 兼容性:支持Chrome 64+、Firefox 69+、Safari 13.1+,需配合polyfill处理旧版浏览器。

3. 定时器轮询检测(兼容性方案)

const chartContainer = document.getElementById('chart-container');
const chart = echarts.init(chartContainer);

let lastWidth = chartContainer.clientWidth;
let lastHeight = chartContainer.clientHeight;

const checkResize = () => {
  const currentWidth = chartContainer.clientWidth;
  const currentHeight = chartContainer.clientHeight;
  
  if (currentWidth !== lastWidth || currentHeight !== lastHeight) {
    chart.resize();
    lastWidth = currentWidth;
    lastHeight = currentHeight;
  }
  
  requestAnimationFrame(checkResize); // 高效循环检测
};

checkResize();
  • 适用场景:需兼容不支持ResizeObserver的旧版浏览器;
  • 注意事项:频繁检测可能影响性能,建议配合节流函数(如lodash.throttle)。

三、性能优化与实践建议

1. 节流处理resize事件

import { throttle } from 'lodash';

window.addEventListener('resize', throttle(() => {
  chart.resize();
}, 100)); // 每100ms执行一次,避免频繁重绘

2. ECharts配置优化

// 初始化时设置renderer为canvas(性能优于SVG)
const chart = echarts.init(document.getElementById('chart-container'), null, {
  renderer: 'canvas',
  useDirtyRect: true // 启用脏矩形渲染优化
});

// 配置项中开启自适应
option = {
  responsive: true, // ECharts 5+ 支持的全局自适应配置
  // 其他配置...
};

3. 组件生命周期管理

// React组件示例
useEffect(() => {
  const chart = echarts.init(containerRef.current);
  chart.setOption(option);
  
  const handleResize = () => chart.resize();
  window.addEventListener('resize', handleResize);
  
  return () => {
    window.removeEventListener('resize', handleResize);
    chart.dispose(); // 销毁图表释放资源
  };
}, []);

四、框架集成方案

1. Vue中使用ECharts自适应

<template>
  <div ref="chartContainer" style="width: 100%; height: 400px;"></div>
</template>

<script>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';

export default {
  setup() {
    const chartContainer = ref(null);
    let chart = null;
    
    onMounted(() => {
      chart = echarts.init(chartContainer.value);
      chart.setOption(option);
      
      // 监听窗口resize
      const handleResize = () => chart.resize();
      window.addEventListener('resize', handleResize);
      
      // 监听容器自身变化(Vue 3+ 支持)
      const observer = new ResizeObserver(() => chart.resize());
      observer.observe(chartContainer.value);
      
      // 保存到组件实例以便销毁
      chartContainer.value.__resizeHandler = handleResize;
      chartContainer.value.__resizeObserver = observer;
    });
    
    onUnmounted(() => {
      if (chart) {
        chart.dispose();
      }
      
      // 移除监听
      const handler = chartContainer.value.__resizeHandler;
      const observer = chartContainer.value.__resizeObserver;
      if (handler) window.removeEventListener('resize', handler);
      if (observer) observer.disconnect();
    });
    
    return { chartContainer };
  }
};
</script>

2. React中使用ECharts自适应

import React, { useRef, useEffect } from 'react';
import * as echarts from 'echarts';

const EChartsComponent = () => {
  const chartRef = useRef(null);
  
  useEffect(() => {
    const chart = echarts.init(chartRef.current);
    chart.setOption(option);
    
    // 窗口resize监听
    const handleResize = () => chart.resize();
    window.addEventListener('resize', handleResize);
    
    // 容器resize监听
    const observer = new ResizeObserver(() => chart.resize());
    observer.observe(chartRef.current);
    
    return () => {
      window.removeEventListener('resize', handleResize);
      observer.disconnect();
      chart.dispose();
    };
  }, []);
  
  return <div ref={chartRef} style={{ width: '100%', height: '400px' }} />;
};

五、问题

1. 问:ResizeObserver与window.resize的本质区别?

    • window.resize:监听整个浏览器窗口的尺寸变化,无法感知容器因DOM操作或CSS变化导致的尺寸调整;
    • ResizeObserver:直接监听目标元素的尺寸变化,精度高,适合复杂布局场景(如侧边栏展开/收起)。

2. 问:ECharts在移动端频繁resize性能优化?

    • 降级处理:在小屏设备上简化图表复杂度(如减少数据点、关闭动画);
    • 节流防抖:使用requestAnimationFramelodash.throttle控制resize频率;
    • 离屏渲染:在resize过程中暂停图表渲染,完成后一次性更新(通过chart.clear()chart.setOption())。

3. 问:如何处理iframe内ECharts的自适应?

    • iframe内部:在iframe内监听window.resize并调用chart.resize()
    • 父页面:通过postMessage通知iframe窗口尺寸变化;
    • 第三方库:使用iframe-resizer自动同步iframe与父容器尺寸。

六、总结:解决方案选择策略

  1. 优先方案ResizeObserver + 节流处理,覆盖90%以上场景;
  2. 兼容性方案:窗口resize监听 + 定时器轮询(旧版浏览器);
  3. 框架集成:结合组件生命周期管理(如Vue的onUnmounted、React的useEffect返回清理函数);
  4. 性能优化:合理配置rendereruseDirtyRect,避免不必要的重绘。