preface

When I saw TJ star, I was always curious about how to convert HTML to image

So let’s look at the source code and see how it’s implemented. It’s actually less than 800 lines of code, which is pretty easy to read. Okay

The working principle of

Using a feature of SVG, you can include arbitrary HTML content in the

tag. (mainly XMLSerializer | MDN this API will turn SVG dom) so, in order to render the dom node, you need to take the following steps:

  1. recursivecloneThe originaldomnode
  2. Gets the values on the node and its childrencomputed styleAnd add these styles to the new style tag (don’t forget the clone pseudo-element style)
  3. Embed web fonts
  • Find all@font-face
  • Parse the URL resources and download the corresponding resources
  • Base64 encoding and inline resources asdata:URLS references
  • To finish up the topcss rulesPut it all in<style>Add the tag to the clone node
  1. Embedded images (all converted to dataUrl)
  • Inline image SRC URL intoThe < img > element
  • Background images use the background CSS property, similar to how fonts are used
  1. The DOM node of serialized Clone issvg
  2. Wrap the XML to<foreignobject>In the tagsvg, and then treat it asdata: url
  3. PNG content or raw data asuint8arrayGet and create one using SVG as the sourceimgTag and render it to the newly createdcanvasGo on, and thencanvastobase64
  4. complete

Core API

import domtoimage from 'dom-to-image'
Copy the code

Domtoimage has the following methods:

* toSvg (dom toSvg) * toPng (dom toPng) * toJpeg (dom to JPG) * toBlob (dom toBlob) * toPixelData (dom) To pixel data)Copy the code

The name is very good

Let me pick a toPng to briefly analyze the principle, the other principles are similar


Analyze toPng principle

Try to pick the most core to speak, I hope it will not appear very tedious, understand the core idea

Here are some core functions:

  • ToPng (wrapped draw function, meaningless)
  • Draw (dom => canvas)
  • ToSvg (dom > SVG)
  • CloneNode (Clone DOM Tree and CSS Styles)
  • MakeSvgDataUri (dom => SVG => data: URI)

Call order:

ToPng call Draw Draw call toSvg toSvg callcloneNode
Copy the code

ToPng method:

// The draw method is called and the promise returns a canvas
function toPng(node, options) {
    return draw(node, options || {})
        .then(function (canvas) {
            return canvas.toDataURL();
        });
}
Copy the code

The Draw method

function draw(domNode, options) {
    // Convert dom nodes to SVG (data: URl-form SVG)
    return toSvg(domNode, options)    
        // util. MakeImage Converts canvas to 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;
        });

    // Create an empty canvas node
    function newCanvas(domNode) {
        var canvas = document.createElement('canvas'); canvas.width = options.width || util.width(domNode); canvas.height = options.height || util.height(domNode); . return canvas; }}Copy the code

ToSvg method

  function toSvg (node, options) {
    options = options || {}
    // Set some default values if option is null
    copyOptions(options)

    return (
      Promise.resolve(node)
        .then(function (node) {
          / / clone dom tree
          return cloneNode(node, options.filter, true)})// Add a new stylesheet to all the csSText related fonts
        .then(embedFonts)
        // Handle resources in img and background URLS (") and convert them to dataUrl
        .then(inlineImages)
        // Put some of the styles in option into the stylesheet
        .then(applyOptions)
        .then(function (clone) {
          // Serialize the node to SVG
          return makeSvgDataUri(
            clone,
            // util.width is getComputedStyle to get the width of the node
            options.width || util.width(node),
            options.height || util.height(node)
          )
        })
    )
	  // Set some default values
    function applyOptions (clone) {... return clone } }Copy the code

CloneNode method

  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
    // If it is not canvas, clone it
    // If so, return the canvas to image img object
    function makeNodeCopy (node) {
      if (node instanceof HTMLCanvasElement) { return util.makeImage(node.toDataURL()) }
      return node.cloneNode(false)}// Clone child node (if present)
    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
        }
      )
      // Recursively clone nodes
      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
      }
    }
    
    // Handle CSS with DOM added, handle SVG
    function processClone (original, clone) {
      if(! (cloneinstanceof Element)) return clone

      return Promise.resolve()
        // Get the getComputedStyle of the node and add it to the CSS
        .then(cloneStyle)
        // Get the pseudo-class CSS and add it to the CSS
        .then(clonePseudoElements)
        // Read the value of input textarea
        .then(copyUserInput)
        // Set XMLNS for SVG
        // Namespace declarations are provided by the XMLNS attribute. This attribute said < SVG > tag and its children mark belongs to the namespace XML dialect for "http://www.w3.org/2000/svg"
        .then(fixSvg)
        .then(function () {
          return clone
        })

Copy the code

The focus of this article is to serialize HTML nodes to SVG

  // Serialize the node to SVG
  function makeSvgDataUri (node, width, height) {
    return Promise.resolve(node)
      .then(function (node) {
        node.setAttribute('xmlns'.'http://www.w3.org/1999/xhtml')

        The XMLSerializer object enables you to convert or "serialize" an XML document or Node object into a string of unparsed XML tags.
        // To use an XMLSerializer, instantiate it with a constructor that takes no arguments and call its serializeToString() method:
        return new XMLSerializer().serializeToString(node)
      })
      / / escapeXhtml code is a string. The replace (/ # / g, "% 23"). The replace (\ n/g, '% 0 a')
      .then(util.escapeXhtml)
      .then(function (xhtml) {
        return (
          '<foreignObject x="0" y="0" width="100%" height="100%">' +
          xhtml +
          '</foreignObject>')})/ / into SVG
      .then(function (foreignObject) {
        return (
          '<svg xmlns="http://www.w3.org/2000/svg" width="' +
          width +
          '" height="' +
          height +
          '" >' +
          foreignObject +
          '</svg>')})// Change to data: URL
      .then(function (svg) {
        return 'data:image/svg+xml; charset=utf-8,' + svg
      })
  }

Copy the code

Refer to the link

  • CSSStyleDeclaration. SetProperty () – | MDN Web API interface

  • dom-to-image

  • XML DOM-xmlSerializer object