vue虚拟DOM渲染成Canvas

前言

在刷github的时候偶然发现一个有意思的库vnode2canvas,因为关于转canvas我们通常都会用到html2canvas。我们知道vue是通过vnode实现渲染工作,所以vnode2canvas实现了vnode -> canvas,简化了html2canvas vnode -> html -> canvas。

背景

下面是关于canvas的概述

canvas是一种立即模式的渲染方式,不会存储额外的渲染信息。从而Canvas润徐直接发送绘图命令到GPU。但若用它来构建用户界面,需要进行一个更高层次的抽象。例如一些简单的处理,比如当绘制一个异步加载的资源到一个元素上会出现问题,如在图片上绘制文本。在HTML中,由于元素存在顺序,以及css中存在z-index,因此是很容易实现的。dom渲染是一种保留模式,保留模式是一种声明性的API,用于维护绘制到其中对象的层次结构中。保留模式API的优点是,对于你的应用程序,他们通常更容易构建复杂的场景,例如DOM。通常这都带来性能成本,需要额外的内存来保存场景和更新场景,这可能会减慢速度。

源码分析

vnode处理

Vue.mixin({
  // ...
  created() {
    if (this.$options.renderCanvas) {
      // ...
      // 监听vnode中引用的变化,重新渲染
      this.$watch(this.updateCanvas, this.noop)
      // ...
    }
  },
  methods: {
    updateCanvas() {
      // 模拟Vue render 函数
      // 寻找实例中定义的 renderCanva 方法,并传入 createElement 方法
      let vnode = this.$options.renderCanvas.call(this._renderProxy, this.$createElement)
    }
  }
})

Vue通过render函数,传入createElement方法创造vnode,这里就是加了个监听函数,混入到Vue实例中。

canvas元素处理

同时作者还对render的vnode做了额外的约束,比如:

view/scrollView/scrollItem --> fillRect
text --> fillText
image --> drawImage

其中这些元素类分别都继承于一个Super类,并且由于它们各有不同的展示方式,因此它们分别实现自己的draw方法,做定制化的展示。

调用

renderCanvas(h) {
  return h('view', {
     style: {
       left: 10,
       top: 10,
       width: 100,
       height: 100
     }
  })
}

通过这样绘制 canvas 布局最基础的写法是为canvas 元素传入一系列坐标点和相关的基础宽高。

通过作者相关文章,了解到作者还有别的方案去处理,因为上面写法还是有些不方便,其一就是写一个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需要的定义形式。并以变量的形式注入到组件中。

结语

上面就是我与这个库的理解,这个库在一些卡片生成的业务场景下还是有很大的应用空间。

下面是例子地址: