本文是我初学 ECharts 官网中 “服务端渲染” 的实践(Vue3 + express)、理解和总结。官网网址:服务端渲染 - 跨平台方案 - 应用篇 - Handbook - Apache ECharts
普遍情况下,Apache ECharts 会选择在浏览器中动态渲染图表,并且根据用户交互来更新渲染。但是在以下场景里,我们也需要在服务端渲染图表并输出到浏览器中:
- 需要缩短前端渲染时间,保证首先显示图表
- 在不支持动态运行脚本的环境下嵌入图表,如Markdown, PDF等
ECharts 提供了两种服务器渲染(server-side rendering,SSR)的方案:SVG 渲染或 Canvas 渲染。
| 渲染方案 | 渲染结果的形式 | 优点 |
|---|---|---|
| 服务端 SVG 渲染 | SVG 字符串 | 比 Canvas 图片体积更小;矢量 SVG 图片不会模糊(在实践中发现的确比 Canvas 渲染更清晰);支持初始动画 |
| 服务端 Canvas 渲染 | 图片 | 图片形式适用场景更广泛,对不支持 SVG 的场景可以选择 |
官网建议优先考虑使用 SVG 渲染方案,如果 SVG 不适用,也可以考虑 Canvas 渲染方案。但使用服务端渲染也有一定局限性,特别是不支持交互相关的部分操作。所以,如果有交互需求,可参考服务端渲染 Hydration(本文也有提及和实践)
为了大家更好理解服务端渲染,我先贴出init方法的类型(具体可看官方API文档:Documentation - Apache ECharts)
(dom?: HTMLDivElement | HTMLCanvasElement, theme?: Object | string, opts?: {
devicePixelRatio?: number, // 设备像素比
renderer?: string, // 渲染模式,支持'canvas'或者'svg'
useDirtyRect?: boolean, // 是否开启脏矩形渲染
useCoarsePointer?: boolean, // 是否扩大可点击元素的响应范围
pointerSize?: number, // 扩大元素响应范围的像素大小
ssr?: boolean, // 是否使用服务端渲染
width?: number|string, // 可显式指定实例宽度,单位为像素
height?: number|string, // 可显式指定实例高度,单位为像素
locale?: string // 使用的语言
}) => ECharts
参数解释
- dom:实例容器,一般是具有宽高的 DIV 元素。只有设置
opts.ssr开启了服务端渲染后该参数才是可选。也支持直接使用canvas元素作为容器。 - theme:应用的主题。
- opts:附加参数。
服务端 SVG 渲染
我使用的是 5.3.0 里新引入的零依赖的服务端 SVG 字符串渲染方案:
以下是我实践服务端 SVG 渲染的服务端完整代码:
import express from "express";
import echarts from "echarts";
const app = express();
// 服务端 SVG 渲染的整体代码结构
function renderSVGChart() {
// 在服务端 SVG 渲染模式下第一个参数不需要再传入 DOM 对象
const chart = echarts.init(null, null, {
renderer: 'svg', // 指定使用 SVG 模式
ssr: true, // 开启 SSR
width: 400, // 需要指明高和宽
height: 300
})
// 正常使用 setOption
chart.setOption({
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
yAxis: {
type: "value"
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: "bar",
}
]
});
// 使用 renderToSVGString 将当前的图表渲染成 SVG 字符串
return chart.renderToSVGString();
}
// 为路径“/” 的 GET 请求定义路由处理程序
app.get('/', (req, res) => {
// 设置响应头,解决跨域
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'Authorization,X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method')
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PATCH, PUT, DELETE')
res.header('Allow', 'GET, POST, PATCH, OPTIONS, PUT, DELETE')
// 向请求客户端发送 HTTP 响应消息(返回的是 SVG 字符串)
res.send(renderSVGChart());
})
app.listen(3000, () => {
console.log('Example app listening on port 3000')
})
可以看出整体使用的代码结构跟在浏览器中使用是一致的:首先init初始化一个图表实例,然后通过setOption设置图表的配置项。但是init传入的参数会跟在浏览器中使用有些出入:
- 因为服务端会通过字符串拼接的方式来渲染得到 SVG ,并不需要定义容器来展示渲染内容,所以在通过
init初始化图表实例时,第一个参数dom传入null或者undefined。 - 在
init的第三个参数中,我们需要显式指定ssr: true来告诉ECharts我们需要开启服务端渲染模式,Echarts会关闭动画循环和事件交互模块。 - 在服务端渲染中我们也必须要通过
width和height指定图表的高和宽。
// 在服务端 SVG 渲染模式下第一个参数不需要再传入 DOM 对象
const chart = echarts.init(null, null, {
renderer: 'svg', // 指定使用 SVG 模式
ssr: true, // 开启 SSR
width: 400, // 需要指明高和宽
height: 300
})
初始化完图表实例后,接着调用setOption。在浏览器中我们在setOption完后ECharts就会自动进行渲染并把结果绘制到页面中,后续也会在每一帧判断是否有动画需要进行重绘。在服务端中我们设置了ssr: true故则没有这个过程,取而代之我们使用了renderToSVGString,将当前的图表渲染到 SVG 字符串,进一步通过 HTTP Response 返回给前端。
const chart = echarts.init(...)
chart.setOption({...});
const svgString = chart.renderToSVGString()
app.get('/', (req, res) => {
// ...
// 向请求客户端发送 HTTP 响应消息(返回的是 SVG 字符串)
res.send(svgString);
})
经过以上程序,服务端返回响应后,客户端处理如下(此处我省略了封装axios和封装请求方法的代码,直接展示我发送请求后处理响应):
// vue setup lang="ts"
import { ref } from 'vue'
import { api } from '../api/data'
const chart = ref('')
// 发送请求,请求成功后把响应的数据赋值给变量chart
api.getSVGChart().then(res => {
chart.value = res.data;
}).catch(err => {
console.log(err);
})
<!-- 使用 v-html 标签渲染SVG字符串 -->
<div v-html="chart"></div>
服务端 Canvas 渲染
如果你希望输出的是一张图片而非 SVG 字符串,或者你还在使用更老的版本,官方推荐使用 node-canvas 来实现 ECharts 的服务渲染,node-canvas 是在 NodeJS 上的一套 Canvas 实现,它提供了跟浏览器中 Canvas 几乎一致的接口。
以下是我实践服务端 Canvas 渲染的服务端完整代码:
import express from "express";
import echarts from "echarts";
import { createCanvas } from 'canvas';
const app = express();
// 服务端 Canvas 渲染
function renderCanvasChart() {
// 创建 Canvas 实例
const canvas = createCanvas(400, 300);
// ECharts 可以直接使用 node-canvas 创建的 Canvas 实例作为容器
const chart = echarts.init(canvas);
// 正常使用 setOption
chart.setOption({
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
yAxis: {
type: "value"
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: "bar"
}
]
});
return canvas;
}
app.get('/', (req, res) => {
// 解决跨域
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'Authorization,X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method')
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PATCH, PUT, DELETE')
res.header('Allow', 'GET, POST, PATCH, OPTIONS, PUT, DELETE')
res.header("Content-Type", "image/png")
// 通过响应返回 PNG 图片
res.send(renderCanvasChart().toBuffer("image/png"));
})
app.listen(3000, () => {
console.log('Example app listening on port 3000')
})
经过以上程序,服务端返回响应后,客户端处理如下(同样,此处我省略了封装axios和封装请求方法的代码,直接展示我发送请求后处理响应):
// vue setup lange="ts"
import { ref } from 'vue'
import { api } from '../api/data'
const chart = ref('')
const transformBlobToPNG = (data: Blob) => {
//将Blob类型数据转换为url对象
return window.URL.createObjectURL(data);
}
api.getCanvasChart().then(res => {
chart.value = transformBlobToPNG(res.data)
}).catch(err => {
console.log(err);
})
<!-- 显示PNG图片 -->
<img :src="chart" alt="">
我们可以对比一下服务端 SVG 渲染和 Canvas 渲染后的结果,SVG 的渲染结果是比 Canvas 清晰一些。
服务端渲染 Hydration
服务端渲染无法支持的功能包括:
- 动态改变数据
- 高亮鼠标所在的数据项
- 点击图例切换系列是否显示
- 移动鼠标显示提示框
- 其他交互相关的功能
如果有相关需求,可以考虑先使用服务端渲染快速输出首屏图表,然后等待echarts.js加载完后,重新在客户端渲染同样的图表(称为 Hydration),这样就可以实现正常的交互效果和动态改变数据了。需要注意的是,在客户端渲染的时候,应开启 tooltip: { show: true } 之类的交互组件,并且用 animation: 0 关闭初始动画(初始动画应由服务端渲染结果的 SVG 动画完成)。
我在此实践中先用 SVG 做服务端渲染,再用 Canvas 做客户端渲染的效果。本次实践的服务端代码我就不贴了,与服务端 SVG 渲染的服务端代码一致。以下是我客户端的代码:
import { ref } from 'vue'
import { api } from '../api/data'
import * as echarts from 'echarts';
const chart = ref('')
// 控制显示服务端的 SVG 图片类型图表
const loading = ref(false);
api.getSVGChart().then(res => {
chart.value = res.data;
}).catch(err => {
console.log(err);
})
const loadChart = () => {
loading.value = true;
// 客户端渲染同样数据的图表,实现交互效果
let card = document.getElementById('card');
let myChart = echarts.init(card);
myChart.setOption({
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
yAxis: {
type: "value"
},
tooltip: {
// 展现交互效果
show: true
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: "bar",
// 关闭初始动画
animation: 0
}
]
})
}
<!-- 服务端渲染 Hydration -->
<!-- 鼠标移入容器后,服务端渲染的 SVG 图片会取消显示, -->
<!-- 取而代之的是客户端已经渲染好的动态图表 -->
<div id="card" @mouseover.once="loadChart">
<div v-html="chart" v-if="!loading"></div>
</div>
结语
以上就是我分享的所有内容,感谢大家的观看!欢迎大家在评论区发表自己的见解~