博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
html2image原理简述
阅读量:7064 次
发布时间:2019-06-28

本文共 5698 字,大约阅读时间需要 18 分钟。

前言

看到 star了,也一直很好奇html怎么转 image

那么就翻下源码,看下是如何实现的,其实一共就不到800行代码,还蛮容易读懂的

工作原理

使用svg的一个特性,允许在<foreignobject>标签中包含任意的html内容。(主要是 这个apidom转为svg) 所以,为了渲染那个dom节点,你需要采取以下步骤:

  1. 递归 clone 原始的 dom 节点
  2. 获取 节点以及子节点 上的 computed style,并将这些样式添加进新建的style标签中(不要忘记了clone 伪元素的样式)
  3. 嵌入网页字体
  • 找到所有的@font-face
  • 解析URL资源,并下载对应的资源
  • base64编码和内联资源 作为 data: URLS引用
  • 把上面处理完的css rules全部都放进<style>中,并把标签加入到clone的节点中去
  1. 内嵌图片(都转成dataUrl)
  • 内联图片src 的url 进 <img>元素
  • 背景图片 使用 background css 属性,类似fonts的使用方式
  1. 序列化 clone 的 dom 节点 为 svg
  2. 将xml包装到<foreignobject>标签中,放入svg中,然后将其作为data: url
  3. 将png内容或原始数据作为uint8array获取,使用svg作为源创建一个img标签,并将其渲染到新创建的canvas上,然后把canvas转为base64
  4. 完成

核心API

import domtoimage from 'dom-to-image'复制代码

domtoimage 有如下一些方法:

* toSvg (`dom` 转 `svg`)    * toPng (`dom` 转 `png`)    * toJpeg (`dom` 转 `jpg`)    * toBlob (`dom` 转 `blob`)    * toPixelData (`dom` 转 像素数据)复制代码

见名知意,名字取得非常好

下面我挑一个toPng来简单解析一下原理,其他的原理也都是类似的


分析 toPng 原理

尽量挑最核心的讲,希望不会显得很繁琐,了解核心思想就好

下面介绍几个核心函数:

  • toPng (包装了draw函数,没啥意义)
  • Draw (dom => canvas)
  • toSvg (dom => svg)
  • cloneNode (clone dom树和css样式)
  • makeSvgDataUri (dom => svg => data:uri)

调用顺序为

toPng 调用 DrawDraw 调用 toSvgtoSvg 调用 cloneNode复制代码

toPng方法:

// 里面其实就是调用了 draw 方法,promise返回的是一个canvas对象function toPng(node, options) {    return draw(node, options || {})        .then(function (canvas) {            return canvas.toDataURL();        });}复制代码

Draw方法

function draw(domNode, options) {    // 将 dom 节点转为 svg(data: url形式的svg)    return toSvg(domNode, options)            // util.makeImage 将 canvas 转为 new Image(uri)        .then(util.makeImage)        .then(util.delay(100))        .then(function (image) {            var canvas = newCanvas(domNode);            canvas.getContext('2d').drawImage(image, 0, 0);            return canvas;        });    // 创建一个空的 canvas 节点    function newCanvas(domNode) {        var canvas = document.createElement('canvas');        canvas.width = options.width || util.width(domNode);        canvas.height = options.height || util.height(domNode);		  ......        return canvas;    }}复制代码

toSvg方法

function toSvg (node, options) {    options = options || {}    // 设置一些默认值,如果option是空的话    copyOptions(options)    return (      Promise.resolve(node)        .then(function (node) {          // clone dom 树          return cloneNode(node, options.filter, true)        })        // 把字体相关的csstext 全部都新建一个 stylesheet 添加进去        .then(embedFonts)        // 处理img和background url('')里面的资源,转成dataUrl        .then(inlineImages)        // 把option 里面的一些 style 放进stylesheet里面        .then(applyOptions)        .then(function (clone) {          // node 节点序列化成 svg          return makeSvgDataUri(            clone,            // util.width 就是 getComputedStyle 获取节点的宽            options.width || util.width(node),            options.height || util.height(node)          )        })    )	  // 设置一些默认值    function applyOptions (clone) {		......      return clone    }  }复制代码

cloneNode 方法

function cloneNode (node, filter, root) {    if (!root && filter && !filter(node)) return Promise.resolve()    return (      Promise.resolve(node)        .then(makeNodeCopy)        .then(function (clone) {          return cloneChildren(node, clone, filter)        })        .then(function (clone) {          return processClone(node, clone)        })    )    // makeNodeCopy    // 如果不是canvas 节点的话,就clone    // 是的话,就返回 canvas转image的 img 对象    function makeNodeCopy (node) {      if (node instanceof HTMLCanvasElement) { return util.makeImage(node.toDataURL()) }      return node.cloneNode(false)    }    // clone 子节点 (如果存在的话)    function cloneChildren (original, clone, filter) {      var children = original.childNodes      if (children.length === 0) return Promise.resolve(clone)      return cloneChildrenInOrder(clone, util.asArray(children), filter).then(        function () {          return clone        }      )      // 递归 clone 节点      function cloneChildrenInOrder (parent, children, filter) {        var done = Promise.resolve()        children.forEach(function (child) {          done = done            .then(function () {              return cloneNode(child, filter)            })            .then(function (childClone) {              if (childClone) parent.appendChild(childClone)            })        })        return done      }    }        // 处理添加dom的css,处理svg    function processClone (original, clone) {      if (!(clone instanceof Element)) return clone      return Promise.resolve()        // 读取节点的getComputedStyle,添加进css中        .then(cloneStyle)        // 获取伪类的css,添加进css        .then(clonePseudoElements)        // 读取 input textarea 的value        .then(copyUserInput)        // 设置svg 的 xmlns        // 命名空间声明由xmlns属性提供。此属性表示
标记及其子标记属于名称空间为“http://www.w3.org/2000/svg”的XML方言 .then(fixSvg) .then(function () { return clone })复制代码

下面是这篇的重点 把 html 节点序列化成 svg

// node 节点序列化成 svg  function makeSvgDataUri (node, width, height) {    return Promise.resolve(node)      .then(function (node) {        node.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml')        // XMLSerializer 对象使你能够把一个 XML 文档或 Node 对象转化或“序列化”为未解析的 XML 标记的一个字符串。        // 要使用一个 XMLSerializer,使用不带参数的构造函数实例化它,然后调用其 serializeToString() 方法:        return new XMLSerializer().serializeToString(node)      })      // escapeXhtml代码是string.replace(/#/g, '%23').replace(/\n/g, '%0A')      .then(util.escapeXhtml)      .then(function (xhtml) {        return (          '
' + xhtml + '
' ) }) // 变成svg .then(function (foreignObject) { return ( '
' + foreignObject + '
' ) }) // 变成 data: url .then(function (svg) { return 'data:image/svg+xml;charset=utf-8,' + svg }) }复制代码

参考链接

转载地址:http://fyill.baihongyu.com/

你可能感兴趣的文章
Android之TextureView浅析
查看>>
activiti学习资料(架构描述)
查看>>
概率图常见模型
查看>>
Android JNI编程(二)——C语言的基本数据类型,输出函数,输入函数
查看>>
使用SwingBench 对Oracle RAC DB性能 压力测试
查看>>
前端学Markdown
查看>>
easyui datagrid 行右键生成 动态获取(toolbar) 按钮
查看>>
Hibernate实体关系映射(OneToMany、ManyToOne双边)——完整实例
查看>>
get方式和set方式提交时乱码
查看>>
正则表达式
查看>>
JavaBean,List,Map转成json格式
查看>>
(原+转)Ubuntu16.04软件中心闪退及wifi消失
查看>>
Linux下高并发socket最大连接数所受的各种限制
查看>>
java 设计模式 -- 责任链模式
查看>>
ATL接口返回类型&&ATL接口返回字符串BSTR*
查看>>
51 Nod 1008 N的阶乘 mod P【Java大数乱搞】
查看>>
空间统计之七:中心要素
查看>>
自己写的一部分斗地主的程序,没有去写界面,临时是用黑框来显示的
查看>>
nginx学习1
查看>>
mysql批量删除相同前缀的表格
查看>>