dom-to-image失真,修改生成后的清晰度

947 阅读5分钟

背景:

项目中遇到了网页截图的场景,开始使用了html2canvas ,本地一切都好,但正式环境中出现问题,dom中有图片,以为是这些图片跨域,最后按照解决办法也不行;再后来又看到是dom要设置宽高,但试过依旧不可以。最终换个库来实现需求

dom-to-image

github: github.com/tsayen/dom-…

后来发现了基于此库的新项目 dom-to-image-more(www.npmjs.com/package/dom…

改造

项目中使用发现清晰度有点低,客户也指出来,但这个库貌似并没有相关配置可以实现,无奈只得开始对其进行修改

文件替换

文件路径: node_modules\dom-to-image\src\dom-to-image.js

 (function (global) {
     'use strict';
 ​
     var util = newUtil();
     var inliner = newInliner();
     var fontFaces = newFontFaces();
     var images = newImages();
 ​
     // Default impl options
     var defaultOptions = {
         // Default is to fail on error, no placeholder
         imagePlaceholder: undefined,
         // Default cache bust is false, it will use the cache
         cacheBust: false
     };
 ​
     var domtoimage = {
         toSvg: toSvg,
         toPng: toPng,
         toJpeg: toJpeg,
         toBlob: toBlob,
         toPixelData: toPixelData,
         impl: {
             fontFaces: fontFaces,
             images: images,
             util: util,
             inliner: inliner,
             options: {}
         }
     };
 ​
     if (typeof module !== 'undefined')
         module.exports = domtoimage;
     else
         global.domtoimage = domtoimage;
 ​
 ​
     /**
      * @param {Node} node - The DOM Node object to render
      * @param {Object} options - Rendering options
      * @param {Function} options.filter - Should return true if passed node should be included in the output
      *          (excluding node means excluding it's children as well). Not called on the root node.
      * @param {String} options.bgcolor - color for the background, any valid CSS color value.
      * @param {Number} options.width - width to be applied to node before rendering.
      * @param {Number} options.height - height to be applied to node before rendering.
      * @param {Object} options.style - an object whose properties to be copied to node's style before rendering.
      * @param {Number} options.quality - a Number between 0 and 1 indicating image quality (applicable to JPEG only),
                 defaults to 1.0.
      * @param {String} options.imagePlaceholder - dataURL to use as a placeholder for failed images, default behaviour is to fail fast on images we can't fetch
      * @param {Boolean} options.cacheBust - set to true to cache bust by appending the time to the request url
      * @return {Promise} - A promise that is fulfilled with a SVG image data URL
      * */
     function toSvg (node, options) {
         options = options || {};
         copyOptions(options);
         return Promise.resolve(node)
             .then(function (node) {
                 return cloneNode(node, options.filter, true);
             })
             .then(embedFonts)
             .then(inlineImages)
             .then(applyOptions)
             .then(function (clone) {
                 return makeSvgDataUri(clone,
                     options.width || util.width(node),
                     options.height || util.height(node)
                 );
             });
 ​
         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;
         }
     }
 ​
     /**
      * @param {Node} node - The DOM Node object to render
      * @param {Object} options - Rendering options, @see {@link toSvg}
      * @return {Promise} - A promise that is fulfilled with a Uint8Array containing RGBA pixel data.
      * */
     function toPixelData (node, options) {
         return draw(node, options || {})
             .then(function (canvas) {
                 return canvas.getContext('2d').getImageData(
                     0,
                     0,
                     util.width(node),
                     util.height(node)
                 ).data;
             });
     }
 ​
     /**
      * @param {Node} node - The DOM Node object to render
      * @param {Object} options - Rendering options, @see {@link toSvg}
      * @return {Promise} - A promise that is fulfilled with a PNG image data URL
      * */
     function toPng (node, options) {
         return draw(node, options || {})
             .then(function (canvas) {
                 return canvas.toDataURL();
             });
     }
 ​
     /**
      * @param {Node} node - The DOM Node object to render
      * @param {Object} options - Rendering options, @see {@link toSvg}
      * @return {Promise} - A promise that is fulfilled with a JPEG image data URL
      * */
     function toJpeg (node, options) {
         options = options || {};
         return draw(node, options)
             .then(function (canvas) {
                 return canvas.toDataURL('image/jpeg', options.quality || 1.0);
             });
     }
 ​
     /**
      * @param {Node} node - The DOM Node object to render
      * @param {Object} options - Rendering options, @see {@link toSvg}
      * @return {Promise} - A promise that is fulfilled with a PNG image blob
      * */
     function toBlob (node, options) {
         return draw(node, options || {})
             .then(util.canvasToBlob);
     }
 ​
     function copyOptions (options) {
         // Copy options to impl options for use in impl
         if (typeof (options.imagePlaceholder) === 'undefined') {
             domtoimage.impl.options.imagePlaceholder = defaultOptions.imagePlaceholder;
         } else {
             domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder;
         }
 ​
         if (typeof (options.cacheBust) === 'undefined') {
             domtoimage.impl.options.cacheBust = defaultOptions.cacheBust;
         } else {
             domtoimage.impl.options.cacheBust = options.cacheBust;
         }
     }
 ​
     function draw (domNode, options) {
         return toSvg(domNode, options)
             .then(util.makeImage)
             .then(util.delay(100))
             .then(function (image) {
                 var canvas = newCanvas(domNode);
                 canvas.getContext('2d').drawImage(image, 0, 0);
                 return canvas;
             });
 ​
         function newCanvas (domNode) {
             var canvas = document.createElement('canvas');
             var ctx = canvas.getContext('2d');
             ctx.mozImageSmoothingEnabled = false;
             ctx.webkitImageSmoothingEnabled = false;
             ctx.msImageSmoothingEnabled = false;
             ctx.imageSmoothingEnabled = false;
             var scale = options.scale || 1; // 默认值1
             canvas.width = (options.width * scale) || util.width(domNode);
             canvas.height = (options.height * scale) || util.height(domNode);
             ctx.scale(scale, scale) // 添加了scale参数
             if (options.bgcolor) {
                 ctx.fillStyle = options.bgcolor;
                 ctx.fillRect(0, 0, canvas.width, canvas.height);
             }
 ​
             return canvas;
         }
     }
 ​
     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) {
             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 () {
                 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)
                             );
                         });
                     }
                 }
             }
 ​
             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 () {
                 if (original instanceof HTMLTextAreaElement) clone.innerHTML = original.value;
                 if (original instanceof HTMLInputElement) clone.setAttribute("value", original.value);
             }
 ​
             function fixSvg () {
                 if (!(clone instanceof SVGElement)) return;
                 clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
 ​
                 if (!(clone instanceof SVGRectElement)) return;
                 ['width', 'height'].forEach(function (attribute) {
                     var value = clone.getAttribute(attribute);
                     if (!value) return;
 ​
                     clone.style.setProperty(attribute, value);
                 });
             }
         }
     }
 ​
     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 inlineImages (node) {
         return images.inlineAll(node)
             .then(function () {
                 return node;
             });
     }
 ​
     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;
             });
     }
 ​
     function newUtil () {
         return {
             escape: escape,
             parseExtension: parseExtension,
             mimeType: mimeType,
             dataAsUrl: dataAsUrl,
             isDataUrl: isDataUrl,
             canvasToBlob: canvasToBlob,
             resolveUrl: resolveUrl,
             getAndEncode: getAndEncode,
             uid: uid(),
             delay: delay,
             asArray: asArray,
             escapeXhtml: escapeXhtml,
             makeImage: makeImage,
             width: width,
             height: height
         };
 ​
         function mimes () {
             /*
              * Only WOFF and EOT mime types for fonts are 'real'
              * see http://www.iana.org/assignments/media-types/media-types.xhtml
              */
             var WOFF = 'application/font-woff';
             var JPEG = 'image/jpeg';
 ​
             return {
                 'woff': WOFF,
                 'woff2': WOFF,
                 'ttf': 'application/font-truetype',
                 'eot': 'application/vnd.ms-fontobject',
                 'png': 'image/png',
                 'jpg': JPEG,
                 'jpeg': JPEG,
                 'gif': 'image/gif',
                 'tiff': 'image/tiff',
                 'svg': 'image/svg+xml'
             };
         }
 ​
         function parseExtension (url) {
             var match = /.([^./]*?)$/g.exec(url);
             if (match) return match[1];
             else return '';
         }
 ​
         function mimeType (url) {
             var extension = parseExtension(url).toLowerCase();
             return mimes()[extension] || '';
         }
 ​
         function isDataUrl (url) {
             return url.search(/^(data:)/) !== -1;
         }
 ​
         function toBlob (canvas) {
             return new Promise(function (resolve) {
                 var binaryString = window.atob(canvas.toDataURL().split(',')[1]);
                 var length = binaryString.length;
                 var binaryArray = new Uint8Array(length);
 ​
                 for (var i = 0; i < length; i++)
                     binaryArray[i] = binaryString.charCodeAt(i);
 ​
                 resolve(new Blob([binaryArray], {
                     type: 'image/png'
                 }));
             });
         }
 ​
         function canvasToBlob (canvas) {
             if (canvas.toBlob)
                 return new Promise(function (resolve) {
                     canvas.toBlob(resolve);
                 });
 ​
             return toBlob(canvas);
         }
 ​
         function resolveUrl (url, baseUrl) {
             var doc = document.implementation.createHTMLDocument();
             var base = doc.createElement('base');
             doc.head.appendChild(base);
             var a = doc.createElement('a');
             doc.body.appendChild(a);
             base.href = baseUrl;
             a.href = url;
             return a.href;
         }
 ​
         function uid () {
             var index = 0;
 ​
             return function () {
                 return 'u' + fourRandomChars() + index++;
 ​
                 function fourRandomChars () {
                     /* see http://stackoverflow.com/a/6248722/2519373 */
                     return ('0000' + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4);
                 }
             };
         }
 ​
         function makeImage (uri) {
             return new Promise(function (resolve, reject) {
                 var image = new Image();
                 image.onload = function () {
                     resolve(image);
                 };
                 image.onerror = reject;
                 image.src = uri;
             });
         }
 ​
         function getAndEncode (url) {
             var TIMEOUT = 30000;
             if (domtoimage.impl.options.cacheBust) {
                 // Cache bypass so we dont have CORS issues with cached images
                 // Source: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache
                 url += ((/?/).test(url) ? "&" : "?") + (new Date()).getTime();
             }
 ​
             return new Promise(function (resolve) {
                 var request = new XMLHttpRequest();
 ​
                 request.onreadystatechange = done;
                 request.ontimeout = timeout;
                 request.responseType = 'blob';
                 request.timeout = TIMEOUT;
                 request.open('GET', url, true);
                 request.send();
 ​
                 var placeholder;
                 if (domtoimage.impl.options.imagePlaceholder) {
                     var split = domtoimage.impl.options.imagePlaceholder.split(/,/);
                     if (split && split[1]) {
                         placeholder = split[1];
                     }
                 }
 ​
                 function done () {
                     if (request.readyState !== 4) return;
 ​
                     if (request.status !== 200) {
                         if (placeholder) {
                             resolve(placeholder);
                         } else {
                             fail('cannot fetch resource: ' + url + ', status: ' + request.status);
                         }
 ​
                         return;
                     }
 ​
                     var encoder = new FileReader();
                     encoder.onloadend = function () {
                         var content = encoder.result.split(/,/)[1];
                         resolve(content);
                     };
                     encoder.readAsDataURL(request.response);
                 }
 ​
                 function timeout () {
                     if (placeholder) {
                         resolve(placeholder);
                     } else {
                         fail('timeout of ' + TIMEOUT + 'ms occured while fetching resource: ' + url);
                     }
                 }
 ​
                 function fail (message) {
                     console.error(message);
                     resolve('');
                 }
             });
         }
 ​
         function dataAsUrl (content, type) {
             return 'data:' + type + ';base64,' + content;
         }
 ​
         function escape (string) {
             return string.replace(/([.*+?^${}()|[]/\])/g, '\$1');
         }
 ​
         function delay (ms) {
             return function (arg) {
                 return new Promise(function (resolve) {
                     setTimeout(function () {
                         resolve(arg);
                     }, ms);
                 });
             };
         }
 ​
         function asArray (arrayLike) {
             var array = [];
             var length = arrayLike.length;
             for (var i = 0; i < length; i++) array.push(arrayLike[i]);
             return array;
         }
 ​
         function escapeXhtml (string) {
             return string.replace(/#/g, '%23').replace(/\n/g, '%0A');
         }
 ​
         function width (node) {
             var leftBorder = px(node, 'border-left-width');
             var rightBorder = px(node, 'border-right-width');
             return node.scrollWidth + leftBorder + rightBorder;
         }
 ​
         function height (node) {
             var topBorder = px(node, 'border-top-width');
             var bottomBorder = px(node, 'border-bottom-width');
             return node.scrollHeight + topBorder + bottomBorder;
         }
 ​
         function px (node, styleProperty) {
             var value = window.getComputedStyle(node).getPropertyValue(styleProperty);
             return parseFloat(value.replace('px', ''));
         }
     }
 ​
     function newInliner () {
         var URL_REGEX = /url(['"]?([^'"]+?)['"]?)/g;
 ​
         return {
             inlineAll: inlineAll,
             shouldProcess: shouldProcess,
             impl: {
                 readUrls: readUrls,
                 inline: inline
             }
         };
 ​
         function shouldProcess (string) {
             return string.search(URL_REGEX) !== -1;
         }
 ​
         function readUrls (string) {
             var result = [];
             var match;
             while ((match = URL_REGEX.exec(string)) !== null) {
                 result.push(match[1]);
             }
             return result.filter(function (url) {
                 return !util.isDataUrl(url);
             });
         }
 ​
         function inline (string, url, baseUrl, get) {
             return Promise.resolve(url)
                 .then(function (url) {
                     return baseUrl ? util.resolveUrl(url, baseUrl) : url;
                 })
                 .then(get || util.getAndEncode)
                 .then(function (data) {
                     return util.dataAsUrl(data, util.mimeType(url));
                 })
                 .then(function (dataUrl) {
                     return string.replace(urlAsRegex(url), '$1' + dataUrl + '$3');
                 });
 ​
             function urlAsRegex (url) {
                 return new RegExp('(url\(['"]?)(' + util.escape(url) + ')(['"]?\))', 'g');
             }
         }
 ​
         function inlineAll (string, baseUrl, get) {
             if (nothingToInline()) return Promise.resolve(string);
 ​
             return Promise.resolve(string)
                 .then(readUrls)
                 .then(function (urls) {
                     var done = Promise.resolve(string);
                     urls.forEach(function (url) {
                         done = done.then(function (string) {
                             return inline(string, url, baseUrl, get);
                         });
                     });
                     return done;
                 });
 ​
             function nothingToInline () {
                 return !shouldProcess(string);
             }
         }
     }
 ​
     function newFontFaces () {
         return {
             resolveAll: resolveAll,
             impl: {
                 readAll: readAll
             }
         };
 ​
         function resolveAll () {
             return readAll(document)
                 .then(function (webFonts) {
                     return Promise.all(
                         webFonts.map(function (webFont) {
                             return webFont.resolve();
                         })
                     );
                 })
                 .then(function (cssStrings) {
                     return cssStrings.join('\n');
                 });
         }
 ​
         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');
                     }
                 };
             }
         }
     }
 ​
     function newImages () {
         return {
             inlineAll: inlineAll,
             impl: {
                 newImage: newImage
             }
         };
 ​
         function newImage (element) {
             return {
                 inline: inline
             };
 ​
             function inline (get) {
                 if (util.isDataUrl(element.src)) return Promise.resolve();
 ​
                 return Promise.resolve(element.src)
                     .then(get || util.getAndEncode)
                     .then(function (data) {
                         return util.dataAsUrl(data, util.mimeType(element.src));
                     })
                     .then(function (dataUrl) {
                         return new Promise(function (resolve, reject) {
                             element.onload = resolve;
                             element.onerror = reject;
                             element.src = dataUrl;
                         });
                     });
             }
         }
 ​
         function inlineAll (node) {
             if (!(node instanceof Element)) return Promise.resolve(node);
 ​
             return inlineBackground(node)
                 .then(function () {
                     if (node instanceof HTMLImageElement)
                         return newImage(node).inline();
                     else
                         return Promise.all(
                             util.asArray(node.childNodes).map(function (child) {
                                 return inlineAll(child);
                             })
                         );
                 });
 ​
             function inlineBackground (node) {
                 var background = node.style.getPropertyValue('background');
 ​
                 if (!background) return Promise.resolve(node);
 ​
                 return inliner.inlineAll(background)
                     .then(function (inlined) {
                         node.style.setProperty(
                             'background',
                             inlined,
                             node.style.getPropertyPriority('background')
                         );
                     })
                     .then(function () {
                         return node;
                     });
             }
         }
     }
 })(this);
 ​

在使用的地方直接添加参数

   // 跳转到生成名片
     clickGeneratePictureBydomtoimage(){
       console.log('clickGeneratePictureBydomtoimage')
       var that = this;
       var index = that.activeIndex;
       let node = document.getElementsByClassName("mint-swipe-item")[index]
       var options = {
         width:375, // 高度宽度自行设定
         height:450,
         scale:8
       }  
     domtoimage.toPng(node, options)
     .then(function (dataUrl) {
        //do something
     })
     .catch(function (error) {
         console.error('oops, something went wrong!', error);
     });
 ​
     }