说实话,之前我也被Echarts在手机上的表现气够呛。大屏上图表精致得像艺术品,一到手机屏幕上不是挤成一团看不清坐标轴,就是字太小像天书,甚至有时候会直接乱码或者空白一片。那种感觉,就像精心准备了一份PPT,结果投影幕布是个放大镜。
其实问题往往不在Echarts本身,而在我们怎么让它“呼吸”。今天我就把这套用flex布局配合resize监听让图表自适应的实战方案掰开揉碎讲给你听,保证你的数据在手机和平板上都能清晰、优雅地展示。
先搞清楚:为什么图表在手机上看乱或看不清?
大多数情况下,Echarts图表渲染依赖一个固定尺寸的DOM容器。如果你给容器写死了宽度(比如width: 800px),而在手机竖屏上屏幕只有375px宽,图表不会自动收缩,要么溢出屏幕右侧,要么被浏览器强制压缩导致文字重叠、线条乱飞。
更坑的是,Echarts初始化时如果没有明确指定容器大小,它可能取到0,或者取到一个不准确的值,导致后续渲染异常。有些开发者会直接在setOption后调用resize(),但这只是一次性的,当用户旋转屏幕、或者在平板上分屏浏览时,图表又废了。
所以,核心思路是:让图表容器随屏幕/父容器变化,并实时通知Echarts重新计算尺寸。
第一步:用Flexbox打造灵活的图表容器
别再给图表容器写死像素了。试试用flex布局,让它自适应可用空间。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自适应Echarts图表</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f5f7fa;
padding: 16px;
}
/* 关键:用flex布局包裹图表容器 */
.chart-wrapper {
display: flex;
flex-direction: column;
width: 100%;
/* 在手机上,给一个合理的最小高度,避免图表被压扁 */
min-height: 300px;
/* 在平板或更大屏幕,可以动态调整高度 */
height: 60vh;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
background: #fff;
overflow: hidden;
}
.chart-title {
padding: 16px 20px 8px;
font-size: 18px;
font-weight: 600;
color: #333;
}
/* 图表DOM容器,不写死宽高,让flex控制 */
#mainChart {
flex: 1;
width: 100%;
/* 确保至少有一定的可视高度,避免0高度问题 */
min-height: 200px;
}
</style>
</head>
<body>
<div class="chart-wrapper">
<div class="chart-title">月度销售趋势(自适应手机/平板)</div>
<div id="mainChart"></div>
</div>
<script>
// 初始化图表
const chartDom = document.getElementById('mainChart');
const myChart = echarts.init(chartDom);
// 配置项
const option = {
tooltip: {
trigger: 'axis',
// 手机端tooltip字体调大一点,更清晰
textStyle: {
fontSize: 14
}
},
legend: {
data: ['销售额', '利润'],
// 小屏幕下legend位置可以调整
top: 10
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true // 让标签不溢出
},
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
// 手机屏幕窄,X轴标签可能重叠,旋转一下更清晰
axisLabel: {
rotate: 30,
fontSize: 12
}
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value}',
fontSize: 12
}
},
series: [
{
name: '销售额',
type: 'line',
smooth: true,
data: [12000, 13200, 10100, 13400, 9000, 23000, 21000, 18000, 16000, 19000, 22000, 25000],
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(54, 162, 235, 0.5)' },
{ offset: 1, color: 'rgba(54, 162, 235, 0.05)' }
])
},
lineStyle: {
width: 3
}
},
{
name: '利润',
type: 'line',
smooth: true,
data: [5000, 6200, 4100, 7400, 3000, 12000, 10000, 9000, 8000, 11000, 13000, 15000],
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 99, 132, 0.5)' },
{ offset: 1, color: 'rgba(255, 99, 132, 0.05)' }
])
},
lineStyle: {
width: 3
}
}
]
};
myChart.setOption(option);
// 接下来,让图表跟随容器大小变化...
</script>
</body>
</html>
你看,这里关键有两点:一是.chart-wrapper用了flex-direction: column,标题和图表区域自动排列;二是#mainChart用flex: 1填满剩余空间,同时设置了min-height: 200px防止被压缩到看不见。这样,无论在手机竖屏、横屏,还是平板上,图表容器都能动态调整高度和宽度。
第二步:添加resize监听,让图表“活”起来
光有flex布局还不够,因为Echarts不会自动感知容器大小变化。我们需要监听窗口或容器的resize事件,调用myChart.resize()。
但注意,直接监听window.resize有个小坑:用户频繁拖动窗口边缘时,事件触发太频繁,可能影响性能。我们可以用debounce(防抖)优化一下,或者直接用ResizeObserver(更现代、更精确)。
下面用两种方案给你展示:
方案A:传统window.resize监听 + 防抖
// 防抖函数
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 监听窗口大小变化,延迟300ms执行resize,避免频繁触发
window.addEventListener('resize', debounce(() => {
myChart.resize();
}, 300));
方案B:使用ResizeObserver(推荐,更精准)
ResizeObserver可以监听特定DOM元素的大小变化,比监听window更准确,尤其是当图表在滚动容器内或嵌套在flex布局中时。
// 创建ResizeObserver,监听图表容器
const resizeObserver = new ResizeObserver(entries => {
// 当容器大小变化时,触发图表resize
myChart.resize();
});
resizeObserver.observe(chartDom);
// 页面卸载时记得断开观察,避免内存泄漏
window.addEventListener('beforeunload', () => {
resizeObserver.disconnect();
});
我强烈建议用方案B,因为它只在你指定的容器大小真正变化时才触发,不会像window.resize那样被其他无关元素的大小变化干扰。比如你在手机上打开键盘,页面高度变了,但你的图表容器没变,ResizeObserver就不会瞎触发resize。
第三步:针对不同屏幕,微调图表细节
自适应不只是大小,还有可读性。手机屏幕窄,坐标轴标签、图例、tooltip都需要针对性优化。
你可以用window.innerWidth判断当前屏幕宽度,动态调整配置。比如:
function getResponsiveOption() {
const isMobile = window.innerWidth < 768;
return {
// ... 其他配置保持不变 ...
xAxis: {
axisLabel: {
rotate: isMobile ? 45 : 0, // 手机端旋转标签
fontSize: isMobile ? 11 : 14
}
},
legend: {
top: isMobile ? 5 : 10,
itemWidth: isMobile ? 12 : 16,
itemHeight: isMobile ? 8 : 12
},
grid: {
left: isMobile ? '5%' : '3%',
right: isMobile ? '5%' : '4%'
}
};
}
// 初始化时用响应式配置
myChart.setOption(getResponsiveOption());
// resize时重新应用配置(如果需要根据宽度重新计算)
resizeObserver.observe(chartDom);
window.addEventListener('resize', () => {
myChart.setOption(getResponsiveOption(), true); // true表示合并配置
myChart.resize();
});
这样,手机上看,标签会旋转、字体略小但清晰;平板或大屏上,标签水平、字体稍大,布局更宽松。用户无感知切换,体验很顺滑。
第四步:实战案例——一个完整的手机端仪表盘
想象你正在做一个移动端的销售数据看板,需要同时展示折线图、柱状图和饼图。如果每个图表都手动调尺寸,那简直是一场灾难。用flex布局+ResizeObserver,一行代码搞定所有图表自适应。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>移动端数据仪表盘</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
body {
margin: 0;
padding: 10px;
background: #f0f2f5;
}
.dashboard {
display: flex;
flex-direction: column;
gap: 12px;
}
.chart-card {
background: #fff;
border-radius: 10px;
padding: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
/* 每个卡片高度固定比例,或者让内容撑开 */
height: 260px;
}
.chart-title {
font-size: 15px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.chart-container {
width: 100%;
height: calc(100% - 24px); /* 减去标题高度 */
}
</style>
</head>
<body>
<div class="dashboard">
<div class="chart-card">
<div class="chart-title">销售额趋势</div>
<div id="lineChart" class="chart-container"></div>
</div>
<div class="chart-card">
<div class="chart-title">品类占比</div>
<div id="pieChart" class="chart-container"></div>
</div>
<div class="chart-card">
<div class="chart-title">区域销量</div>
<div id="barChart" class="chart-container"></div>
</div>
</div>
<script>
// 通用初始化函数
function initChart(domId, option) {
const dom = document.getElementById(domId);
const chart = echarts.init(dom);
chart.setOption(option);
// 使用ResizeObserver自动适配
const observer = new ResizeObserver(() => {
chart.resize();
});
observer.observe(dom);
// 存储chart实例,方便后续使用
dom.chartInstance = chart;
return chart;
}
// 折线图配置
const lineOption = {
tooltip: { trigger: 'axis' },
legend: { data: ['线上', '线下'], top: 5, textStyle: { fontSize: 12 } },
grid: { left: '10%', right: '5%', top: '20%', bottom: '15%', containLabel: true },
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月'],
axisLabel: { fontSize: 11 }
},
yAxis: { type: 'value', axisLabel: { fontSize: 11 } },
series: [
{ name: '线上', type: 'line', smooth: true, data: [120, 132, 101, 134, 90, 230], itemStyle: { color: '#5470c6' } },
{ name: '线下', type: 'line', smooth: true, data: [220, 182, 191, 234, 290, 330], itemStyle: { color: '#91cc75' } }
]
};
// 饼图配置
const pieOption = {
tooltip: { trigger: 'item' },
legend: { bottom: 5, left: 'center', textStyle: { fontSize: 11 } },
series: [{
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '45%'],
avoidLabelOverlap: false,
itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 },
label: { show: false },
data: [
{ value: 1048, name: '电子' },
{ value: 735, name: '服装' },
{ value: 580, name: '食品' },
{ value: 484, name: '家居' },
{ value: 300, name: '其他' }
]
}]
};
// 柱状图配置
const barOption = {
tooltip: { trigger: 'axis' },
grid: { left: '12%', right: '5%', top: '15%', bottom: '15%', containLabel: true },
xAxis: {
type: 'category',
data: ['华东', '华南', '华北', '西南', '西北'],
axisLabel: { fontSize: 11, rotate: 0 }
},
yAxis: { type: 'value', axisLabel: { fontSize: 11 } },
series: [{
type: 'bar',
data: [320, 280, 250, 180, 120],
itemStyle: { color: '#fac858', borderRadius: [4, 4, 0, 0] }
}]
};
// 初始化所有图表
initChart('lineChart', lineOption);
initChart('pieChart', pieOption);
initChart('barChart', barOption);
</script>
</body>
</html>
这个案例里,三个图表放在一个垂直排列的flex容器中,每个卡片高度固定260px。chart-container用height: calc(100% - 24px)填满剩余空间。关键是每个图表都用ResizeObserver监听自己的DOM,一旦容器大小变化(比如用户旋转手机、从手机切到平板分屏),图表自动resize
