Dom-to-image

17 篇文章 0 订阅
16 篇文章 1 订阅

Notion – The all-in-one workspace for your notes, tasks, wikis, and databases.

使用svg的一个特性,允许在<foreignobject>标签中包含任意的html内容。

  • 主要是 [XMLSerializer |dom转为svg]

  • dom 转换为 XHTML

    • XMLSerializer 对象使你能够把一个 XML 文档或 Node 对象转化或“序列化”为未解析的 XML 标记的一个字符串要使用一个 XMLSerializer,使用不带参数的构造函数实例化它 ,然后调用其 serializeToString() 方法;

    • 递归去克隆dom节点

      • 遇到canvas转为image对象
      • 提取元素computed样式,并且插到新建的style标签上面,对于":before,:after"这些伪元素,会提取其样式,放到新建样式名中并且插入到新建的style标签中,供所属的节点使用。
      • 处理输入内容和svg。
    • 插入字体

      • 获取所有样式表并处理为数组,提取包含 rule.type === CSSRule.FONT_FACE_RULE 规则,再提取包含 src 的 rules。
      • 下载资源,将资源转为dataUrl并给 src 使用。
    • 处理图片  图片都处理为dataUrl

    • 示例一

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <title>Document</title>
        </head>
        <body onload="load()">
          <div id="long">
            <span>1111</span>
            <h1>222222</h1>
            hello SeriousLose</div>
        </body>
      </html>
      <script>
        function load() {
          let long = document.getElementById('long');
          let xmlStr = new XMLSerializer();
          let xml = xmlStr.serializeToString(long);
          console.log(xml);
          let dom = `data:image/svg+xml;charset=utf-8,<svg viewBox="0 0 200 200" xmlns="<http://www.w3.org/2000/svg>">
            <style>
              polygon { fill: black }
      
              div {
                color: white;
                font:12px serif;
                height: 100%;
                overflow: auto;
              }
            </style>
      
            <polygon points="5,5 195,10 185,185 10,195" />
      
            <!-- Common use case: embed HTML text into SVG -->
            <foreignObject x="20" y="20" width="160" height="160">
            ${xml}
            </foreignObject>
          </svg>`;
      
          let img = new Image();
          img.src = dom;
          document.body.appendChild(img);
          img.onload = () => {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            canvas.width = img.naturalWidth;
            canvas.height = img.naturalHeight;
            ctx.drawImage(img, 0, 0);
            canvas.toBlob(function (blob) {
              let url = URL.createObjectURL(blob);
              const a = document.createElement('a');
              a.style.display = 'none';
              document.body.appendChild(a);
              a.href = url;
              a.download = 'image';
              a.click();
              window.URL.revokeObjectURL(url);
            }, 'image/png');
          };
        }
      </script>
      
  • 使用svg中的<foreignobject>

    • 序列化dom节点为字符串,然后在 foreignObject 嵌入转换好的字符串,
    • foreignObject 能够在 svg 内部嵌入XHTML,再将svg处理为dataUrl数据
  • 用 canvas 渲染出处理好的 dataUrl 数据

  • canvas转化为 bold类型;

源码地址 dom-to-image/dom-to-image.js at master · tsayen/dom-to-image · GitHub

// toPng 方法

  function toPng(node, options) {
    return draw(node, options || {}).then(function (canvas) {
      return canvas.toDataURL();
    });
  }
// draw 方法

  function draw(domNode, options) {
    // 将dom节点转为svg
    return toSvg(domNode, options)
      // 拿到的svg是image data URL,这里进一步通过svg创建图片
      .then(util.makeImage)
      .then(util.delay(100))
      .then(function (image) {
        // 通过图片创建canvas并返回
        var canvas = newCanvas(domNode);
        canvas.getContext("2d").drawImage(image, 0, 0);
        return canvas;
      });
    // 新建canvas节点,处理dataUrl资源,和options参数
    function newCanvas(domNode) {
      var canvas = document.createElement("canvas");
      canvas.width = options.width || util.width(domNode);
      canvas.height = options.height || util.height(domNode);

      if (options.bgcolor) {
        var ctx = canvas.getContext("2d");
        ctx.fillStyle = options.bgcolor;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
      }

      return canvas;
    }
  }
// toSvg

  function toSvg(node, options) {
    options = options || {};
    copyOptions(options);
    return Promise.resolve(node)
      .then(function (node) {
       // 递归克隆dom节点
        return cloneNode(node, options.filter, true);
      })
       // 嵌入字体,找出所有font-face样式,添加入一个新的style里面
      .then(embedFonts)
       // 将图片链接转换为dataUrl形式使用
      .then(inlineImages)
       // 将options里面的一些style放进style里面
      .then(applyOptions)
      .then(function (clone) {
        // 创建svg,将dom节点通过 XMLSerializer().serializeToString() 序列化为字符串
        // 然后用 foreignObject 包裹,就能将dom转为svg。
        return makeSvgDataUri(
          clone,
          options.width || util.width(node),
          options.height || util.height(node)
        );
      });
    // 处理一些options的样式
    function applyOptions(clone) {
      if (options.bgcolor) clone.style.backgroundColor = options.bgcolor;

      if (options.width) clone.style.width = options.width + "px";
      if (options.height) clone.style.height = options.height + "px";

      if (options.style)
        Object.keys(options.style).forEach(function (property) {
          clone.style[property] = options.style[property];
        });

      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);
      });

    function makeNodeCopy(node) {
      // 遇到canvas转为image对象
      if (node instanceof HTMLCanvasElement)
        return util.makeImage(node.toDataURL());
      // 克隆第一层
      return node.cloneNode(false);
    }
    // 克隆子节点
    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;
        }
      );
      // 递归克隆
      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;
      }
    }

    function processClone(original, clone) {
      if (!(clone instanceof Element)) return clone;

      return Promise.resolve()
        .then(cloneStyle)
        .then(clonePseudoElements)
        .then(copyUserInput)
        .then(fixSvg)
        .then(function () {
          return clone;
        });
      // 克隆节点上面所有使用的样式。
      function cloneStyle() {
        // 顺便提提,为什么不用style,因为如果什么样式也没有设置的话,style是光秃秃的
        // 而getComputedStyle则能获取到应用在节点上面所有样式
        copyStyle(window.getComputedStyle(original), clone.style);

        function copyStyle(source, target) {
          if (source.cssText) target.cssText = source.cssText;
          else copyProperties(source, target);

          function copyProperties(source, target) {
            util.asArray(source).forEach(function (name) {
              target.setProperty(
                name,
                source.getPropertyValue(name),
                source.getPropertyPriority(name)
              );
            });
          }
        }
      }
      // 提取伪类样式,放到css
      function clonePseudoElements() {
        [":before", ":after"].forEach(function (element) {
          clonePseudoElement(element);
        });

        function clonePseudoElement(element) {
          var style = window.getComputedStyle(original, element);
          var content = style.getPropertyValue("content");

          if (content === "" || content === "none") return;

          var className = util.uid();
          clone.className = clone.className + " " + className;
          var styleElement = document.createElement("style");
          styleElement.appendChild(
            formatPseudoElementStyle(className, element, style)
          );
          clone.appendChild(styleElement);
          function formatPseudoElementStyle(className, element, style) {
            var selector = "." + className + ":" + element;
            var cssText = style.cssText
              ? formatCssText(style)
              : formatCssProperties(style);
            return document.createTextNode(selector + "{" + cssText + "}");

            function formatCssText(style) {
              var content = style.getPropertyValue("content");
              return style.cssText + " content: " + content + ";";
            }

            function formatCssProperties(style) {
              return util.asArray(style).map(formatProperty).join("; ") + ";";

              function formatProperty(name) {
                return (
                  name +
                  ": " +
                  style.getPropertyValue(name) +
                  (style.getPropertyPriority(name) ? " !important" : "")
                );
              }
            }
          }
        }
      }
      // 处理输入内容
      function copyUserInput() {
      ...
      }
      // 处理svg,创建命名空间
      function fixSvg() {
        if (!(clone instanceof SVGElement)) return;
        clone.setAttribute("xmlns", "<http://www.w3.org/2000/svg>");
        ...
      }
    }
  }
// makeSvgDataUri

  function makeSvgDataUri(node, width, height) {
    return (
      Promise.resolve(node)
        .then(function (node) {
          // 将dom转换为字符串
          node.setAttribute("xmlns", "<http://www.w3.org/1999/xhtml>");
          return new XMLSerializer().serializeToString(node);
        })
        .then(util.escapeXhtml)
        .then(function (xhtml) {
          return (
            '<foreignObject x="0" y="0" width="100%" height="100%">' +
            xhtml +
            "</foreignObject>"
          );
        })
        /**
         * 顺带提一提
         * 不指定xmlns命名空间是不会渲染的
         * xmlns="<http://www.w3.org/2000/svg>"
         */

        .then(function (foreignObject) {
          return (
            '<svg xmlns="<http://www.w3.org/2000/svg>" width="' +
            width +
            '" height="' +
            height +
            '">' +
            foreignObject +
            "</svg>"
          );
        })
        .then(function (svg) {
          return "data:image/svg+xml;charset=utf-8," + svg;
        })
    );
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值