3

背景

近期在项目中遇到了一个需求,需求大致就是把文字,贴纸和背景图合成一张图片产物。这个需求最后就用到了dom-to-image。过程中也踩了一些坑,本文就分析一下这个库的原理,及这个库的一些坑。

基本原理

这个库需要做的事情就是把一段dom变为一个图片,我们来看一下最核心的源码中的一个函数makeSvgDataUri

function makeSvgDataUri(node, width, height) {
        return Promise.resolve(node)
            .then(function (node) {
                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>';
            })
            .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;
            });
    }

这个函数就是一个node节点变为图片的一个过程,大致经历了以下过程

  1. 将html node转化为xml,设定命名空间
  2. foreignObject 包裹xml
  3. 把内容变为了svg
  4. svg变为base64的图片

大致就是利用了svg作为了dom变为图片的桥梁

图片与字体的处理

了解了基本原理之后我们知道,其实html和css 转为svg再到图片其实是没有什么问题和难度,但是有两个问题我们必须解决,一个就是dom中的图片,另一个就是字体,而这两个问题在实际项目中也都是踩了一些坑。

图片问题

先要明白为什么图片是需要单独处理的,在基本原理中我们提到了dom最后会变成base64的图片,但实际上我们知道我们的dom中如果是有图片资源的话,浏览器是需要通过请求去获取这个远程资源的。解决这个问题的方案就是提前去下载这个图片资源,并且将其转化为base64,插入到dom中。

第一步下载图片,这个库的处理就是直接get请求下载图片,在实际工程中需要主要一个点就是资源的ajax跨域下载的问题,建议如果是cdn的素材资源,可以通过工程后端做一个请求转发。
第二步就是将data变为base64,这个没有什么难度,可以看一下这个链接https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs,只要确定了mediatype即可,用字符串拼接的方式实现。

踩坑记录,dom2image的库中也确实实现了上面的过程,但是在图片的判断上有些问题,dom中可能存在的图片有,img标签,背景图和边框图,但实际上库中只在属性中遍历了前两种,border-iamge属性没有考虑到,需要自己修复一下。

字体问题

问题来源,我们目前使用的字体含iconfont,无非两种写法

  1. css @font定义font-family
  2. var fontFace = new FontFace(family, source, descriptors);

第二种相对较少,而远程的字体同样有图片远程资源的问题,这个的解决方案是与图片一致的,不再赘述。我们来看一下库中如何处理字体的问题。

源码中的处理
`

function embedFonts(node) {
    return fontFaces.resolveAll()
        .then(function (cssText) {
            var styleNode = document.createElement('style');
            node.appendChild(styleNode);
            styleNode.appendChild(document.createTextNode(cssText));
            return node;
        })
        

`

function readAll() {
            return Promise.resolve(util.asArray(document.styleSheets))
                .then(getCssRules)
                .then(selectWebFontRules)
                .then(function (rules) {
                    return rules.map(newWebFont);
                });

            function selectWebFontRules(cssRules) {
                return cssRules
                    .filter(function (rule) {
                        return rule.type === CSSRule.FONT_FACE_RULE;
                    })
                    .filter(function (rule) {
                        return inliner.shouldProcess(rule.style.getPropertyValue('src'));
                    });
            }

            function getCssRules(styleSheets) {
                var cssRules = [];
                styleSheets.forEach(function (sheet) {
                    try {
                        util.asArray(sheet.cssRules || []).forEach(cssRules.push.bind(cssRules));
                    } catch (e) {
                        console.log('Error while reading CSS rules from ' + sheet.href, e.toString());
                    }
                });
                return cssRules;
            }

            function newWebFont(webFontRule) {
                return {
                    resolve: function resolve() {
                        var baseUrl = (webFontRule.parentStyleSheet || {}).href;
                        return inliner.inlineAll(webFontRule.cssText, baseUrl);
                    },
                    src: function () {
                        return webFontRule.style.getPropertyValue('src');
                    }
                };
            }
        }
    }

难点就是去找到与字体相关的css,然后只要新建style标签插入对应的字体定义,只要保证这个style也在最后的svg里,那么字体展示就不会有问题。
主要用了CSSRule.cssText,字体相关的css就是在CSSRule.FONT_FACE_RULE中,这部分需要遍历styleSheets。

这里也是有一些坑的,主要是两个问题

  1. 用FontFace的定义的字体,并不能用上面的方法生效
  2. 工程项目的styleSheets较大,遍历性能较差。

问了解决这两个问题,建议改造一下库的处理,删去原有的处理,自己手动给dom2image传入需要的字体文件,或者是直接在需要转化的dom中自己插入字体style

总结

domtoimage的方案,确实是有很多的坑,但也不是完全不能在工程中使用,使用前还是要注意解决一下他自身的一些问题。最后base64的这个方案还是有些问题,来源于base64在处理大图片,大资源的时候会有一定的性能问题,这个问题目前除了避免大资源外,暂时还没有想到更好的方案。


求实亭下
142 声望13 粉丝

有着深度学习技能的前端开发工程师