将你的 Virtual dom 渲染成 Canvas

项目概述

一个基于Vue的virtual dom插件库,依据Vue render 函数的写法,直接将Vue天生的Vnode衬着到canvas中。支撑通例的转动操作和一些基本的元素事宜绑定。

github 地点: github

demo实例:demo

《将你的 Virtual dom 渲染成 Canvas》

背景

从一个小的需求提及:某一天,产物提了一个如许的需求,须要制造一个微信运动页,运动页能够分享包括用户相干信息的图片。这些信息是须要从接口取的,而且每个人都不一样。第一次遇到这类需求的时刻,基本上都邑去手撸canvasAPI去做衬着功用,这类状况的步骤大抵以下:

  1. 写一大串 dom template 标签
  2. 衬着template成dom标签
  3. 最先捕获dom元素,绘制canvas
  4. 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元素(举个🌰):

  1. view/scrollView/scrollItem –> fillRect
  2. text –> fillText
  3. image –> drawImage

个中这些元素类离别都继续于一个Super类,而且由于它们各有差别的展现体式格局,因而它们离别完成本身的draw要领,做定制化的展现。

绘制对象的规划机制完成

绘制 canvas 规划最基本的写法是为canvas 元素传入一系列坐标点和相干的基本宽高,如许写到现实项目中多是如许的:

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

如许写确切有点不方便保护,现在有好几种处理计划,一种是运用css-layout去做治理。css-layout支撑的转换属性以下:

《将你的 Virtual dom 渲染成 Canvas》

如许也只是做了一层转换,帮我们更好的用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的衬着的组合。

    原文作者:muwoo
    原文地址: https://segmentfault.com/a/1190000015676408
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞