Add async parsing option.

In my testing, the major time sink is parsing. This commit adds a setTimeout() around parsing
of each item so control can return to the browser. This increases the total time it takes to finish
a screenshot but will not freeze the browser when it does. This is a good option when e.g. doing
error reporting, where you might not want to freeze the browser while sending debugging information
back to your server.
This commit is contained in:
ssafejava 2013-09-18 09:54:20 +08:00
parent 806cd60474
commit e115180731
4 changed files with 274 additions and 221 deletions

View File

@ -1029,7 +1029,7 @@ function h2cRenderContext(width, height) {
} }
}; };
} }
_html2canvas.Parse = function (images, options) { _html2canvas.Parse = function (images, options, cb) {
window.scroll(0,0); window.scroll(0,0);
var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default
@ -1050,6 +1050,28 @@ _html2canvas.Parse = function (images, options) {
images = images || {}; images = images || {};
function init() {
var background = getCSS(document.documentElement, "backgroundColor"),
transparentBackground = (Util.isTransparent(background) && element === document.body),
stack = renderElement(element, null, false, transparentBackground);
parseChildren(element, stack, null, function() {
if (transparentBackground) {
background = stack.backgroundColor;
}
Util.log('Done parsing, moving to Render.');
body.removeChild(hidePseudoElements);
cb({
backgroundColor: background,
stack: stack
});
});
}
init();
function documentWidth () { function documentWidth () {
return Math.max( return Math.max(
Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth), Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
@ -1367,6 +1389,13 @@ _html2canvas.Parse = function (images, options) {
} }
} }
function h2czContext(zindex) {
return {
zindex: zindex,
children: []
};
}
function renderImage(ctx, element, image, bounds, borders) { function renderImage(ctx, element, image, bounds, borders) {
var paddingLeft = getCSSInt(element, 'paddingLeft'), var paddingLeft = getCSSInt(element, 'paddingLeft'),
@ -1403,69 +1432,67 @@ _html2canvas.Parse = function (images, options) {
}); });
} }
var getCurvePoints = (function(kappa) { function getCurvePoints(x, y, r1, r2) {
var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
return function(x, y, r1, r2) { var ox = (r1) * kappa, // control point offset horizontal
var ox = (r1) * kappa, // control point offset horizontal oy = (r2) * kappa, // control point offset vertical
oy = (r2) * kappa, // control point offset vertical xm = x + r1, // x-middle
xm = x + r1, // x-middle ym = y + r2; // y-middle
ym = y + r2; // y-middle return {
return { topLeft: bezierCurve({
topLeft: bezierCurve({ x:x,
x:x, y:ym
y:ym }, {
}, { x:x,
x:x, y:ym - oy
y:ym - oy }, {
}, { x:xm - ox,
x:xm - ox, y:y
y:y }, {
}, { x:xm,
x:xm, y:y
y:y }),
}), topRight: bezierCurve({
topRight: bezierCurve({ x:x,
x:x, y:y
y:y }, {
}, { x:x + ox,
x:x + ox, y:y
y:y }, {
}, { x:xm,
x:xm, y:ym - oy
y:ym - oy }, {
}, { x:xm,
x:xm, y:ym
y:ym }),
}), bottomRight: bezierCurve({
bottomRight: bezierCurve({ x:xm,
x:xm, y:y
y:y }, {
}, { x:xm,
x:xm, y:y + oy
y:y + oy }, {
}, { x:x + ox,
x:x + ox, y:ym
y:ym }, {
}, { x:x,
x:x, y:ym
y:ym }),
}), bottomLeft: bezierCurve({
bottomLeft: bezierCurve({ x:xm,
x:xm, y:ym
y:ym }, {
}, { x:xm - ox,
x:xm - ox, y:ym
y:ym }, {
}, { x:x,
x:x, y:y + oy
y:y + oy }, {
}, { x:x,
x:x, y:y
y:y })
})
};
}; };
})(4 * ((Math.sqrt(2) - 1) / 3)); }
function bezierCurve(start, startControl, endControl, end) { function bezierCurve(start, startControl, endControl, end) {
@ -1984,9 +2011,8 @@ _html2canvas.Parse = function (images, options) {
return str.replace("px", ""); return str.replace("px", "");
} }
var transformRegExp = /(matrix)\((.+)\)/;
function getTransform(element, parentStack) { function getTransform(element, parentStack) {
var transformRegExp = /(matrix)\((.+)\)/;
var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform"); var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform");
var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px"; var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px";
@ -2127,52 +2153,54 @@ _html2canvas.Parse = function (images, options) {
return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore")); return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
} }
function parseElement (element, stack, pseudoElement) { function parseElement (element, stack, pseudoElement, cb) {
if (!cb) {
cb = function(){};
}
if (isElementVisible(element)) { if (isElementVisible(element)) {
stack = renderElement(element, stack, pseudoElement, false) || stack; stack = renderElement(element, stack, pseudoElement, false) || stack;
if (!ignoreElementsRegExp.test(element.nodeName)) { if (!ignoreElementsRegExp.test(element.nodeName)) {
parseChildren(element, stack, pseudoElement); return parseChildren(element, stack, pseudoElement, cb);
} }
} }
cb();
} }
function parseChildren(element, stack, pseudoElement) { function parseChildren(element, stack, pseudoElement, cb) {
Util.Children(element).forEach(function(node) { var children = Util.Children(element);
// After all nodes have processed, finished() will call the cb.
// We add one and kick it off so this will still work when children.length === 0.
// Note that unless async is true, this will happen synchronously, just will callbacks.
var jobs = children.length + 1;
finished();
if (options.async) {
children.forEach(function(node) {
// Don't block the page from rendering
setTimeout(function(){ parseNode(node); }, 0);
});
} else {
children.forEach(parseNode);
}
function parseNode(node) {
if (node.nodeType === node.ELEMENT_NODE) { if (node.nodeType === node.ELEMENT_NODE) {
parseElement(node, stack, pseudoElement); parseElement(node, stack, pseudoElement, finished);
} else if (node.nodeType === node.TEXT_NODE) { } else if (node.nodeType === node.TEXT_NODE) {
renderText(element, node, stack); renderText(element, node, stack);
finished();
} else {
finished();
}
}
function finished(el) {
if (--jobs <= 0){
Util.log("finished rendering " + children.length + " children.");
cb();
} }
});
}
function init() {
var background = getCSS(document.documentElement, "backgroundColor"),
transparentBackground = (Util.isTransparent(background) && element === document.body),
stack = renderElement(element, null, false, transparentBackground);
parseChildren(element, stack);
if (transparentBackground) {
background = stack.backgroundColor;
} }
body.removeChild(hidePseudoElements);
return {
backgroundColor: background,
stack: stack
};
} }
return init();
}; };
function h2czContext(zindex) {
return {
zindex: zindex,
children: []
};
}
_html2canvas.Preload = function( options ) { _html2canvas.Preload = function( options ) {
var images = { var images = {
@ -2697,21 +2725,19 @@ window.html2canvas = function(elements, opts) {
return; return;
} }
} }
queue = _html2canvas.Parse( images, options ); _html2canvas.Parse( images, options, function(queue) {
if (typeof options.onparsed === "function") {
if (typeof options.onparsed === "function") { if ( options.onparsed( queue ) === false ) {
if ( options.onparsed( queue ) === false ) { return;
return; }
} }
}
canvas = _html2canvas.Renderer( queue, options );
if (typeof options.onrendered === "function") {
options.onrendered( canvas );
}
canvas = _html2canvas.Renderer( queue, options );
if (typeof options.onrendered === "function") {
options.onrendered( canvas );
}
});
}; };
// for pages without images, we still want this to be async, i.e. return methods before executing // for pages without images, we still want this to be async, i.e. return methods before executing

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
_html2canvas.Parse = function (images, options) { _html2canvas.Parse = function (images, options, cb) {
window.scroll(0,0); window.scroll(0,0);
var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default var element = (( options.elements === undefined ) ? document.body : options.elements[0]), // select body by default
@ -19,6 +19,28 @@ _html2canvas.Parse = function (images, options) {
images = images || {}; images = images || {};
function init() {
var background = getCSS(document.documentElement, "backgroundColor"),
transparentBackground = (Util.isTransparent(background) && element === document.body),
stack = renderElement(element, null, false, transparentBackground);
parseChildren(element, stack, null, function() {
if (transparentBackground) {
background = stack.backgroundColor;
}
Util.log('Done parsing, moving to Render.');
body.removeChild(hidePseudoElements);
cb({
backgroundColor: background,
stack: stack
});
});
}
init();
function documentWidth () { function documentWidth () {
return Math.max( return Math.max(
Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth), Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
@ -336,6 +358,13 @@ _html2canvas.Parse = function (images, options) {
} }
} }
function h2czContext(zindex) {
return {
zindex: zindex,
children: []
};
}
function renderImage(ctx, element, image, bounds, borders) { function renderImage(ctx, element, image, bounds, borders) {
var paddingLeft = getCSSInt(element, 'paddingLeft'), var paddingLeft = getCSSInt(element, 'paddingLeft'),
@ -372,69 +401,67 @@ _html2canvas.Parse = function (images, options) {
}); });
} }
var getCurvePoints = (function(kappa) { function getCurvePoints(x, y, r1, r2) {
var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
return function(x, y, r1, r2) { var ox = (r1) * kappa, // control point offset horizontal
var ox = (r1) * kappa, // control point offset horizontal oy = (r2) * kappa, // control point offset vertical
oy = (r2) * kappa, // control point offset vertical xm = x + r1, // x-middle
xm = x + r1, // x-middle ym = y + r2; // y-middle
ym = y + r2; // y-middle return {
return { topLeft: bezierCurve({
topLeft: bezierCurve({ x:x,
x:x, y:ym
y:ym }, {
}, { x:x,
x:x, y:ym - oy
y:ym - oy }, {
}, { x:xm - ox,
x:xm - ox, y:y
y:y }, {
}, { x:xm,
x:xm, y:y
y:y }),
}), topRight: bezierCurve({
topRight: bezierCurve({ x:x,
x:x, y:y
y:y }, {
}, { x:x + ox,
x:x + ox, y:y
y:y }, {
}, { x:xm,
x:xm, y:ym - oy
y:ym - oy }, {
}, { x:xm,
x:xm, y:ym
y:ym }),
}), bottomRight: bezierCurve({
bottomRight: bezierCurve({ x:xm,
x:xm, y:y
y:y }, {
}, { x:xm,
x:xm, y:y + oy
y:y + oy }, {
}, { x:x + ox,
x:x + ox, y:ym
y:ym }, {
}, { x:x,
x:x, y:ym
y:ym }),
}), bottomLeft: bezierCurve({
bottomLeft: bezierCurve({ x:xm,
x:xm, y:ym
y:ym }, {
}, { x:xm - ox,
x:xm - ox, y:ym
y:ym }, {
}, { x:x,
x:x, y:y + oy
y:y + oy }, {
}, { x:x,
x:x, y:y
y:y })
})
};
}; };
})(4 * ((Math.sqrt(2) - 1) / 3)); }
function bezierCurve(start, startControl, endControl, end) { function bezierCurve(start, startControl, endControl, end) {
@ -953,9 +980,8 @@ _html2canvas.Parse = function (images, options) {
return str.replace("px", ""); return str.replace("px", "");
} }
var transformRegExp = /(matrix)\((.+)\)/;
function getTransform(element, parentStack) { function getTransform(element, parentStack) {
var transformRegExp = /(matrix)\((.+)\)/;
var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform"); var transform = getCSS(element, "transform") || getCSS(element, "-webkit-transform") || getCSS(element, "-moz-transform") || getCSS(element, "-ms-transform") || getCSS(element, "-o-transform");
var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px"; var transformOrigin = getCSS(element, "transform-origin") || getCSS(element, "-webkit-transform-origin") || getCSS(element, "-moz-transform-origin") || getCSS(element, "-ms-transform-origin") || getCSS(element, "-o-transform-origin") || "0px 0px";
@ -1096,48 +1122,51 @@ _html2canvas.Parse = function (images, options) {
return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore")); return (getCSS(element, 'display') !== "none" && getCSS(element, 'visibility') !== "hidden" && !element.hasAttribute("data-html2canvas-ignore"));
} }
function parseElement (element, stack, pseudoElement) { function parseElement (element, stack, pseudoElement, cb) {
if (!cb) {
cb = function(){};
}
if (isElementVisible(element)) { if (isElementVisible(element)) {
stack = renderElement(element, stack, pseudoElement, false) || stack; stack = renderElement(element, stack, pseudoElement, false) || stack;
if (!ignoreElementsRegExp.test(element.nodeName)) { if (!ignoreElementsRegExp.test(element.nodeName)) {
parseChildren(element, stack, pseudoElement); return parseChildren(element, stack, pseudoElement, cb);
} }
} }
cb();
} }
function parseChildren(element, stack, pseudoElement) { function parseChildren(element, stack, pseudoElement, cb) {
Util.Children(element).forEach(function(node) { var children = Util.Children(element);
// After all nodes have processed, finished() will call the cb.
// We add one and kick it off so this will still work when children.length === 0.
// Note that unless async is true, this will happen synchronously, just will callbacks.
var jobs = children.length + 1;
finished();
if (options.async) {
children.forEach(function(node) {
// Don't block the page from rendering
setTimeout(function(){ parseNode(node); }, 0);
});
} else {
children.forEach(parseNode);
}
function parseNode(node) {
if (node.nodeType === node.ELEMENT_NODE) { if (node.nodeType === node.ELEMENT_NODE) {
parseElement(node, stack, pseudoElement); parseElement(node, stack, pseudoElement, finished);
} else if (node.nodeType === node.TEXT_NODE) { } else if (node.nodeType === node.TEXT_NODE) {
renderText(element, node, stack); renderText(element, node, stack);
finished();
} else {
finished();
}
}
function finished(el) {
if (--jobs <= 0){
Util.log("finished rendering " + children.length + " children.");
cb();
} }
});
}
function init() {
var background = getCSS(document.documentElement, "backgroundColor"),
transparentBackground = (Util.isTransparent(background) && element === document.body),
stack = renderElement(element, null, false, transparentBackground);
parseChildren(element, stack);
if (transparentBackground) {
background = stack.backgroundColor;
} }
body.removeChild(hidePseudoElements);
return {
backgroundColor: background,
stack: stack
};
} }
};
return init();
};
function h2czContext(zindex) {
return {
zindex: zindex,
children: []
};
}

View File

@ -20,9 +20,9 @@ window.html2canvas = function(elements, opts) {
useOverflow: true, useOverflow: true,
letterRendering: false, letterRendering: false,
chinese: false, chinese: false,
async: false, // If true, parsing will not block, but if the user scrolls during parse the image can get weird
// render options // render options
width: null, width: null,
height: null, height: null,
taintTest: true, // do a taint test with all images before applying to canvas taintTest: true, // do a taint test with all images before applying to canvas
@ -39,21 +39,19 @@ window.html2canvas = function(elements, opts) {
return; return;
} }
} }
queue = _html2canvas.Parse( images, options ); _html2canvas.Parse( images, options, function(queue) {
if (typeof options.onparsed === "function") {
if (typeof options.onparsed === "function") { if ( options.onparsed( queue ) === false ) {
if ( options.onparsed( queue ) === false ) { return;
return; }
} }
}
canvas = _html2canvas.Renderer( queue, options );
if (typeof options.onrendered === "function") {
options.onrendered( canvas );
}
canvas = _html2canvas.Renderer( queue, options );
if (typeof options.onrendered === "function") {
options.onrendered( canvas );
}
});
}; };
// for pages without images, we still want this to be async, i.e. return methods before executing // for pages without images, we still want this to be async, i.e. return methods before executing