🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
前端性能優化實戰:ECharts地圖渲染12萬+數據動態動畫方案
本文記錄了在實際項目中,使用ECharts地圖組件渲染12萬+設備安裝數據的性能優化實戰經驗,包含完整的技術方案和代碼實現。
項目背景
公司需要將全年設備安裝量通過旗幟的形式展示在全國地圖上,實現數據可視化大屏。主要技術挑戰:
- 數據量大:全年設備安裝數據約20萬條
- 實時更新:通過WebSocket實時接收數據
- 動畫效果:需要展示數據逐條添加的動態效果
- 性能要求:需要保持60fps的流暢動畫
一、初始實現與性能瓶頸
1.1 基礎地圖配置
首先使用ECharts搭建基礎地圖框架:
initChart() {
this.chart = echarts.init(this.$refs.chart);
let option = {
geo: {
map: 'china',
roam: true,
zoom: 1.1,
scaleLimit: { min: 1, max: 10 },
itemStyle: {
areaColor: 'rgba(91,97,141,.3)',
borderColor: 'rgba(0,0,0,.2)'
}
},
series: [
// 基礎地圖層
{
type: 'map',
map: 'china',
itemStyle: {
areaColor: 'rgba(0,0,0,0)',
borderColor: 'rgba(255,255,255,1)',
}
},
// 設備點圖層
{
id: 'scatter',
type: 'scatter',
coordinateSystem: 'geo',
data: [],
symbol: 'image://flag.png', // 旗幟圖標
symbolSize: [16, 22],
animation: false // 關閉內置動畫
}
]
};
this.chart.setOption(option);
}
1.2 動畫設計
設計旗幟生長動畫效果,通過隨機數實現多樣化的動畫展示:
// 旗幟動畫效果設計
const flagAnimations = [
'scaleUp', // 從小變大
'fadeIn', // 淡入
'bounceIn', // 彈跳進入
'rotateIn' // 旋轉進入
];
function getRandomAnimation() {
return flagAnimations[Math.floor(Math.random() * flagAnimations.length)];
}
1.3 遇到的性能瓶頸
當數據量達到3-5萬條時,開始出現明顯卡頓:
- 動畫幀率下降到30fps以下
- 內存佔用持續增長
- 縮放平移操作卡頓
- WebSocket數據堆積
二、分層策略優化方案
經過調研,我們採用了分層策略來優化性能,根據不同的縮放級別採用不同的渲染策略。
2.1 分層策略設計
const zoomConfigs = {
low: { // 低縮放級別:全國視圖
zoom: 2,
sampleRate: 0.1, // 10%抽樣顯示
precision: 2, // 經緯度精度:小數點後2位
symbolSize: [8, 11] // 縮小圖標
},
mid: { // 中縮放級別:省級視圖
zoom: 5,
sampleRate: 0.5, // 50%抽樣顯示
precision: 3, // 經緯度精度:小數點後3位
symbolSize: [12, 16]
},
high: { // 高縮放級別:市級視圖
zoom: 10,
sampleRate: 1, // 100%顯示
precision: 4, // 經緯度精度:小數點後4位
symbolSize: [16, 22]
}
};
2.2 動態動畫調度系統
class AnimationScheduler {
constructor() {
this.pendingList = []; // 待處理數據隊列
this.allDeviceList = []; // 所有數據存儲
this.displayList = []; // 當前顯示數據
this.deviceSet = new Set(); // 數據去重
this.displaySet = new Set(); // 顯示去重
this.animationTimer = null;
// 幀率控制
this.frameInterval = 50; // 20fps
this.batchSize = 100; // 每批處理數量
}
// 啓動動畫調度
startAnimation() {
if (this.animationTimer) return;
let lastTime = 0;
const animate = (currentTime) => {
if (this.pendingList.length === 0) {
this.stopAnimation();
return;
}
// 幀率控制
if (currentTime - lastTime >= this.frameInterval) {
lastTime = currentTime;
this.processBatch();
}
this.animationTimer = requestAnimationFrame(animate);
};
this.animationTimer = requestAnimationFrame(animate);
}
// 處理一批數據
processBatch() {
const batch = this.pendingList.splice(0, this.batchSize);
const config = this.getCurrentZoomConfig();
let hasNewData = false;
batch.forEach(item => {
// 全局去重
const globalKey = `${item.lng},${item.lat}`;
if (this.deviceSet.has(globalKey)) return;
this.deviceSet.add(globalKey);
const point = {
value: [item.lng, item.lat],
createTime: item.createTime
};
this.allDeviceList.push(point);
// 根據當前縮放級別抽樣
if (this.shouldDisplay(point, config)) {
const displayKey = this.getDisplayKey(point, config);
if (!this.displaySet.has(displayKey)) {
this.displaySet.add(displayKey);
this.displayList.push(point);
hasNewData = true;
}
}
});
// 批量更新圖表
if (hasNewData) {
this.updateChart();
}
}
}
2.3 智能顯示判斷
// 根據縮放級別判斷是否顯示
shouldDisplay(point, config) {
// 完全顯示模式
if (config.sampleRate >= 1) return true;
// 抽樣顯示模式
const displayChance = Math.random();
return displayChance < config.sampleRate;
}
// 生成顯示鍵(根據精度去重)
getDisplayKey(point, config) {
const lng = point.value[0].toFixed(config.precision);
const lat = point.value[1].toFixed(config.precision);
return `${lng},${lat}`;
}
2.4 縮放級別變化處理
// 監聽縮放變化
setupZoomListener() {
this.chart.on('georoam', () => {
const option = this.chart.getOption();
if (option.geo && option.geo[0]) {
const newZoom = option.geo[0].zoom;
if (Math.abs(newZoom - this.currentZoom) > 0.1) {
this.currentZoom = newZoom;
this.handleZoomChange();
}
}
});
}
// 處理縮放變化
handleZoomChange() {
const config = this.getCurrentZoomConfig();
// 只有層級變化時才重建顯示數據
if (config.level !== this.currentZoomLevel) {
this.currentZoomLevel = config.level;
this.rebuildDisplayList();
}
}
// 重建顯示數據
rebuildDisplayList() {
const config = this.getCurrentZoomConfig();
this.displayList = [];
this.displaySet = new Set();
if (config.sampleRate >= 1) {
// 全量顯示模式
this.displayAllData(config);
} else {
// 抽樣顯示模式
this.displaySampledData(config);
}
this.updateChart();
}
// 全量顯示(高精度去重)
displayAllData(config) {
for (const item of this.allDeviceList) {
const key = this.getDisplayKey(item, config);
if (!this.displaySet.has(key)) {
this.displaySet.add(key);
this.displayList.push(item);
}
}
}
// 抽樣顯示
displaySampledData(config) {
const step = Math.max(1, Math.floor(1 / config.sampleRate));
for (let i = 0; i < this.allDeviceList.length; i += step) {
const item = this.allDeviceList[i];
const key = this.getDisplayKey(item, config);
if (!this.displaySet.has(key)) {
this.displaySet.add(key);
this.displayList.push(item);
}
}
}
三、其他優化技巧
3.1 內存管理優化
// 定期清理過期數據
setupMemoryManagement() {
setInterval(() => {
// 限制總數據量
const maxTotal = 150000;
if (this.allDeviceList.length > maxTotal) {
const removeCount = this.allDeviceList.length - 120000;
this.allDeviceList.splice(0, removeCount);
// 清理相關緩存
this.cleanCache();
// 重建顯示
this.rebuildDisplayList();
}
}, 30000); // 每30秒檢查一次
}
// WebSocket數據流控
setupWebSocketFlowControl() {
let buffer = [];
let processing = false;
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
buffer.push(...data);
// 流量控制:如果緩衝過多,暫停接收
if (buffer.length > 5000 && !processing) {
this.ws.pause();
}
if (!processing) {
this.processWebSocketBuffer();
}
};
}
3.2 性能監控
// 添加性能監控
setupPerformanceMonitor() {
let frames = 0;
let lastTime = performance.now();
const monitor = () => {
frames++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
const fps = Math.round(frames * 1000 / (currentTime - lastTime));
console.log(`當前FPS: ${fps}`);
// 動態調整策略
this.adjustStrategyByFPS(fps);
frames = 0;
lastTime = currentTime;
}
requestAnimationFrame(monitor);
};
requestAnimationFrame(monitor);
}
// 根據FPS動態調整
adjustStrategyByFPS(fps) {
if (fps < 30) {
// 降低渲染質量
this.frameInterval = 100; // 10fps
this.batchSize = 50;
} else if (fps > 50) {
// 提高渲染質量
this.frameInterval = 33; // 30fps
this.batchSize = 150;
}
}
四、效果對比
優化前:
- 3萬數據開始卡頓
- 內存佔用500MB+
- 縮放操作延遲明顯
- 動畫掉幀嚴重
優化後:
- 12萬數據流暢運行
- 內存控制在200MB以內
- 縮放操作流暢
- 保持30fps以上動畫
五、總結
通過分層策略優化,我們成功實現了:
- 智能顯示:根據縮放級別動態調整顯示策略
- 性能平衡:在視覺效果和性能之間找到平衡點
- 內存控制:有效管理大量數據的內存佔用
- 流暢動畫:保持高幀率的動畫效果
這種分層策略不僅適用於地圖可視化,也可以擴展到其他大規模數據可視化場景中。關鍵思想是:不同視角需要不同精度的數據展示。