From 50d7e0e04bda923097be8495eb1bbf43bb295853 Mon Sep 17 00:00:00 2001 From: huangjihua Date: Fri, 29 Jul 2022 17:17:24 +0800 Subject: [PATCH 1/6] feat: Solve the problem of blurring generated pictures --- src/dom-to-image.js | 1401 ++++++++++++++++++++++--------------------- 1 file changed, 720 insertions(+), 681 deletions(-) diff --git a/src/dom-to-image.js b/src/dom-to-image.js index 27201ac9..bd3c57b7 100644 --- a/src/dom-to-image.js +++ b/src/dom-to-image.js @@ -1,41 +1,40 @@ (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; - - - /** + '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, + toCanvas: toCanvas, + impl: { + fontFaces: fontFaces, + images: images, + util: util, + inliner: inliner, + options: {}, + }, + }; + + if (typeof module !== 'undefined') module.exports = domtoimage; + else global = !global ? window : global; + 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 @@ -50,720 +49,760 @@ * @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; - } + 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 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 toCanvas(node, options) { + return draw(node, options || {}).then(function (canvas) { + return canvas; + }); + } + + 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; } - /** - * @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(); - }); + if (typeof options.cacheBust === 'undefined') { + domtoimage.impl.options.cacheBust = defaultOptions.cacheBust; + } else { + domtoimage.impl.options.cacheBust = options.cacheBust; } - - /** - * @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); - }); + } + + 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, canvas.width, canvas.height); + return canvas; + }); + + function newCanvas(domNode) { + var canvas = document.createElement('canvas'); + var scale = options.scale || util.scale; + canvas.width = (options.width || util.width(domNode)) * scale; + canvas.height = (options.height || util.height(domNode)) * scale; + if (options.bgcolor) { + var ctx = canvas.getContext('2d'); + ctx.fillStyle = options.bgcolor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + + return canvas; } - - /** - * @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 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 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; - } + function cloneChildren(original, clone, filter) { + var children = original.childNodes; + if (children.length === 0) return Promise.resolve(clone); - if(typeof(options.cacheBust) === 'undefined') { - domtoimage.impl.options.cacheBust = defaultOptions.cacheBust; - } else { - domtoimage.impl.options.cacheBust = options.cacheBust; + return cloneChildrenInOrder(clone, util.asArray(children), filter).then( + function () { + return clone; } - } + ); - 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 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); }); - - 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; - } + }); + return done; + } } - 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 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 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 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 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 formatCssProperties(style) { + return util.asArray(style).map(formatProperty).join('; ') + ';'; - 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 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 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'); + 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; + if (!(clone instanceof SVGRectElement)) return; + ['width', 'height'].forEach(function (attribute) { + var value = clone.getAttribute(attribute); + if (!value) return; - clone.style.setProperty(attribute, value); - }); - } - } + 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 ( + '' + + xhtml + + '' + ); + }) + .then(function (foreignObject) { + return ( + '' + + foreignObject + + '' + ); + }) + .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, + scale: window.devicePixelRatio, + }; - 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 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 inlineImages(node) { - return images.inlineAll(node) - .then(function () { - return node; - }); + function parseExtension(url) { + var match = /\.([^\.\/]*?)$/g.exec(url); + if (match) return match[1]; + else return ''; } - 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 '' + xhtml + ''; - }) - .then(function (foreignObject) { - return '' + - foreignObject + ''; - }) - .then(function (svg) { - return 'data:image/svg+xml;charset=utf-8,' + svg; - }); + function mimeType(url) { + var extension = parseExtension(url).toLowerCase(); + return mimes()[extension] || ''; } - 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); + function isDataUrl(url) { + return url.search(/^(data:)/) !== -1; + } - resolve(new Blob([binaryArray], { - type: 'image/png' - })); - }); - } + 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); - }); + function canvasToBlob(canvas) { + if (canvas.toBlob) + return new Promise(function (resolve) { + canvas.toBlob(resolve); + }); - return toBlob(canvas); - } + 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 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; + function uid() { + var index = 0; - return function () { - return 'u' + fourRandomChars() + index++; + 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 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 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(); + 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 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(''); - } - }); - } + return; + } - function dataAsUrl(content, type) { - return 'data:' + type + ';base64,' + content; + var encoder = new FileReader(); + encoder.onloadend = function () { + var content = encoder.result.split(/,/)[1]; + resolve(content); + }; + encoder.readAsDataURL(request.response); } - function escape(string) { - return string.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1'); + function timeout() { + if (placeholder) { + resolve(placeholder); + } else { + fail( + 'timeout of ' + + TIMEOUT + + 'ms occured while fetching resource: ' + + url + ); + } } - 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 fail(message) { + console.error(message); + resolve(''); } + }); + } - function escapeXhtml(string) { - return string.replace(/#/g, '%23').replace(/\n/g, '%0A'); - } + function dataAsUrl(content, type) { + return 'data:' + type + ';base64,' + content; + } - function width(node) { - var leftBorder = px(node, 'border-left-width'); - var rightBorder = px(node, 'border-right-width'); - return node.scrollWidth + leftBorder + rightBorder; - } + function escape(string) { + return string.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1'); + } - function height(node) { - var topBorder = px(node, 'border-top-width'); - var bottomBorder = px(node, 'border-bottom-width'); - return node.scrollHeight + topBorder + bottomBorder; - } + function delay(ms) { + return function (arg) { + return new Promise(function (resolve) { + setTimeout(function () { + resolve(arg); + }, ms); + }); + }; + } - function px(node, styleProperty) { - var value = window.getComputedStyle(node).getPropertyValue(styleProperty); - return parseFloat(value.replace('px', '')); - } + function asArray(arrayLike) { + var array = []; + var length = arrayLike.length; + for (var i = 0; i < length; i++) array.push(arrayLike[i]); + return array; } - function newInliner() { - var URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/g; + function escapeXhtml(string) { + return string.replace(/#/g, '%23').replace(/\n/g, '%0A'); + } - return { - inlineAll: inlineAll, - shouldProcess: shouldProcess, - impl: { - readUrls: readUrls, - inline: inline - } - }; + function width(node) { + var leftBorder = px(node, 'border-left-width'); + var rightBorder = px(node, 'border-right-width'); + return node.scrollWidth + leftBorder + rightBorder; + } - function shouldProcess(string) { - return string.search(URL_REGEX) !== -1; - } + function height(node) { + var topBorder = px(node, 'border-top-width'); + var bottomBorder = px(node, 'border-bottom-width'); + return node.scrollHeight + topBorder + bottomBorder; + } - 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 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 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 shouldProcess(string) { + return string.search(URL_REGEX) !== -1; + } - 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 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 newFontFaces() { - return { - resolveAll: resolveAll, - impl: { - readAll: readAll - } - }; + 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 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 inlineAll(string, baseUrl, get) { + if (nothingToInline()) return Promise.resolve(string); - 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')); - }); - } + 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 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 nothingToInline() { + return !shouldProcess(string); + } + } + } + + function newFontFaces() { + return { + resolveAll: resolveAll, + impl: { + readAll: readAll, + }, + }; - 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 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 newImages() { + 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 { - inlineAll: inlineAll, - impl: { - newImage: newImage - } + 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 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) { + console.log(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; - }); - } - } + 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); From 374b65c11c4d2fae68d53d03ddbdd4215ad1b329 Mon Sep 17 00:00:00 2001 From: huangjihua Date: Mon, 8 Aug 2022 16:16:09 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat:=E6=89=A9=E5=B1=95useCredentials,httpT?= =?UTF-8?q?imeout,scale=20=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dom-to-image.js | 1641 ++++++++++++++++++++++--------------------- 1 file changed, 833 insertions(+), 808 deletions(-) diff --git a/src/dom-to-image.js b/src/dom-to-image.js index bd3c57b7..115672d0 100644 --- a/src/dom-to-image.js +++ b/src/dom-to-image.js @@ -1,808 +1,833 @@ -(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, - toCanvas: toCanvas, - impl: { - fontFaces: fontFaces, - images: images, - util: util, - inliner: inliner, - options: {}, - }, - }; - - if (typeof module !== 'undefined') module.exports = domtoimage; - else global = !global ? window : global; - 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 toCanvas(node, options) { - return draw(node, options || {}).then(function (canvas) { - return canvas; - }); - } - - 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, canvas.width, canvas.height); - return canvas; - }); - - function newCanvas(domNode) { - var canvas = document.createElement('canvas'); - var scale = options.scale || util.scale; - canvas.width = (options.width || util.width(domNode)) * scale; - canvas.height = (options.height || util.height(domNode)) * scale; - if (options.bgcolor) { - var ctx = canvas.getContext('2d'); - 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 ( - '' + - xhtml + - '' - ); - }) - .then(function (foreignObject) { - return ( - '' + - foreignObject + - '' - ); - }) - .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, - scale: window.devicePixelRatio, - }; - - 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) { - console.log(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); +(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, + // Use (existing) authentication credentials for external URIs (CORS requests) + useCredentials: false, + // Default resolve timeout + httpTimeout: 30000, + // 根据手机屏分辨率倍数来放大图片倍数,以免图片失真 + scale: window.devicePixelRatio, + }; + + var domtoimage = { + toSvg: toSvg, + toPng: toPng, + toJpeg: toJpeg, + toBlob: toBlob, + toPixelData: toPixelData, + toCanvas: toCanvas, + impl: { + fontFaces: fontFaces, + images: images, + util: util, + inliner: inliner, + options: {} + } + }; + + if (typeof module !== 'undefined') + module.exports = domtoimage; + else + global = !global ? window : global; + 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) { + clone.style = '' + 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 toCanvas(node, options) { + return draw(node, options || {}) + .then(function(canvas) { + return canvas; + }); + } + + 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; + } + if (typeof(options.useCredentials) === 'undefined') { + domtoimage.impl.options.useCredentials = defaultOptions.useCredentials; + } else { + domtoimage.impl.options.useCredentials = options.useCredentials; + } + if (typeof(options.scale) === 'undefined') { + domtoimage.impl.options.scale = defaultOptions.scale; + } else { + domtoimage.impl.options.scale = options.scale; + } + } + + 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, canvas.width, canvas.height); + return canvas; + }); + + function newCanvas(domNode) { + var canvas = document.createElement('canvas'); + + canvas.width = (options.width || util.width(domNode)) * domtoimage.impl.options.scale; + canvas.height = (options.height || util.height(domNode)) * domtoimage.impl.options.scale; + if (options.bgcolor) { + var ctx = canvas.getContext('2d'); + 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); + }); + }); + gitreturn done; + } + } + + /** + * 克隆 + * @param {Object} original 原对象 + * @param {Object} clone 克隆对象 + */ + 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) { + if (source.getPropertyValue(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); + }); + } + } + } + + /** + * 嵌入字体,生成style + * @param {Object} node + */ + 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; + }); + } + /** + * 生成 SVG,base64 + * @param {Object} node + * @param {Object} width + * @param {Object} height + */ + 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 '' + xhtml + ''; + }) + .then(function(foreignObject) { + return '' + + foreignObject + ''; + }) + .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; + console.log(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); + } + }; + } + /** + * 生成图片 + * @param {Object} uri + */ + function makeImage(uri) { + if (uri === 'data:,') return Promise.resolve(); + return new Promise(function(resolve, reject) { + var image = new Image(); + // 处理跨域图片 + image.crossOrigin = 'anonymous'; + if(domtoimage.impl.options.useCredentials) { + image.crossOrigin = 'use-credentials'; + } + image.onload = function() { + resolve(image); + }; + image.onerror = reject; + image.src = uri; + }); + } + + function getAndEncode(url) { + var TIMEOUT = domtoimage.impl.options.httpTimeout;; + 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; + if(domtoimage.impl.options.useCredentials) { + request.withCredentials = true; + } + 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(''); + } + }); + } + /** + * 转换 base64 + * @param {Object} content 要转换内容 + * @param {Object} type 类型:svg,jpeg,png,canvas + */ + 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); + console.log(string,baseUrl); + 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); \ No newline at end of file From 83335f3a427bc9e370186bdf768c1c5958f54163 Mon Sep 17 00:00:00 2001 From: huangjihua Date: Mon, 8 Aug 2022 16:17:00 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat:=E6=89=A9=E5=B1=95useCredentials,httpT?= =?UTF-8?q?imeout,scale=20=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dom-to-image.js | 1667 ++++++++++++++++++++++--------------------- 1 file changed, 848 insertions(+), 819 deletions(-) diff --git a/src/dom-to-image.js b/src/dom-to-image.js index 115672d0..af8029e4 100644 --- a/src/dom-to-image.js +++ b/src/dom-to-image.js @@ -1,49 +1,46 @@ -(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, - // Use (existing) authentication credentials for external URIs (CORS requests) - useCredentials: false, - // Default resolve timeout - httpTimeout: 30000, - // 根据手机屏分辨率倍数来放大图片倍数,以免图片失真 - scale: window.devicePixelRatio, - }; - - var domtoimage = { - toSvg: toSvg, - toPng: toPng, - toJpeg: toJpeg, - toBlob: toBlob, - toPixelData: toPixelData, - toCanvas: toCanvas, - impl: { - fontFaces: fontFaces, - images: images, - util: util, - inliner: inliner, - options: {} - } - }; - - if (typeof module !== 'undefined') - module.exports = domtoimage; - else - global = !global ? window : global; - global.domtoimage = domtoimage; - - - /** +(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, + // Use (existing) authentication credentials for external URIs (CORS requests) + useCredentials: false, + // Default resolve timeout + httpTimeout: 30000, + // 根据手机屏分辨率倍数来放大图片倍数,以免图片失真 + scale: window.devicePixelRatio, + }; + + var domtoimage = { + toSvg: toSvg, + toPng: toPng, + toJpeg: toJpeg, + toBlob: toBlob, + toPixelData: toPixelData, + toCanvas: toCanvas, + impl: { + fontFaces: fontFaces, + images: images, + util: util, + inliner: inliner, + options: {}, + }, + }; + + if (typeof module !== 'undefined') module.exports = domtoimage; + else global = !global ? window : global; + 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 @@ -58,776 +55,808 @@ * @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) { - clone.style = '' - 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 toCanvas(node, options) { - return draw(node, options || {}) - .then(function(canvas) { - return canvas; - }); - } - - 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; - } - if (typeof(options.useCredentials) === 'undefined') { - domtoimage.impl.options.useCredentials = defaultOptions.useCredentials; - } else { - domtoimage.impl.options.useCredentials = options.useCredentials; - } - if (typeof(options.scale) === 'undefined') { - domtoimage.impl.options.scale = defaultOptions.scale; - } else { - domtoimage.impl.options.scale = options.scale; - } - } - - 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, canvas.width, canvas.height); - return canvas; - }); - - function newCanvas(domNode) { - var canvas = document.createElement('canvas'); - - canvas.width = (options.width || util.width(domNode)) * domtoimage.impl.options.scale; - canvas.height = (options.height || util.height(domNode)) * domtoimage.impl.options.scale; - if (options.bgcolor) { - var ctx = canvas.getContext('2d'); - 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); - }); - }); - gitreturn done; - } - } - - /** - * 克隆 - * @param {Object} original 原对象 - * @param {Object} clone 克隆对象 - */ - 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) { - if (source.getPropertyValue(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); - }); - } - } - } - - /** - * 嵌入字体,生成style - * @param {Object} node - */ - 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; - }); - } - /** - * 生成 SVG,base64 - * @param {Object} node - * @param {Object} width - * @param {Object} height - */ - 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 '' + xhtml + ''; - }) - .then(function(foreignObject) { - return '' + - foreignObject + ''; - }) - .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; - console.log(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); - } - }; - } - /** - * 生成图片 - * @param {Object} uri - */ - function makeImage(uri) { - if (uri === 'data:,') return Promise.resolve(); - return new Promise(function(resolve, reject) { - var image = new Image(); - // 处理跨域图片 - image.crossOrigin = 'anonymous'; - if(domtoimage.impl.options.useCredentials) { - image.crossOrigin = 'use-credentials'; - } - image.onload = function() { - resolve(image); - }; - image.onerror = reject; - image.src = uri; - }); - } - - function getAndEncode(url) { - var TIMEOUT = domtoimage.impl.options.httpTimeout;; - 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; - if(domtoimage.impl.options.useCredentials) { - request.withCredentials = true; - } - 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(''); - } - }); - } - /** - * 转换 base64 - * @param {Object} content 要转换内容 - * @param {Object} type 类型:svg,jpeg,png,canvas - */ - 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); - console.log(string,baseUrl); - 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); \ No newline at end of file + 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) { + clone.style = ''; + 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 toCanvas(node, options) { + return draw(node, options || {}).then(function (canvas) { + return canvas; + }); + } + + 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; + } + if (typeof options.useCredentials === 'undefined') { + domtoimage.impl.options.useCredentials = defaultOptions.useCredentials; + } else { + domtoimage.impl.options.useCredentials = options.useCredentials; + } + if (typeof options.scale === 'undefined') { + domtoimage.impl.options.scale = defaultOptions.scale; + } else { + domtoimage.impl.options.scale = options.scale; + } + } + + 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, canvas.width, canvas.height); + return canvas; + }); + + function newCanvas(domNode) { + var canvas = document.createElement('canvas'); + + canvas.width = + (options.width || util.width(domNode)) * domtoimage.impl.options.scale; + canvas.height = + (options.height || util.height(domNode)) * + domtoimage.impl.options.scale; + if (options.bgcolor) { + var ctx = canvas.getContext('2d'); + 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; + } + } + + /** + * 克隆 + * @param {Object} original 原对象 + * @param {Object} clone 克隆对象 + */ + 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) { + if (source.getPropertyValue(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); + }); + } + } + } + + /** + * 嵌入字体,生成style + * @param {Object} node + */ + 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; + }); + } + /** + * 生成 SVG,base64 + * @param {Object} node + * @param {Object} width + * @param {Object} height + */ + 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 ( + '' + + xhtml + + '' + ); + }) + .then(function (foreignObject) { + return ( + '' + + foreignObject + + '' + ); + }) + .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; + console.log(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); + } + }; + } + /** + * 生成图片 + * @param {Object} uri + */ + function makeImage(uri) { + if (uri === 'data:,') return Promise.resolve(); + return new Promise(function (resolve, reject) { + var image = new Image(); + // 处理跨域图片 + image.crossOrigin = 'anonymous'; + if (domtoimage.impl.options.useCredentials) { + image.crossOrigin = 'use-credentials'; + } + image.onload = function () { + resolve(image); + }; + image.onerror = reject; + image.src = uri; + }); + } + + function getAndEncode(url) { + var TIMEOUT = domtoimage.impl.options.httpTimeout; + 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; + if (domtoimage.impl.options.useCredentials) { + request.withCredentials = true; + } + 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(''); + } + }); + } + /** + * 转换 base64 + * @param {Object} content 要转换内容 + * @param {Object} type 类型:svg,jpeg,png,canvas + */ + 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); + console.log(string, baseUrl); + 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); From a00604ebb20099aec035debe00e4e8f847586f74 Mon Sep 17 00:00:00 2001 From: huangjihua Date: Tue, 9 Aug 2022 18:52:37 +0800 Subject: [PATCH 4/6] fix: update desc --- src/dom-to-image.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/dom-to-image.js b/src/dom-to-image.js index af8029e4..5967324c 100644 --- a/src/dom-to-image.js +++ b/src/dom-to-image.js @@ -1,5 +1,5 @@ (function (global) { - 'use strict'; + ('use strict'); var util = newUtil(); var inliner = newInliner(); @@ -14,9 +14,9 @@ cacheBust: false, // Use (existing) authentication credentials for external URIs (CORS requests) useCredentials: false, - // Default resolve timeout + // set resolve timeout httpTimeout: 30000, - // 根据手机屏分辨率倍数来放大图片倍数,以免图片失真 + // 自定义图像缩放比例, scale: window.devicePixelRatio, }; @@ -53,6 +53,9 @@ 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 + * @param {Boolean} options.useCredentials - Use (existing) authentication credentials for external URIs (CORS requests) ,default to false + * @param {Number} options.httpTimeout - set request timeout,default to 30000 + * @param {Number} options.scale - custome image zoom scale * @return {Promise} - A promise that is fulfilled with a SVG image data URL * */ function toSvg(node, options) { From 9032d6375dd4b3888e3e3c2b5239ad355fb14b58 Mon Sep 17 00:00:00 2001 From: huangjihua Date: Tue, 9 Aug 2022 18:52:57 +0800 Subject: [PATCH 5/6] feat: add new flow chart --- flow-chart.drawio | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 flow-chart.drawio diff --git a/flow-chart.drawio b/flow-chart.drawio new file mode 100644 index 00000000..2872fe1d --- /dev/null +++ b/flow-chart.drawio @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 9bf3e477aa9a27cd7a5b9a1a505b8925bd5e79d1 Mon Sep 17 00:00:00 2001 From: huangjihua Date: Tue, 9 Aug 2022 18:53:27 +0800 Subject: [PATCH 6/6] =?UTF-8?q?feat=EF=BC=9Aadd=20china=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zh.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 zh.md diff --git a/zh.md b/zh.md new file mode 100644 index 00000000..c0ad80d9 --- /dev/null +++ b/zh.md @@ -0,0 +1,144 @@ +# 从DOM到图像 +[![Build Status](https://travis-ci.org/tsayen/dom-to-image.svg?branch=master)](https://travis-ci.org/tsayen/dom-to-image) +## 简介 +dom-to-image 是一个用 JavaScript 编写的库,它可以将任意的 DOM 节点转换为矢量(SVG)或栅格(PNG 或 JPEG)图像。它基于 Paul Bakaus 的 [domvas](https://github.com/pbakaus/domvas),已经被完全重写,修复了一些 bug,增加了一些新功能(比如 Web 字体和图像支持)。 + +# 安装 +**NPM** + +> npm install dom-to-image + +Then load +```js +/* in ES 6 */ +import domtoimage from 'dom-to-image'; +/* in ES 5 */ +var domtoimage = require('dom-to-image'); +``` + +**Bower** +> bower install dom-to-image + +在页面中包含 src/dom-to-image. js 或 dist/dom-to-image. min.js,它将使 domtoimage 变量在全局范围内可用。 + +```html + +``` +# 用法 + +所有顶级函数都接受 DOM 节点和呈现选项并返回 promise,这些 promise 通过相应的数据 URL 来实现。获取 PNG 图像 base64编码的数据 URL 并立即显示: + +```js +var node = document.getElementById('my-node'); + +domtoimage.toPng(node) + .then(function (dataUrl) { + var img = new Image(); + img.src = dataUrl; + document.body.appendChild(img); + }) + .catch(function (error) { + console.error('oops, something went wrong!', error); + }); +``` +获取一个 PNG 图像块并下载它(例如,使用 FileSaver) : +```js +domtoimage.toBlob(document.getElementById('my-node')) + .then(function (blob) { + window.saveAs(blob, 'my-node.png'); + }); +``` +保存并下载一个压缩的 JPEG 图片: +```js +domtoimage.toJpeg(document.getElementById('my-node'), { quality: 0.95 }) + .then(function (dataUrl) { + var link = document.createElement('a'); + link.download = 'my-image-name.jpeg'; + link.href = dataUrl; + link.click(); + }); +``` +获取一个 SVG 数据 URL,但过滤掉所有 < i > 元素: +```js +function filter (node) { + return (node.tagName !== 'i'); +} + +domtoimage.toSvg(document.getElementById('my-node'), {filter: filter}) + .then(function (dataUrl) { + /* do something */ + }); +``` +以 Uint8Array 的形式获取原始像素数据,每4个数组元素表示一个像素的 RGBA 数据: +```js +var node = document.getElementById('my-node'); + +domtoimage.toPixelData(node) + .then(function (pixels) { + for (var y = 0; y < node.scrollHeight; ++y) { + for (var x = 0; x < node.scrollWidth; ++x) { + pixelAtXYOffset = (4 * y * node.scrollHeight) + (4 * x); + /* pixelAtXY is a Uint8Array[4] containing RGBA values of the pixel at (x, y) in the range 0..255 */ + pixelAtXY = pixels.slice(pixelAtXYOffset, pixelAtXYOffset + 4); + } + } + }); +``` +Impl 下的所有函数都不是公共 API,只是为了单元测试而公开。 + +# options 渲染选项 + + +| 属性 | 类型 | 默认值 | 说明 +| --- | --- | --- | --- | +| filter | function | -- |一个以 DOM 节点为参数的函数。如果传递的节点应该包含在输出中,那么应该返回 true (排除节点意味着也排除它的子节点)。未在根节点上调用| +| bgcolor | string | -- |设置背景色,CSS 颜色值 | +| width | number | -- |呈现前应用于节点的高度和宽度(以像素为单位)| +| height | number | -- |呈现前应用于节点的高度和宽度(以像素为单位)| +| style | object | -- | 样式属性对象 | +| quality | number | 1.0 | 一个介于0和1之间的数字,表示 JPEG 图像的图像质量(例如0.92 = > 92%) | +| imagePlaceholder | string | -- |占位符图像的数据 URL,在获取图像失败时将使用该 URL。默认值为未定义,并将对失败的映像抛出错误| +| cacheBust | boolean | false |设置为 true 可将当前时间作为查询字符串追加到 URL 请求以启用缓存崩溃 | +| useCredentials | boolean | false | 对外部 URI(CORS 请求)使用(现有)身份验证凭据 | +| httpTimeout | number | 30000 | 设置 resolve 超时时间,单位单位秒 | +| useCredentials | boolean | false | 对外部 URI(CORS 请求)使用(现有)身份验证凭据 | +| scale | number | window.devicePixelRatio | 自定义图像缩放比例 | + +# 依赖性 + + 当前只是使用标准库,但要确保浏览器支持: + - Promise + - SVG tag + + # Tests 测试 +重点是,测试取决于: +- js-imagediff,用于比较渲染图像和控制图像 +- ocard.js,对于无法比较图像(由于浏览器呈现差异)的部分,只需要测试文本是否呈现 + +# 主要工作流程 +也许将来会有一个简单的标准的方法可以导出 HTML 的部分图像,单目前还没有找到。 + +这个库使用了 SVG 的一个特性,允许在标记中包含任意的 HTML 内容。因此,为了能够呈现 DOM 节点,需要执行以下步骤: + +- 递归地克隆原始 DOM节点 +- 计算节点和每个子节点的样式,并将其复制到对应的克隆节点,另外由于伪元素不会以任何方式被克隆,所以需要重新创建伪元素。 +- 嵌入式网页字体 + - 找到所有可能代表 web 字体的@font-face声明 + - 解析文件 URL,下载相应文件 + - base64-将内容和内联内容编码为数据:URLs + - 将所有处理的 css 规则链接起来,并将他们放到一个