项目概述
一个基于Vue的virtual dom插件库,依据Vue render 函数的写法,直接将Vue天生的Vnode衬着到canvas中。支撑通例的转动操作和一些基本的元素事宜绑定。
github 地点: github
demo实例:demo
背景
从一个小的需求提及:某一天,产物提了一个如许的需求,须要制造一个微信运动页,运动页能够分享包括用户相干信息的图片。这些信息是须要从接口取的,而且每个人都不一样。第一次遇到这类需求的时刻,基本上都邑去手撸canvasAPI去做衬着功用,这类状况的步骤大抵以下:
- 写一大串 dom template 标签
- 衬着template成dom标签
- 最先捕获dom元素,绘制canvas
- canvas 衬着图片
面对的重要题目是复用性太差,其次是机能上也有题目,用户看到的界面不一定和正式衬着出的界面一致,能够存在衬着差别。作为一个有寻求的前端,固然得想想看有无更好的要领。于是乎相识到了一个html2canvas 如许一个库。然则老是觉得照样要转成dom再去绘制,而且觉得机能和稳定性也不是很好。
我们晓得vue经由过程vnode完成了对差别端的衬着事情,那有无能够经由过程vnode完成对canvas的衬着呢?也就是说,没有vnode -> html -> canvas 而是直接vnode -> canvas。 同时应用vue的数据驱动,来到达绘制的数据驱动。主意有了,下面最先实行。
调研
这篇文章对此有细致的引见:60 FPS on the mobile web 这里简朴的归纳综合一下:
canvas是一种马上情势的衬着体式格局,不会存储分外的衬着信息。Canvas 受益于马上情势,许可直接发送画图敕令到 GPU。但若用它来构建用户界面,须要举行一个更高条理的笼统。比方一些简朴的处置惩罚,比方当绘制一个异步加载的资本到一个元素上时会出现题目,如在图片上绘制文本。在HTML中,由于元素存在递次,以及 CSS 中存在 z-index,因而是很轻易完成的。
dom衬着是一种保留情势,保留情势是一种声明性API,用于保护绘制到个中的对象的条理构造。保留情势 API 的长处是,关于你的应用顺序,他们一般更轻易构建庞杂的场景,比方 DOM。一般这都邑带来机能本钱,须要分外的内存来保留场景和更新场景,这能够会很慢。
看来canvas绘制页面的研讨,良久之前就已有人付出过研讨了。而且机能照样很不错的。那我们更要碰运气,究竟我们的主意能不能完成了!愈来愈期待….
最先
canvas 的衬着实在也是一种尝试,既然前人以及做了充足的实践,那末我们便站在伟人的肩膀上去基于vue来完成一个数据驱动的canvas衬着。说做就做!(我们这里只提供思绪,不做详细完成细节的议论,由于完成起来有点庞杂,假如有兴致能够参考我的项目完成,或许一同交换讨论 )
处置惩罚vnode
熟习Vue源码的应当都晓得,Vue经由过程render
函数,传入createElement
要领来构造出一个vnode
,经由过程宣布--定阅
情势来完成对数据的监听,从新天生vnode
。我们要做的就是在vnode
这一层最先。所以,我们基于Vue源码的体式格局,完成一个监听函数,并混入Vue实例中:
Vue.mixin({
// ...
created() {
if (this.$options.renderCanvas) {
// ...
// 监听vnode中援用的变化,从新衬着
this.$watch(this.updateCanvas, this.noop)
// ...
}
},
methods: {
updateCanvas() {
// 模仿Vue render 函数
// 寻觅实例中定义的 renderCanvas 要领,并传入createElement要领
let vnode = this.$options.renderCanvas.call(this._renderProxy, this.$createElement)
}
})
如许我们就能够兴奋的在组件内部运用:
renderCanvas (h) {
return h(...)
}
canvas 元素处置惩罚
render 的vnode我们须要做分外的一些束缚,也就是说我们须要怎样的衬着标签,来衬着对应的canvas元素(举个🌰):
- view/scrollView/scrollItem –> fillRect
- text –> fillText
- image –> drawImage
个中这些元素类离别都继续于一个Super类,而且由于它们各有差别的展现体式格局,因而它们离别完成本身的draw要领,做定制化的展现。
绘制对象的规划机制完成
绘制 canvas 规划最基本的写法是为canvas 元素传入一系列坐标点和相干的基本宽高,如许写到现实项目中多是如许的:
renderCanvas(h) {
return h('view', {
style: {
left: 10,
top: 10,
width: 100,
height: 100
}
})
}
如许写确切有点不方便保护,现在有好几种处理计划,一种是运用css-layout
去做治理。css-layout
支撑的转换属性以下:
如许也只是做了一层转换,帮我们更好的用css头脑去写canvas,然则假如我们很不爽css in js
的写法,实在我们还能够写一个webpack loader 来加载外部css:
const css = require('css')
module.exports = function (source, other) {
let cssAST = css.parse(source)
let parseCss = new ParseCss(cssAST)
parseCss.parse()
this.cacheable();
this.callback(null, parseCss.declareStyle(), other);
};
class ParseCss {
constructor(cssAST) {
this.rules = cssAST.stylesheet.rules
this.targetStyle = {}
}
parse () {
this.rules.forEach((rule) => {
let selector = 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, '')
return string.indexOf('px') !== -1 ? parseInt(string) : string
}
declareStyle (property) {
return `window.${property || 'vStyle'} = ${JSON.stringify(this.targetStyle)}`
}
}
重要也就是将 css 文件转成AST
语法树,以后再对语法树做转换,转成canvas
须要的定义情势。并以变量的情势注入到组件中。
完成列表转动
假如我们的元素许多,须要转动时,我们必需处理canvas
内部元素转动的题目。这里我挑选了运用Zynga Scroller 来模仿用户转动要领,经由过程他返回的转动坐标点,来对canvas举行重绘。
细致的参考这里
事宜模仿
关于click,touch
等dom事宜的模仿,我们采纳的计划是依据点击地区举行检测,并找出最底层的元素,递归寻觅父元素并触发对应事宜处置惩罚顺序,从而模仿事宜冒泡。
细致的完成能够参考这里
末了
canvas绘制页面也是一种立异的尝试,愿望这里的研讨对你有启示,也欢迎您的PR。这里也做了许多机能优化,限于篇幅不在赘述了,有兴致也能够一同讨论。
末了:它并不意味着完整庖代基于DOM的衬着,这依然须要文本输入,复制/粘贴,可接见性和SEO。
出于这些缘由,我们能够运用canvas和基于DOM的衬着的组合。