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);
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 || {};
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 () {
return Math.max(
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) {
var paddingLeft = getCSSInt(element, 'paddingLeft'),
@ -1403,69 +1432,67 @@ _html2canvas.Parse = function (images, options) {
});
}
var getCurvePoints = (function(kappa) {
return function(x, y, r1, r2) {
var ox = (r1) * kappa, // control point offset horizontal
oy = (r2) * kappa, // control point offset vertical
xm = x + r1, // x-middle
ym = y + r2; // y-middle
return {
topLeft: bezierCurve({
x:x,
y:ym
}, {
x:x,
y:ym - oy
}, {
x:xm - ox,
y:y
}, {
x:xm,
y:y
}),
topRight: bezierCurve({
x:x,
y:y
}, {
x:x + ox,
y:y
}, {
x:xm,
y:ym - oy
}, {
x:xm,
y:ym
}),
bottomRight: bezierCurve({
x:xm,
y:y
}, {
x:xm,
y:y + oy
}, {
x:x + ox,
y:ym
}, {
x:x,
y:ym
}),
bottomLeft: bezierCurve({
x:xm,
y:ym
}, {
x:xm - ox,
y:ym
}, {
x:x,
y:y + oy
}, {
x:x,
y:y
})
};
function getCurvePoints(x, y, r1, r2) {
var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
var ox = (r1) * kappa, // control point offset horizontal
oy = (r2) * kappa, // control point offset vertical
xm = x + r1, // x-middle
ym = y + r2; // y-middle
return {
topLeft: bezierCurve({
x:x,
y:ym
}, {
x:x,
y:ym - oy
}, {
x:xm - ox,
y:y
}, {
x:xm,
y:y
}),
topRight: bezierCurve({
x:x,
y:y
}, {
x:x + ox,
y:y
}, {
x:xm,
y:ym - oy
}, {
x:xm,
y:ym
}),
bottomRight: bezierCurve({
x:xm,
y:y
}, {
x:xm,
y:y + oy
}, {
x:x + ox,
y:ym
}, {
x:x,
y:ym
}),
bottomLeft: bezierCurve({
x:xm,
y:ym
}, {
x:xm - ox,
y:ym
}, {
x:x,
y:y + oy
}, {
x:x,
y:y
})
};
})(4 * ((Math.sqrt(2) - 1) / 3));
}
function bezierCurve(start, startControl, endControl, end) {
@ -1984,9 +2011,8 @@ _html2canvas.Parse = function (images, options) {
return str.replace("px", "");
}
var transformRegExp = /(matrix)\((.+)\)/;
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 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"));
}
function parseElement (element, stack, pseudoElement) {
function parseElement (element, stack, pseudoElement, cb) {
if (!cb) {
cb = function(){};
}
if (isElementVisible(element)) {
stack = renderElement(element, stack, pseudoElement, false) || stack;
if (!ignoreElementsRegExp.test(element.nodeName)) {
parseChildren(element, stack, pseudoElement);
return parseChildren(element, stack, pseudoElement, cb);
}
}
cb();
}
function parseChildren(element, stack, pseudoElement) {
Util.Children(element).forEach(function(node) {
function parseChildren(element, stack, pseudoElement, cb) {
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) {
parseElement(node, stack, pseudoElement);
parseElement(node, stack, pseudoElement, finished);
} else if (node.nodeType === node.TEXT_NODE) {
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 ) {
var images = {
@ -2697,21 +2725,19 @@ window.html2canvas = function(elements, opts) {
return;
}
}
queue = _html2canvas.Parse( images, options );
if (typeof options.onparsed === "function") {
if ( options.onparsed( queue ) === false ) {
return;
_html2canvas.Parse( images, options, function(queue) {
if (typeof options.onparsed === "function") {
if ( options.onparsed( queue ) === false ) {
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

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);
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 || {};
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 () {
return Math.max(
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) {
var paddingLeft = getCSSInt(element, 'paddingLeft'),
@ -372,69 +401,67 @@ _html2canvas.Parse = function (images, options) {
});
}
var getCurvePoints = (function(kappa) {
return function(x, y, r1, r2) {
var ox = (r1) * kappa, // control point offset horizontal
oy = (r2) * kappa, // control point offset vertical
xm = x + r1, // x-middle
ym = y + r2; // y-middle
return {
topLeft: bezierCurve({
x:x,
y:ym
}, {
x:x,
y:ym - oy
}, {
x:xm - ox,
y:y
}, {
x:xm,
y:y
}),
topRight: bezierCurve({
x:x,
y:y
}, {
x:x + ox,
y:y
}, {
x:xm,
y:ym - oy
}, {
x:xm,
y:ym
}),
bottomRight: bezierCurve({
x:xm,
y:y
}, {
x:xm,
y:y + oy
}, {
x:x + ox,
y:ym
}, {
x:x,
y:ym
}),
bottomLeft: bezierCurve({
x:xm,
y:ym
}, {
x:xm - ox,
y:ym
}, {
x:x,
y:y + oy
}, {
x:x,
y:y
})
};
function getCurvePoints(x, y, r1, r2) {
var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
var ox = (r1) * kappa, // control point offset horizontal
oy = (r2) * kappa, // control point offset vertical
xm = x + r1, // x-middle
ym = y + r2; // y-middle
return {
topLeft: bezierCurve({
x:x,
y:ym
}, {
x:x,
y:ym - oy
}, {
x:xm - ox,
y:y
}, {
x:xm,
y:y
}),
topRight: bezierCurve({
x:x,
y:y
}, {
x:x + ox,
y:y
}, {
x:xm,
y:ym - oy
}, {
x:xm,
y:ym
}),
bottomRight: bezierCurve({
x:xm,
y:y
}, {
x:xm,
y:y + oy
}, {
x:x + ox,
y:ym
}, {
x:x,
y:ym
}),
bottomLeft: bezierCurve({
x:xm,
y:ym
}, {
x:xm - ox,
y:ym
}, {
x:x,
y:y + oy
}, {
x:x,
y:y
})
};
})(4 * ((Math.sqrt(2) - 1) / 3));
}
function bezierCurve(start, startControl, endControl, end) {
@ -953,9 +980,8 @@ _html2canvas.Parse = function (images, options) {
return str.replace("px", "");
}
var transformRegExp = /(matrix)\((.+)\)/;
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 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"));
}
function parseElement (element, stack, pseudoElement) {
function parseElement (element, stack, pseudoElement, cb) {
if (!cb) {
cb = function(){};
}
if (isElementVisible(element)) {
stack = renderElement(element, stack, pseudoElement, false) || stack;
if (!ignoreElementsRegExp.test(element.nodeName)) {
parseChildren(element, stack, pseudoElement);
return parseChildren(element, stack, pseudoElement, cb);
}
}
cb();
}
function parseChildren(element, stack, pseudoElement) {
Util.Children(element).forEach(function(node) {
function parseChildren(element, stack, pseudoElement, cb) {
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) {
parseElement(node, stack, pseudoElement);
parseElement(node, stack, pseudoElement, finished);
} else if (node.nodeType === node.TEXT_NODE) {
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,
letterRendering: 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
width: null,
height: null,
taintTest: true, // do a taint test with all images before applying to canvas
@ -39,21 +39,19 @@ window.html2canvas = function(elements, opts) {
return;
}
}
queue = _html2canvas.Parse( images, options );
if (typeof options.onparsed === "function") {
if ( options.onparsed( queue ) === false ) {
return;
_html2canvas.Parse( images, options, function(queue) {
if (typeof options.onparsed === "function") {
if ( options.onparsed( queue ) === false ) {
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