嘿,说到在手机上渲染 ECharts,你是不是也遇到过那种让人抓狂的情况?明明在电脑浏览器上看着完美无瑕的柱状图、饼图,一到真机测试或者手机预览,瞬间就变成了马赛克,或者图被拉得细长,简直惨不忍睹。
别急,这种问题其实非常典型,核心矛盾大多集中在设备的物理像素比(DPR)以及容器尺寸动态变化的处理上。今天我们就深入聊一下 Vue 3 项目里如何优雅地解决这个问题。
理解”模糊”背后的真相
为什么手机端会模糊?简单来说,是因为 ECharts 默认是按照你给定的 width 和 height 来绘制 SVG 或 Canvas 的。如果你在 div 里设置的是 CSS 像素(比如 width: 100% 换算成 375px),但手机屏幕的 DPR(Device Pixel Ratio)是 2 或 3,那么 375 个 CSS 像素里其实塞进了 750 甚至 1125 个物理像素。
这时候,ECharts 如果没有针对这个比例进行缩放,浏览器就只能在有限的像素空间里强行拉伸,结果就是糊成一团。
代码实战:Vue 3 中的完美适配方案
我通常会建议在一个自定义 Hook 或者直接在组件的 mounted 阶段处理这两个关键点:缩放和自适应。下面这段代码你可以直接参考,它兼顾了清晰度、自适应和防止内存泄漏。
// ECharts 适配核心工具
const initChart = async (dom, option) => {
// 1. 获取 ECharts 实例
const chart = echarts.init(dom);
// 2. 关键:根据 DPR 缩放,防止模糊
// 这里利用 resize 事件来处理容器的变化,同时通过 scale 处理高 DPI 屏幕
const resize = () => {
const rect = dom.getBoundingClientRect();
// 设置宽高时需要考虑到 DPR,或者让 ECharts 自动处理
// 推荐方案:让 ECharts 根据容器大小自动绘制,但要手动处理 scale
chart.setOption(option, true); // true 表示不合并,完全重绘
chart.resize();
};
// 3. 监听窗口变化
window.addEventListener('resize', resize);
// 初始调用
resize();
// 4. 返回清理函数,防止内存泄漏
return {
chart,
destroy: () => {
window.removeEventListener('resize', resize);
chart.dispose();
}
};
};
在 Vue 3 的组件中,我们可以这样包装:
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
import * as echarts from 'echarts';
const chartRef = ref(null);
let chartInstance = null;
onMounted(() => {
if (chartRef.value) {
chartInstance = echarts.init(chartRef.value);
// 设置基础配置
const option = {
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: ['A', 'B', 'C', 'D'] },
yAxis: { type: 'value' },
series: [{ data: [150, 230, 224, 218], type: 'line' }]
};
chartInstance.setOption(option);
// 处理 resize
const handleResize = () => chartInstance.resize();
window.addEventListener('resize', handleResize);
// 保存监听器以便销毁
chartInstance._resizeHandler = handleResize;
}
});
onUnmounted(() => {
// 清理资源
if (chartInstance) {
window.removeEventListener('resize', chartInstance._resizeHandler);
chartInstance.dispose();
}
});
</script>
<template>
<!-- 关键:给 div 一个明确的高度,宽度可以用 100% -->
<div ref="chartRef" style="width: 100%; height: 300px;"></div>
</template>
常见的几个坑点与避坑指南
坑点一:容器高度没设置
很多开发者在写 CSS 时喜欢用 Flex 布局,觉得宽度能自适应,高度也应该没问题。但是!如果父容器没有明确的高度,或者 ECharts 容器本身高度为 0,图表就会渲染失败或者显示异常。
解决方案:
确保 <div> 有明确的高度。如果是动态高度,可以用 JavaScript 计算父容器的高度赋给图表容器。
.chart-container {
width: 100%;
height: 300px; /* 必须设置高度 */
}
坑点二:频繁 setOption 导致性能问题
在移动端,频繁的 setOption 会触发重绘,可能导致动画卡顿。特别是当数据是实时推送的时候。
解决方案:
使用 chart.setOption(option, notMerge=true) 进行全量重绘,但在数据量不大的情况下,尽量合并配置项,避免不必要的重计算。如果数据是实时滚动的,可以考虑使用 visualMap 或者只更新数据而不是整个配置。
坑点三:没有正确处理高 DPI 屏幕
前面提到的代码中,我们用了 echarts.init(dom),默认情况下 ECharts 会根据容器的 CSS 像素进行绘制。对于 Retina 屏(DPR=2),如果容器是 375px,ECharts 默认绘制 375px,然后被 CSS 拉伸到 750px 显示,结果就是模糊。
进阶解决方案:
在初始化时,手动设置 devicePixelRatio:
const chart = echarts.init(dom, null, {
renderer: 'canvas', // 使用 canvas 渲染
devicePixelRatio: window.devicePixelRatio || 1
});
这样可以确保绘制的 Canvas 具有更高的物理像素,显示更清晰。
让图表更”智能”:响应式与动态高度
如果你的图表需要填满整个屏幕宽度,并且高度要跟着数据动态变化,可以这样处理:
// 计算高度函数
const getHeight = () => {
const screenWidth = document.body.clientWidth;
// 根据屏幕宽度计算合适的高度,比如宽度的 0.5 倍
return screenWidth * 0.5;
};
然后在 mounted 时初始化,并监听窗口变化:
onMounted(() => {
chartInstance = echarts.init(chartRef.value);
updateChart();
window.addEventListener('resize', updateChart);
});
const updateChart = () => {
const height = getHeight();
chartRef.value.style.height = `${height}px`;
chartInstance.resize();
};
总结:记住这三个关键点
- 明确高度:容器必须设置高度,否则 ECharts 无法渲染。
- 处理 DPR:对于高清屏,确保 ECharts 使用正确的
devicePixelRatio初始化,避免模糊。 - 监听 Resize:移动端屏幕尺寸多样,务必监听窗口变化并调用
resize()。
通过这种方式,你的 ECharts 图表在手机上就能既清晰又适配良好,不会再让用户体验打折扣了。如果你在 Vue 3 中遇到其他图表相关问题,随时可以再深入探讨!
