本身着手完成一个html2canvas

媒介

昨天写了新手指导动画的4种完成体式格局,
内里用到了 html2canvas 因而就趁便了解了一下完成思绪.

也许就是 应用 svgforeignObject 标签, 嵌入 dom, 末了再应用 canvas 绘制 svg. 从而完成终究目的.

先让人人看看结果

《本身着手完成一个html2canvas》

MDN示例

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
           '<foreignObject width="100%" height="100%">' +
           '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
             '<em>I</em> like' +
             '<span style="color:white; text-shadow:0 0 2px blue;">' +
             'cheese</span>' +
           '</div>' +
           '</foreignObject>' +
           '</svg>';

var DOMURL = window.URL || window.webkitURL || window;

var img = new Image();
var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svg);

img.onload = function () {
  ctx.drawImage(img, 0, 0);
  DOMURL.revokeObjectURL(url);
}

img.src = url;

MDN示例实在写的很清晰,不过也相对比较简朴一点, dom 是已构建好的字符串, 实在我以为全部历程内里最贫苦的就是构建 dom. 所以接下来,我们就来看看详细怎样完成吧

第一步 遍历目的节点的一切子元素,并构建对应的字符串

/**
 * 递归遍历一切子节点
 * @param element Document Element 要盘算的元素
 * @param isTop Boolean 是不是是最外层元素
**/
function renderDom (element, isTop) {
    let tag = element.tagName.toLowerCase()
    let str = `<${tag} `
    // 最外层的节点,须要加 xmlns 定名空间
    isTop && (str += `xmlns="http://www.w3.org/1999/xhtml" `)
    str += ` style="${getElementStyles(element)}">\n`

    if (element.children.length) {
        // 递归子元素
        for (let el of element.children) {
            str += renderDom(el)
        }
    } else {
        str += element.innerHTML
    }
    str += `</${tag}>\n`
    return str
}

这里只做了一个最简朴的处置惩罚,由因而简朴完成,许多特殊情况没斟酌进去(如:单标签, img等),有兴致的童鞋能够本身尝试完成看看.

最外层的元素, 须要加定名空间,不然没法辨认

这里用到的 getElementStyles 就是猎取元素的终究衬着款式,下一步会完成.

第二步, 猎取元素的终究衬着款式,并拼接成行内款式

一般的 dom 元素, 是没法直接放在 foreignObject 内里正确地衬着的, 由于还要涉及到父子元素直接的属性继续, 元素默许属性, 非行内款式没法衬着等题目.
所以我们要猎取每一个元素的终究衬着款式, 然后拼接成行内款式.

怎样猎取元素的终究衬着款式呢? 恰好,浏览器有供应一个 window.getComputedStyle() 要领能够做到.

// 盘算每一个 dom 的款式
// 这里原本应当直接用 Object.keys + forEach 遍历掏出的
// 然则不知道为何,遍历掏出的,会衬着不出来,应当是某些属性有题目
// 临时没空去排查那些有题目,所以现在先把经常使用的直接写死.
function getElementStyles (el) {
    let css = window.getComputedStyle(el)
    let style = ''
    // 尺寸相干
    style += `width:${css.width};`
    style += `height: ${css.height};`
    style += `line-height: ${css.lineHeight};`
    style += `max-height: ${css.maxHeight};`
    style += `min-height: ${css.minHeight};`
    style += `max-width: ${css.maxWidth};`
    style += `min-width: ${css.minWidth};`

    style += `font-size: ${css.fontSize};`
    // 色彩相干
    style += `color: ${css.color};`
    style += `background: ${css.background};`
    // 边框相干
    style += `border: ${css.border};`
    style += `box-sizing: ${css.boxSizing};`
    // 位置相干
    style += `margin: ${css.margin};`
    style += `padding: ${css.padding};`
    style += `position: ${css.position};`
    style += `left: ${css.left};`
    style += `right: ${css.right};`
    style += `top: ${css.top};`
    style += `bottom: ${css.bottom};`
    // 规划相干
    style += `display: ${css.display};`
    style += `flex: ${css.flex};`
    return style
}

第三步, 衬着 svg

把拼接好的 svg 字符串用 Blob 对象 new 出来(Blob真的是个很壮大的对象啊), 然后用 DOMURL.createObjectURL() 转换为 url,
有了url, 接下来就看人人自由发挥了. 能够直接下载,也能够在 canvas 里绘制. 或许看成图片直接插入到文档…


// 主进口函数
function shotScreen () {
    let target = document.querySelector('.content')
    let data = getSvgDomString(target)

    let DOMURL = window.URL || window.webkitURL || window;

    let img = new Image();
    let svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
    let url = DOMURL.createObjectURL(svg);

    img.src = url;
    document.body.appendChild(img)
}

// 盘算 svg 的字符串
function getSvgDomString (element) {
    return `
    <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">\n
       <foreignObject width="100%" height="100%">\n
          ${renderDom(element, 1)}
       </foreignObject>\n
   </svg>`
}

这里趁便给个绘制到 canvas 里的代码

//  假如想画到 canvas 内里
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let img = new Image();

img.onload = function () {
   ctx.drawImage(img, 0, 0);
   DOMURL.revokeObjectURL(url);
}

末了

参考文档:

MDN: 将 DOM 对象绘制到 canvas 中

MDN: foreignObject

完全的代码在这里,能够直接运转看结果.

本文地点在->个人手艺帖合集, 迎接给个 start 或 follow

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