项目概述一个基于vue的虚拟dom插件库,根据Vue的render函数,直接将Vue生成的Vnode渲染到canvas中。支持常规的滚动操作和一些基本的元素事件绑定。Demo地址:https://muwoo.github.io/vnode2canvas/背景从一个小需求开始:有一天,产品提出这样一个需求,需要做一个微信活动页面,可以分享,包含用户相关信息的图片。这个信息需要从界面获取,每个人不一样。第一次遇到这种需求,基本都会去canvasAPI做渲染功能。本案例的步骤大致如下:编写一系列dom模板标签,将模板渲染成dom标签,开始抓取dom元素,绘制canvascanvas渲染图片面临的主要问题是复用性太差,其次是性能问题。用户看到的界面不一定和官方渲染的界面一样,可能存在渲染差异。作为一个追求前端的人,当然要想想有没有更好的办法。所以我了解了一个库,比如html2canvas。但是总觉得还是要转换成dom再画,感觉性??能和稳定性都不是很好。我们知道vue是通过vnode实现不同终端的渲染,那么是否可以通过vnode实现canvas的渲染呢?也就是说没有vnode->html->canvas而是直接vnode->canvas。同时利用vue的数据驱动实现绘图的数据驱动。有了想法,我们就开始实施吧。研究文章对此有详细介绍:60FPSonthemobileweb这里简单总结一下:canvas是一种即时模式的渲染方式,不存储额外的渲染信息。Canvas受益于即时模式,允许将绘图命令直接发送到GPU。但是如果你用它来构建用户界面,你需要更高层次的抽象。比如一些简单的处理,比如将异步加载的资源绘制到元素上,就会出现问题,比如在图片上绘制文字。在HTML中,由于CSS中元素的顺序和z-index,这很容易实现。dom渲染是一种保留模式,一种声明式API,用于维护绘制到其中的对象的层次结构。保留模式API的优点是它们通常更容易为您的应用程序构建复杂的场景,例如DOM。通常这是以性能成本为代价的,需要额外的内存来保存和更新场景,这可能很慢。canvas绘图页的研究好像早就有人做了。而且表现还是很不错的。那我们就得试一试,看看我们的想法能不能实现!越来越期待了……开始渲染canvas其实也是一种尝试。既然前辈们的实践已经足够了,我们就站在巨人的肩膀上,实现一个基于vue的数据驱动的canvas渲染。去做就对了!(这里只提供思路,不讨论具体的实现细节,因为实现起来有点复杂,有兴趣的可以参考我的项目实现,或者一起讨论。)vue源码应该知道Vue使用了render函数,传入createElement方法构造一个vnode,通过发布-订阅的方式监听数据,重新生成vnode。vnode最终转化为各个平台需要的视图。而我们所要做的就是从vnode层开始。因此,我们基于Vue源码实现一个监听函数,混入到Vue实例中:Vue.mixin({//...created(){if(this.$options.renderCanvas){//...//监听vnode中引用的变化,重新渲染this.$watch(this.updateCanvas,this.noop)//...}},methods:{updateCanvas(){//模拟Vuerender函数//寻找在实例renderCanvas方法中定义的,并传入createElement方法letvnode=this.$options.renderCanvas.call(this._renderProxy,this.$createElement)}})这样我们就可以在组件内部愉快的使用了:renderCanvas(h){returnh(...)}canvaselementhandlestherendervnode我们需要做一些额外的约束,也就是说我们需要什么样的渲染标签来渲染对应的canvas元素(例如:view/scrollView)/scrollItem-->fillRecttext-->fillTextimage-->drawImage其中,这些元素类继承自一个Super类,由于显示方式不同,所以实现了继承自己的自定义显示绘制方法。绘制对象的布局机制实现绘制画布布局最基本的写法是为canvas元素传入一系列坐标点和相关的基本宽高,所以在实际项目中可能会这样写:renderCanvas(h){returnh('view',{style:{left:10,top:10,width:100,height:100}})}这样写真的很不方便,有几种解决方法,一种是使用css-layout来管理它。css-layout支持的转换属性如下:这只是一层转换,帮助我们更好的用css的思维来写canvas,但是如果我们对js中css的写法不满意,其实我们也可以写一个webpackloader来加载外部css:constcss=require('css')module.exports=function(source,other){letcssAST=css.parse(source)letparseCss=newParseCss(cssAST)parseCss.parse()this.cacheable();这个。回调(空,parseCss.declareStyle(),其他);};classParseCss{构造函数(cssAST){this.rules=cssAST.stylesheet.rulesthis.targetStyle={}}parse(){this.rules.forEach((规则)=>{letselector=rule.selectors[0]this.targetStyle[selector]={}rule.declarations.forEach((dec)=>{this.targetStyle[selector][dec.property]=this.formatValue(dec.value)})})}formatValue(string){string=string.replace(/"/g,'').replace(/'/g,'')returnstring.indexOf('px')!==-1?parseInt(string):string}declareStyle(property){return`window.${property||'vStyle'}=${JSON.stringify(this.targetStyle)}`}}简单的说:最主要的是将css文件转换成AST语法树,再将语法树转换成canvas需要的定义形式,以变量的形式注入到组件中。要实现列表滚动,如果我们有很多元素,并且需要滚动,就要解决canvas内部元素的滚动问题。这里我选择使用ZyngaScroller来模拟用户的滚动方式,通过他返回的滚动坐标重新绘制画布。有兴趣的可以参考我这里的实现:https://github.com/muwoo/vnode2canvas/blob/master/src/core/shape/scrollView.js事件模拟对于点击、触摸等dom事件的模拟,我们采用的解决方案是根据点击区域进行检测,找到底部元素,递归找到父元素并触发相应的事件处理程序,从而模拟事件冒泡。详细实现可以参考这里:https://github.com/muwoo/vnode2canvas/blob/master/src/core/event.js最后,canvas绘图页面也是一个创新的尝试。我希望这里的研究能对你有所启发。也欢迎您的PR。这里也做了很多性能优化,限于篇幅就不赘述了。如果大家有兴趣,也可以一起探讨。最后:它并不意味着完全取代基于DOM的渲染,它仍然需要文本输入、复制/粘贴、可访问性和SEO。由于这些原因,我们可以结合使用画布和基于DOM的渲染。
