项目概述
1. 依赖包版本
// package.json
{
// ...
"dependencies": {
// ...
"echarts": "^4.8.0",
"vue": "^2.6.10",
// ...
},
// ...
}
复制代码
2. 业务说明
简要分析下公司目前的数据大屏情况:
- 除了地图模块数据量相对复杂些,其他模块展示的数据量可控;
- 项目设置了专门的展厅,页面需要长时间启用,页面时有崩溃现象发生;
- 页面投放方式为主机加载后,有场景会涉及需要使用浏览器内置的缩放功能展示。
项目的页面缩放方案参考了 百度云 页面的实现思路,按照固定的宽高比(如:16:9
)来计算出最合适的缩放比进行缩放渲染。
<template>
<div class="scale-box" :style="{width: cw + 'px', height: ch + 'px'}">
<div class="scale-container" :style="{transform: 'scale(' + scale + ')', width: width + 'px', height: height + 'px'}">
<slot></slot>
</div>
</div>
</template>
复制代码
// 容器宽高
let cw = 0;
let ch = 0;
// UI规范宽高
let width = 1920;
let height = 1080;
let scale = 1;
// 可视区宽高比例
let ww = window.innerWidth / this.width;
let wh = window.innerHeight / this.height;
// 计算实际最合适缩放比:以小者为缩放基准,内容不足处留白处理,确保内容展示的完整性。原理同背景图的background-size属性。
scale = ww < wh ? ww : wh;
// 更新容器渲染宽高
cw = this.width * scale
ch = this.height * scale
复制代码
一、Canvas
渲染还是 SVG
渲染
1. 理论梳理
-
Canvas
更适合绘制图形元素数量非常大的图表,也利于实现某些 视觉特效。如:热力图、地理坐标系或平行坐标系上的大规模线图或散点图等。
// 使用 Canvas 渲染器(默认) var chart = echarts.init(containerDom, null, {renderer: 'canvas'}); // 等价于: var chart = echarts.init(containerDom); 复制代码
-
SVG
内存占用更低、渲染性能略高、用户使用浏览器内置的缩放功能时不会模糊。使用
Canvas
渲染器和SVG
渲染器绘制中等数据量的折、柱、饼图,SVG
渲染器相比Canvas
渲染器在移动端的总体表现更好。但是在另一些数据量较大或者有图表交互动画的场景中,目前的SVG
渲染器的性能还比不过Canvas
渲染器。// 使用 SVG 渲染器 var chart = echarts.init(containerDom, null, {renderer: 'svg'}); 复制代码
-
综上所述,选择哪种渲染器,需要根据
软硬件环境
、数据量
、功能需求
来综合考虑决定。业务场景(↓) \ ECharts渲染器(→) Canvas SVG 软硬件环境较好
数据量不大√ √ 软硬件环境较差
数据量不大多ECharts实例
浏览器易崩溃× √ 数据量很大
交互较多√ × -
注意 目前的
SVG
版中,富文本、材质功能尚不支持。
2. 项目分析
基于现有缩放方案,UI
稿是按照 1920 × 1080
的宽高像素输出,购置的硬件规格也是一个屏幕为 1920 × 1080
像素的宽高尺寸。
- 如果一个页面在一个硬件屏幕上展示时,无需缩放,展示效果接近输出的
UI
稿。 - 但实际存在九个硬件屏幕拼接成一个大屏幕来渲染一个页面的场景(此时相当于页面被二次放大了
9
倍。最终,页面元素渲染模糊)。
综上所述,公司该数据大屏项目最终选择的 ECharts
渲染器为 SVG
。
// svg渲染
var chart = echarts.init(containerDom, null, {renderer: 'svg'});
复制代码
3. 来个扩展
关于 ECharts
元素渲染模糊问题,翻看 ECharts文档 有发现一个配置参数:devicePixelRatio
(设备像素比,默认取浏览器的值 window.devicePixelRatio
)。
试问,开发中是否可以保留使用 Canvas
渲染,通过设置 devicePixelRatio
来解决元素渲染模糊这个问题呢?
思路如下:
- 保留
ECharts
默认渲染器(Canvas
); - 设置
devicePixelRatio
为一个较大值,如:9
; - 使用浏览器内置的缩放功能查看页面元素渲染结果是否模糊。如果模糊,那存在对应场景的业务代码就只能采用
SVG
渲染模式;如果不模糊,那就可以继续使用Canvas
渲染啦,只是加多一个devicePixelRatio
参数值设置而已。
思路有了,那我们测试下吧~
-
默认渲染器,默认设备像素比
// 默认渲染器,默认设备像素比 var chart = echarts.init(containerDom); 复制代码
-
默认渲染器,设备像素比设置为较大值
// 默认渲染器,设备像素比设置为较大值 var chart = echarts.init(containerDom, null, {devicePixelRatio: 9}); 复制代码
-
SVG
渲染// svg渲染 var chart = echarts.init(containerDom, null, {renderer: 'svg'}); 复制代码
由此说明,如若是单纯考虑浏览器内置的缩放功能的因素,使用 SVG
渲染并不是必须的。而且仔细看会发现,Canvas
渲染的效果更丰富:当前高亮模块的渐变竖线,Canvas
渲染有,而SVG
渲染没有。
二、慎用有色原生表格渲染数据
1. 问题陈述
基于现有缩放方案,使用 原生表格(qb-origin-table
)渲染数据,如若表格行(tr
)是有背景色的,使用该页面缩放方案即使表格的边框合并在一起(border-collapse: collapse;
),表格边框之间的间距(border-spacing
)为 0
,无边框(border: 0 none;
),边框色透明(border-color: transparent;
),单元格之间仍可见一条黑线!
最终不得不借用无意义的 div
模拟实现表格(qb-imitate-table
)来兼容。
2. 原理分析
-
CSS3
transform
属性scale
改变的不是元素的宽高,而是X
,Y
轴的刻度。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CSS3 transform scale解析</title> <style> .box-origin { width: 100px; height: 100px; margin: 150px; background: lightgray; color: black; } .box-scale { width: 100px; height: 100px; margin: 150px; background: lightgray; color: black; transform: scale(2); } </style> </head> <body> <div class="box-origin">原始div块</div> <div class="box-scale">原始div块scale放大后的div块</div> </body> </html> 复制代码
-
HTML
采用的是窗口坐标系,以参考对象的元素盒子左上角为坐标原点,x
轴向右,y
轴向下,坐标值对应像素值。参考对象通常是最接近分析元素的
position
非static
的元素。-
保留等比缩放
-
重置缩放比例为默认值
尝试对表格行重置缩放比例(
transform: scale(1);
),黑线并未消除。考虑到坐标系因素,往上一层层重置,直到重置到最外层的缩放比,黑线终于没有了! -
三、Vue
中推荐 ECharts
组件模板
<template>
<div ref="moreDataLineChart" style="width: 100%; height: 100%;"></div>
</template>
<script>
/**
* @description: 多折线ECharts组件
* @update: 2021-02-02 11:03:30
* @author: Ada.H
*/
export default {
name: 'moreDataLineChart',
props: {
// 数据集合
seriesData: {
type: Array,
default: () => {
return [];
},
},
// other props here ...
},
data() {
return {
// 组件实例对象
myChart: null,
// 组件配置参数
options: {},
};
},
mounted() {
this.init();
},
beforeDestroy() {
this.destroyEchart();
},
watch: {
seriesData: {
handler() {
this.$nextTick(() => {
this.update();
});
},
deep: true,
},
},
methods: {
// 初始化ECharts组件
init() {
this.destroyEchart();
if (!this.myChart) {
this.myChart = this.$echarts.init(this.$refs.moreDataLineChart, null, {
renderer: 'svg',
});
}
this.update();
},
// 更新组件数据
update() {
// 逻辑处理组件options参数
const options = {
series: this.seriesData,
// other options here ...
};
this.options = options;
// 确保dom存在
this.$nextTick(() => {
// 清除画布
this.myChart.clear();
// 调用ECharts组件setOption更新
this.myChart.setOption(this.options, true);
// 更新动画
this.mixin_loop_animation(this.options, this.myChart, true).then(
(res) => {
if (res) {
this.mixin_loop_animation(this.options, this.myChart, false);
}
}
);
});
},
// 销毁ECharts实例
destroyEchart() {
if (this.myChart) {
this.myChart.clear();
this.myChart.dispose();
this.myChart = null;
}
},
// other methods here ...
},
};
</script>
复制代码
- 组件销毁时要记得清理
ECharts
实例,及时释放应用内存。 - 组件初始化
init
和组件数据更新update
相互独立,保证一个组件从创建到销毁的整个生命周期只初始化一次ECharts
实例,节省程序性能消耗。 - 借由
Vue
的 深度watch
来同步更新ECharts
实例视图,而非粗暴的v-if
,避免引发内存泄漏,导致页面崩溃问题。Vue.js 避免内存泄漏