如何将页面上的图表转换成图片?
图表是使用HTML Canvas绘制的,则可以通过Canvas元素的 toDataURL()方法。
接口将其转换成base64图片。
若图表是使用SVG绘制的,则可以用canvg等工具将SVG转换成Canvas,而后再转换成图片。
若图表使用其他技术绘制,则可以用html2canvas等工具将HTML元素转换成Canvas,而后再转换成图片。
用于绘制图表的原始数据发送给服务器,由服务器启动Puppeteer等无头浏览器来绘制图表。
如何将图片及标题文字等内容组装成PDF?
浏览器前端通过jsPDF等客户端工具将图表图片及标题文字元素组装成PDF。
PDF导出策略
1.前端截图、前端排版
该策略执行步骤:
1.该策略将页面上的图表相关的元素通过html2canvas等工具转为图片,或调用浏览器的getDisolayMedia()接口来截屏。
2.而后通过jsPDF等工具自行完成PDF的组装生成。
3.最后用javascript触发浏览器的下载操作,完成PDF的导出。
优点:
开发实现简单,难度低。
导出速度快:所有操作都在前端浏览器发生,不涉及与服务器之间的操作。
在前端浏览器所有操作中也不存在需要消耗大量CPU计算的场景,可以短时间内完成组装生成PDF。
缺点:
导出PDF效果不好,画质效果感比较差。
该策略也是“将图表导出成PDF”场景中主流方案,尤其是html2canvas+jsPDF方案,如果在搜索引擎搜索类似“ECharts导出PDF的字样”,大部分都是采用这一方案。
2.前端截图、后端排版
和第一种方案类似,只是把逻辑后移交给了后端,并没有解决截图质量的问题。
3.后端截图、后端排版
该策略执行步骤:
1.将用于绘制图表的原始数据或用于获取这些数据的参数发送给后端。
2.由后端启动Puppeteer等无头浏览器来绘制图表。
3.然后调用无头浏览器中的截图接口来获取图表图片。
4.最后由后端负责PDF排版组装。
这种策略好处很多,可以在后端使用LaTex这样的专业排版工具实现高质量的文档排版,呈现精致的报表PDF效果。
这一策略是以开发成本和导出速度为代价,换取高质量的PDF文档效果。
总结
若追求PDF导出速度,则采用前端截图+前端排版的方案,如html2canvas。
若追求PDF导出文档质量,则可采用后端截图+后端排版的方案,如Puppeteer+LaTex
案例
使用vue3实现的一个demo
所需依赖:
代码实现:
<template>
<h1 style="text-align: center;">html2canvas+jsPDF导出canvas柱状图</h1>
<canvas class="canvasSty"
ref="ctxRef"></canvas>
<div style="text-align: center;">
<button @click="toPdf">导出为PDF</button>
</div>
</template>
<scriptsetup>
import { ref, onMounted } from'vue'
import html2canvas from'html2canvas'
import jsPDF from'jspdf'
import*as echarts from'echarts' //引入echarts
let ctxRef=ref()
// 一个简单的canvas柱状图
function drawCanvas () {
// 基于准备好的dom,初始化echarts实例
var myChart=echarts.init(ctxRef.value);
let xData= ["枯水期", "丰水期", "平水期"]
let seriesData= [
{
name: "监测值",
value: [95.04, 96.13, 94.76]
},
{
name: "标准值",
value: [94.96, 96.02, 94.68]
}
]
// 指定图表的配置项和数据
var option= {
"grid": {
left: '4%',
right: '6%',
bottom: '15%',
top: '23%',
containLabel: true,
},
"backgroundColor": "black",
"color": ["#1DB750", "#C7F36A"],
"tooltip": {
"trigger": "axis",
},
"legend": {
"data": seriesData,
right: '4%',
top: '10%',
align: 'left',
icon: 'rect',
itemWidth: 12,
itemHeight: 12,
"textStyle": {
"fontSize": 14,
color: "#fff"
}
},
"xAxis": {
showBackground: true,
nameTextStyle: {
"color": "#c0c3cd",
"padding": [0, 0, -10, 0],
"fontSize": 14
},
axisLine: {
show: true, //隐藏X轴轴线
lineStyle: {
color: '#555f58'
}
},
axisLabel: {
interval: 0,
textStyle: {
color: '#fff'//坐标轴字颜色
},
margin: 15
},
axisTick: {
show: false//隐藏X轴刻度
},
splitLine: { //网格线
"show": false
},
data: xData,
type: "category"
},
"yAxis": {
name: '单位:mg/L',
nameTextStyle: {
fontSize: 14,
color: "#fff",
},
axisLine: {
show: true, //隐藏X轴轴线
lineStyle: {
color: 'rgba(220,220,220,0.3)'
}
},
axisTick: {
show: false//隐藏X轴刻度
},
axisLabel: {
textStyle: {
color: '#fff'
}
},
splitLine: { //网格线
"show": true,
lineStyle: {
color: 'rgba(220,220,220,0.3)'
}
}
},
"series": function () {
let series= []
for (let i=0; i<seriesData.length; i++) {
let serie= {
"name": seriesData[i].name,
"type": "bar",
"barWidth": "13%",
"data": seriesData[i].value
}
series.push(serie)
}
return series
}()
}
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
onMounted(() => {
drawCanvas()
})
// 图片转pdf方法
const toPdf= () => {
console.log(html2canvas(ctxRef.value));
html2canvas(ctxRef.value).then(_d=> {
console.log(_d);
console.log(_d.toDataURL('image/png'));
// 转为base64格式
let imgUrlData=_d.toDataURL('image/png', 1.0)
/*
new jsPDF(orientation, unit, format, compress)
orientation(方向):默认值为"portrait" orientation:'landscape' 横向
unit:告诉jsPDF 在哪个单元工作 "pt(点)"、"mm(默认)"、"cm"、"in"
format(格式):"a4(默认)"、"a3"、"a5"、"letter"、"legal"
const pdfDoc = new jsPDF({
orientation:"landscape", //方向
unit:"in", //pdf大小计量单位
format:[4,2] //自定义第一页pdf页面大小,按html元素比例设定
})
*/
const pdfDoc=newjsPDF('', 'pt', 'a4')
pdfDoc.addImage(imgUrlData, 'jpeg', 0, 0, 600, 300)
pdfDoc.save('导出的图表pdf.pdf')
})
}
</script>
<style scoped>
.canvasSty {
position: relative;
width: 400px;
height: 200px;
left: 50%;
transform: translateX(-50%);
}
</style>
浏览器页面展示:
点击导出为PDF后打开pdf图展示: