基于Vue.js的大型报告页项目实现过程及问题总结(二)
距离上一篇文章过去了二十多天了,期间一直想把第二部分写完,结果在测试过程中遇到了各种坑爹的问题,到今天才算基本完成,也许还有后续,但趁着今天有时间就写出来吧,也算对这个项目的一个总结了
遇到最大问题:
项目的需求是在一个窗口里生成所有图表,还要考虑到整套打印,所以滚动加载和分页浏览不是最好的方案,这导致数据超级多的时候(大概会生成2000多页的报告且上不封顶),会造成页面假死,疯狂占用电脑内存,低配置的电脑根本无法加载,甚至造成死机
在项目结构上我们采用数据分发的方式控制组件的渲染,由大致小每层组件都对数据进行过滤,重新组成新的数据传递给下一级,根据数据去判断显示与否,由于vue里v-if的机制如果该模块数据不存在,那么组件将不被渲染
一般来说我解决问题只有两种方式,一是找到解决问题的办法,二是让这个问题彻底消失,显然第二个是在这是行不通的,所以先分析原因:
1.后端返回的是原始数据,大量代码都需要前端进行处理,在前端进行如此大工作量的数据处理,显然内存消耗也是巨大的,显然这是不明智的,但后台数据暂时无法做进一步处理
2.echarts绘制图表的同时动画和频繁操作dom添加canvas也是也是消耗性能的元凶之一
3.大量的图表绘制同步进行会导致阻塞
原因已经找到接下来就是解决问题
先说动画的问题,这个在echarts的api里已经提出的解决办法,有两种,我这里都用到了:
1.全部图表绘制都有动画渲染的情况
2.单个图表显示超多数据的情况
第一个可以对echarts对象设置animate属性来关闭所有动画
animate:false
第二个需要设置progressive属性
progressive属性的作用如下:
渐进式渲染时每一帧绘制图形数量,设为 0 时不启用渐进式渲染,支持每个系列单独配置。
在图中有数千图形甚至好几万图形的时候,一下子把图形绘制出来,或者交互重绘的时候可能会造成界面的卡顿甚至假死,因此 ECharts 从 3.2.0 开始支持大量图形的渐进式渲染(progressive rendering),渲染的时候会把创建好的图形分到数帧中渲染,每一帧渲染只渲染指定数量的图形。
该配置项就是用于配置该系列每一帧渲染的图形数,默认是 400 个,可以根据图表图形复杂度的需要适当调整这个数字使得在不影响交互流畅性的前提下达到绘制速度的最大化。
比如在 lines 图或者平行坐标中线宽大于 1 的 polyline 绘制会很慢,这个数字就可以设置小一点,而线宽小于等于 1 的 polyline 绘制非常快,该配置项就可以相对调得比较大。
再说频繁操作dom导致的卡顿问题
首先感谢老大提供的的思路,这个问题可以和同步绘制一起来解决,在这里需要仔细的研究一下同步异步的问题,这个问题想清楚了,问题就解决了
在这里推荐阮一峰老师的JavaScript 运行机制详解:再谈Event Loop
JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。这个时候问题就出现了,当我在处理完数据传给图表的执行方法的时候我是这么写的:
var data = 处理好的数据;
for(var i=0;i<data.length;i++){
chart({id:'xxxx'+i,data:data[i]});
}
这条被循环执行的数据多的有可能是上千条,而且这还只是其中一个模块的数据,这样的话就是上千条的数据在主线程上排队,一个图表必须要等到上一个图表绘制完毕才会绘制下一个,并且在这个时候我其他的操作都是在等待图表绘制完成的,也就是说必须要等到所有图表绘制完毕,所有页面加载出来我才能去计算页码并将其赋值,这个期间目录页的大模块页码定位全都是空白的,而这时候由于要等待所有操作完成,且cpu这时候被占满,自然而然的就造成了页面的假死状态.既然同步渲染会造成假死,那么解决方案自然就有了:异步执行绘制图表方法
先看一下异步的运行机制
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
首先先确定哪些任务是要在主线程内执行的
数据的处理
组件的渲染(不包含图表)
页码的赋值
目录页的定位
这些主线程的任务都是可以同步进行的,且速度非常快,这样就避免了必须要等待所有图表渲染完成才能确定页码的尴尬
接下来是异步队列
通过es6的promise方法可以很轻松的实现图表异步的执行,关于promise大家也可以自行百度,在这里不出详细解释,不明白的同学暂时只需要知道这是一种js的异步变成解决方案就可以了;
打个比方,你有一个执行柱状图的方法,还有n个需要绘制柱状图的模块,在这里的解决方案是:
1.新建一个公共的图表执行方法的js文件,将所有图表方法都放在一起,然后按需引入
图表作为一个对象有两个字段:data和method
export const Chart = { data:[], method:function(obj){ //这里放绘制图表的方法 } };
注意这个data,他就相当于一个任务队列,当我处理完数据时,不是第一时间就去执行绘制的方法,而是将处理好需要图表渲染的数据添加到这个data的队列里,每一个用到该图表的模块都是如此,这样一来等数据处理结束data队列里就存着所有需要渲染的数据了,
这个时候组件照常渲染,页码照常出,不去渲染图表,卡顿假死的问题就解决了,虽然还没有图表,但是起码页面已经加载出来了,接下来要做的就是去将队列里的数据进行异步的执行了
最开始考虑过使用定时器延时去传递数据加载图表,像下面这样
for(var i=0;i<10000;i++){ setTimeout(function(){ chart(data[i]) },1000*i) }
其实这样也是可行的,每一个图表的渲染都延迟执行一秒,定时器其实也算是异步执行了,当所有的主线程走完再去执行定时器的方法,但这样的话相当于有10000个定时器在等待执行啊,虽然相隔一秒,不会造成卡顿,但显然不是最优方案,
所以最终使用的是 promise的方法,这样就变成了只有一个定时器,代码如下
// 异步执行图表 export const parmise = obj =>{ console.log('数据加载完毕,准备生成图表'); var p1 = new Promise(function(resolve){ resolve(0); }); p1.then(fun1); function continueFunc(_index){ var p2 = new Promise(function(resolve){ resolve(_index); }); p2.then(fun1); } function fun1(ind){ obj.chart(obj.arr[ind]); setTimeout(function(){ if(ind!=obj.arr.length-1){ ind++; continueFunc(ind); } },500); }; obj.chart(); obj.parevArrLen=obj.arr.length; };
vue的munted方法代表的是所有页面加载完成再去执行,在app.vue里把promise放在这里在合适不过了,当页面渲染完成异步执行图表绘制的方法,最大程度的解决卡顿问题
//先引入
import { parmise,chart } from './assets/js/chart.js'
//在mounted里执行
parmise(chart);
ok,到这里问题解决,基本上每次滑动滚轮时图表绘制两个左右,出图速度飞快,低配置机器也可正常运行;
最后接着上一篇的打印报告来说,因为之前试验过使用HTMLtopPDF打印,所以在写项目期间就没有进行过测试,当项目完成调试打印的时候才发现由于是多页面应用所以根本无法打印,由于HTMLtopPDF是后端的解决方案,我们在前端也不好调试,所以选择了前端打印pdf的方案,
查了许多资料后决定使用html2canvas 和 jsPDF结合使用来生成pdf
html2canvas : 通过遍历页面DOM结构,收集所有元素信息及相应样式,渲染出canvas image
jsPDF:可以通过文字和图片生成pdf
看了他们的作用相信观众老爷们也知道要怎么结合使用了,很简单在点击下载按钮时通过html2canvas将页面转换为canvas image然后通过jsPDF再进行pdf转换就ok了,接下来上简单的教程;
html2canvas
我们可以直接在浏览器端使用html2canvas,对整个或局部页面进行‘截图’。但这并不是真的截图,而是通过遍历页面DOM结构,收集所有元素信息及相应样式,渲染出canvas image。
由于html2canvas只能将它能处理的生成canvas image,因此渲染出来的结果并不是100%与原来一致。但它不需要服务器参与,整个图片都由客户端浏览器生成,使用很方便。
使用
使用的API也很简洁,下面代码可以将某个元素渲染成canvas:
html2canvas(element, { onrendered: function(canvas) { // canvas is the final rendered <canvas> element } });
通过onrendered方法,可以将生成的canvas进行回调,比如插入到页面中:
html2canvas(element, { onrendered: function(canvas) { document.body.appendChild(canvas); } });
做个小例子代码如下
<html> <head> <title>html2canvas example</title> <style type="text/css">...</style> </head> <body> <header> <nav> <ul> <li>one</li> ... </ul> </nav> </header> <section> <aside> <h3>it is a title</h3> <a href="">Stone Giant</a> ... </aside> <article> <img src="./Stone.png"> <h2>Stone Giant</h2> <p>Coming ... </p> <p>以一团石头...</p> </article> </section> <footer>write by linwalker @2017</footer> <script type="text/javascript" src="./html2canvas.js"></script> <script type="text/javascript"> html2canvas(document.body, { onrendered:function(canvas) { document.body.appendChild(canvas) } }) </script> </body> </html>
这个例子将页面body中的元素渲染成canvas,并插入到body中
jsPDF
jsPDF库可以用于浏览器端生成PDF。
文字生成PDF
使用方法如下:
// 默认a4大小,竖直方向,mm单位的PDF var doc = new jsPDF(); // 添加文本‘Download PDF’ doc.text('Download PDF!', 10, 10); doc.save('a4.pdf');
图片生成PDF
使用方法如下:
// 三个参数,第一个方向,第二个单位,第三个尺寸格式 var doc = new jsPDF('landscape','pt',[205, 115]) // 将图片转化为dataUrl var imageData = ‘data:image/png;base64,iVBORw0KGgo...’; doc.addImage(imageData, 'PNG', 0, 0, 205, 115); doc.save('a4.pdf');
文字与图片生成PDF
// 三个参数,第一个方向,第二个尺寸,第三个尺寸格式 var doc = new jsPDF('landscape','pt',[205, 155]) // 将图片转化为dataUrl var imageData = ‘data:image/png;base64,iVBORw0KGgo...’; //设置字体大小 doc.setFontSize(20); //10,20这两参数控制文字距离左边,与上边的距离 doc.text('Stone', 10, 20); // 0, 40, 控制文字距离左边,与上边的距离 doc.addImage(imageData, 'PNG', 0, 40, 205, 115); doc.save('a4.pdf')
生成pdf需要把转化的元素添加到jsPDF实例中,也有添加html的功能,但某些元素无法生成在pdf中,因此可以使用html2canvas + jsPDF的方式将页面转成pdf。通过html2canvas将遍历页面元素,并渲染生成canvas,然后将canvas图片格式添加到jsPDF实例,生成pdf。
html2canvas + jsPDF
单页
将demo1的例子修改下:
<script type="text/javascript" src="./js/jsPdf.debug.js"></script> <script type="text/javascript"> var downPdf = document.getElementById("renderPdf"); downPdf.onclick = function() { html2canvas(document.body, { onrendered:function(canvas) { //返回图片dataURL,参数:图片格式和清晰度(0-1) var pageData = canvas.toDataURL('image/jpeg', 1.0); //方向默认竖直,尺寸ponits,格式a4[595.28,841.89] var pdf = new jsPDF('', 'pt', 'a4'); //addImage后两个参数控制添加图片的尺寸,此处将页面高度按照a4纸宽高比列进行压缩 pdf.addImage(pageData, 'JPEG', 0, 0, 595.28, 592.28/canvas.width * canvas.height ); pdf.save('stone.pdf'); } }) } </script>
关于打印大概就写这些吧,详细的教程大家可以去自行百度超多的;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?