html2canvas适配总结

缘起

近期在使用 html2canvas 插件生成图片时,发现对于 svg 元素支持不是很好。

而且查阅网友的解决方案时, 发现是一份原文和N份copy, 总体来说解决方案有以下几种:

0、使用 canvg 插件转换 svg 元素

//以下是对svg的处理
var nodesToRecover = [];
var nodesToRemove = [];
var svgElem = $("#divReport").find('svg');//divReport为需要截取成图片的dom的id
svgElem.each(function (index, node) {
    var parentNode = node.parentNode;
    var svg = node.outerHTML.trim();
    var canvas = document.createElement('canvas');
    canvg(canvas, svg); 
    if (node.style.position) {
        canvas.style.position += node.style.position;
        canvas.style.left += node.style.left;
        canvas.style.top += node.style.top;
    }
    nodesToRecover.push({
        parent: parentNode,
        child: node
    });
    parentNode.removeChild(node);

    nodesToRemove.push({
        parent: parentNode,
        child: canvas
    });

    parentNode.appendChild(canvas);
});

这里有 nodesToRecovernodesToRemove 两个变量,猜测应该是方便回滚用, 但是并没有回滚的相关代码。

1、把 svg 元素转换为图片,然后再转换成 canvas元素

//允许跨域获取,否则百度地图不能生成图片
const opts = {
    useCORS: true,
    ignoreElements: el => {
        const tagName = el.tagName.toLowerCase();
        const list = ['head', 'body', 'style', 'title', 'meta']
        if(list.includes(tagName)) return false;
        // id="extra" 下所有节点忽略
        if(el.id === 'extra') return true;
        return false;
    },
    // TODO:: SVG to canvas
    onclone(cloneDom) {
        const svgElems = $(cloneDom).find('svg');
        svgElems.each(function (index, node) {
            let parentNode = node.parentNode;
            const svg_string = (node.outerHTML || xmlserializer.serializeToString(node)).trim()
            const img = new Image();
            img.src = 'data:image/svg+xml;charset=utf-8,' + svg_string;
            img.crossOrigin = 'anonymous';
            img.onload = function(){
                const width = parseFloat($(node).css('width'));
                const height = parseFloat($(node).css('height'));
                const canvas = document.createElement('canvas');
                canvas.width = width;
                canvas.height = height;
                const ctx = canvas.getContext("2d");
                ctx.drawImage(img, 0, 0, width, height);
                parentNode.appendChild(canvas).
                parentNode.removeChild(node);
            }
        });
    }
}

我发现百度地图上的 svg 元素是有绝对定位和偏移的, 他们的方案不能解决偏移的问题。

性空

后来测试方案0并不成功。 测试方案一,是因为没有追加图片到document 里面,导致没有触发onload方法失败的, 这里进行如下改进:

  • 0、把当前页面的svg元素转换为 canvas 元素:
// 把svg转换为canvas
async convertSvg2Canvas() {
    const svgElms = document.getElementsByTagName('svg');
    // 回调
    const callbacks = [];
    for(let svg of svgElms) {
        const parentElement = svg.parentElement;
        const img = new Image();
        img.src = `data:image/svg+xml,${encodeURIComponent((new XMLSerializer()).serializeToString(svg))}`;
        img.crossOrigin = 'anonymous';
        img.onload = async () => {
            const width = parseFloat(svg.style.width);
            const height = parseFloat(svg.style.height);
            const canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;
            const ctx = canvas.getContext("2d");
            ctx.drawImage(img, 0, 0, width, height);
            parentElement.append(canvas);
            svg.remove();
            img.remove();
        };
        parentElement.append(img);
        callbacks.push(img.onload);
    }
    //await this.axios.all(callbacks);
    await Promise.all(callbacks);
},
  • 1、等待svg转换完成之后,再使用html2canvas 截图:
async getPreviewImg() {
    //显示加载图标
    this.$store.dispatch('showDataloader');
    await Promise.all([this.loadScript('/static/js/html2canvas.min.js'), this.convertSvg2Canvas()]);
    //允许跨域获取,否则百度地图不能生成图片
    const opts = {
        useCORS: true
    }
    const canvasObj = await html2canvas(document.getElementById('poster_context'), opts);
    var context = canvasObj.getContext('2d');
    //防止图片模糊的设置
    context.mozImageSmoothingEnabled = false;
    context.webkitImageSmoothingEnabled = false;
    context.msImageSmoothingEnabled = false;
    context.imageSmoothingEnabled = false;
    // png 格式图片
    let imgType = "image/png";
    this.canvasSrc = canvasObj.toDataURL(imgType);
    this.previewShowFlag = true;
    //隐藏加载图标
    this.$store.dispatch('hideDataloader');
},
…… 

说明: 截屏的时候,必须等待 svg 元素全部转换成为 canvas 元素才可以,否则是截取不成功的

loadScript 方法用来异步加载js, 本人是全局mixin的, 下附 loadScript 方法:

// 动态加载 js 及 回调
async loadScript(src, callback = null) {
    await new Promise(resolve => {
        // 如果已经加载了本js,直接调用回调
        if (this.checkScriptLoaded(src)) {
            return resolve(callback);
        }
        let scriptNode = document.createElement("script");
        scriptNode.setAttribute("type", "text/javascript");
        scriptNode.setAttribute("src", src);
        document.body.appendChild(scriptNode);
        if (scriptNode.readyState) { //IE 判断
            scriptNode.onreadystatechange = () => {
                if (scriptNode.readyState == "complete" || scriptNode.readyState == 'loaded') {
                    return resolve(callback);
                }
            }
        } else {
            scriptNode.onload = () => resolve(callback);
        }
    })
},
// 检测是否加载了 js 文件
checkScriptLoaded(src) {
    const scriptObjs = Array.from(document.getElementsByTagName('script'));
    return scriptObjs.find(ele => ele.src.includes(src));
},

2021-11-19 更新:

H5适配ios系统多行文本时截图错行的问题

近期使用html2canvas插件截图时,发现 iphone手机上,当文本超过一行时,截图后文本排版错乱了(第二行会缩进一个字,而且右边把空白都占满了)

查看CanvasRenderer渲染文本的时候,发现有个 letter-spacing 属性:

CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
            var _this = this;
            if (letterSpacing === 0) {
                this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
            }
            else {
                var letters = toCodePoints(text.text).map(function (i) { return fromCodePoint(i); });
                letters.reduce(function (left, letter) {
                    _this.ctx.fillText(letter, left, text.bounds.top + text.bounds.height);
                    return left + _this.ctx.measureText(letter).width;
                }, text.bounds.left);
            }
        };

如上,如果没有设置 letter-spacing的样式,则会使用 canvas 的 fillText方法把文本渲染到画布上。(但是fillText 对换行文字排版等支持不够友好), 设置了 letter-spacing属性,会使用measureText 方法把文字渲染到画布,虽然不会溢出box,但是第二行还是会有缩进的问题……

解决方案就是: 尽量避免文本超过两行,当文本超过两行的时候,每行头尾用 行内元素 包一下, 这样就会当做 html元素去渲染到画布,极限的可以把每个字符都用行内元素包一下……

(to be continued …… )


他山之石

  • html2canvas github 地址
  • 解决 html2canvas 处理 svg 时样式丢失的问题【转】
  • html2canvas生成清晰的图片实现打印
  • 【html2canvas】生成图片的字符重叠

你可能感兴趣的:(html2canvas适配总结)