Краткое введение в принцип html2image

внешний интерфейс JavaScript

предисловие

ВидетьТиДжей Богзвездаdom-to-image, всегда было любопытноhtmlкак повернутьimage

Затем загляните в исходный код и посмотрите, как он реализован.На самом деле, всего в нем меньше 800 строк кода, который довольно легко читается.

Принцип работы

использоватьsvgособенность, которая позволяет<foreignobject>Произвольные тегиhtmlсодержание. (в основномXMLSerializer | MDNэтоapiБудуdomПеревести вsvg) Итак, для того, чтобы отобразить этоdomузел, необходимо выполнить следующие действия:

  1. рекурсияcloneоригинальныйdomузел
  2. Получите узел и дочерние узлы наcomputed styleи добавьте эти стили в новый тег стиля (не забудьте стили псевдоэлемента клонирования)
  3. Встроить веб-шрифты
  • найти все@font-face
  • Проанализируйте ресурс URL и загрузите соответствующий ресурс.
  • кодирование base64 и встраивание ресурсов какdata:ссылка на URL
  • закончить вышеcss rulesположить все в<style>и добавьте метку к узлу клона
  1. Встроенные изображения (все преобразованы в dataUrl)
  • Встроить URL-адрес источника изображения в<img>元素
  • Фоновое изображение Используйте свойство background css, аналогично тому, как используются шрифты.
  1. Сериализовать узел клона dom какsvg
  2. оберните xml в<foreignobject>маркировать, ставитьsvg, затем используйте его какdata: url
  3. получить содержимое png или необработанные данные какuint8arrayчтобы получить, создайте его, используя svg в качестве источникаimglabel и визуализировать его во вновь созданный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 (обернул функцию рисования, бессмысленно)
  • Рисовать (дом => холст)
  • toSvg (дом => svg)
  • cloneNode (клонирование дерева dom и стилей css)
  • makeSvgDataUri (dom => svg => данные: uri)

Последовательность вызова

toPng 调用 Draw
Draw 调用 toSvg
toSvg 调用 cloneNode

Метод toPng:

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

Метод рисования

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属性提供。此属性表示<svg>标记及其子标记属于名称空间为“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 (
          '<foreignObject x="0" y="0" width="100%" height="100%">' +
          xhtml +
          '</foreignObject>'
        )
      })
      // 变成svg
      .then(function (foreignObject) {
        return (
          '<svg xmlns="http://www.w3.org/2000/svg" width="' +
          width +
          '" height="' +
          height +
          '">' +
          foreignObject +
          '</svg>'
        )
      })
      // 变成 data: url
      .then(function (svg) {
        return 'data:image/svg+xml;charset=utf-8,' + svg
      })
  }

Ссылка на ссылку