Preserve scrolling positions of nodes on clone (#511)

This commit is contained in:
MoyuScript 2015-02-28 16:51:52 +02:00
parent 9c0ad6baab
commit d59edbd1cf
5 changed files with 107 additions and 124 deletions

77
dist/html2canvas.js vendored
View File

@ -1542,34 +1542,14 @@ process.umask = function() { return 0; };
var log = require('./log'); var log = require('./log');
var Promise = require('./promise'); var Promise = require('./promise');
var html2canvasCanvasCloneAttribute = "data-html2canvas-canvas-clone";
var html2canvasCanvasCloneIndex = 0;
function cloneNodeValues(document, clone, nodeName) {
var originalNodes = document.getElementsByTagName(nodeName);
var clonedNodes = clone.getElementsByTagName(nodeName);
var count = originalNodes.length;
for (var i = 0; i < count; i++) {
clonedNodes[i].value = originalNodes[i].value;
}
}
function restoreOwnerScroll(ownerDocument, x, y) { function restoreOwnerScroll(ownerDocument, x, y) {
if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) { if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) {
ownerDocument.defaultView.scrollTo(x, y); ownerDocument.defaultView.scrollTo(x, y);
} }
} }
function labelCanvasElements(ownerDocument) { function cloneCanvasContents(canvas, clonedCanvas) {
[].slice.call(ownerDocument.querySelectorAll("canvas"), 0).forEach(function(canvas) {
canvas.setAttribute(html2canvasCanvasCloneAttribute, "canvas-" + html2canvasCanvasCloneIndex++);
});
}
function cloneCanvasContents(ownerDocument, documentClone) {
[].slice.call(ownerDocument.querySelectorAll("[" + html2canvasCanvasCloneAttribute + "]"), 0).forEach(function(canvas) {
try { try {
var clonedCanvas = documentClone.querySelector('[' + html2canvasCanvasCloneAttribute + '="' + canvas.getAttribute(html2canvasCanvasCloneAttribute) + '"]');
if (clonedCanvas) { if (clonedCanvas) {
clonedCanvas.width = canvas.width; clonedCanvas.width = canvas.width;
clonedCanvas.height = canvas.height; clonedCanvas.height = canvas.height;
@ -1578,49 +1558,47 @@ function cloneCanvasContents(ownerDocument, documentClone) {
} catch(e) { } catch(e) {
log("Unable to copy canvas content from", canvas, e); log("Unable to copy canvas content from", canvas, e);
} }
canvas.removeAttribute(html2canvasCanvasCloneAttribute);
});
} }
function removeScriptNodes(parent) { function cloneNode(node, javascriptEnabled) {
[].slice.call(parent.childNodes, 0).filter(isElementNode).forEach(function(node) {
if (node.tagName === "SCRIPT") {
parent.removeChild(node);
} else {
removeScriptNodes(node);
}
});
return parent;
}
function isIE9() {
return document.documentMode && document.documentMode <= 9;
}
// https://github.com/niklasvh/html2canvas/issues/503
function cloneNodeIE9(node, javascriptEnabled) {
var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false); var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);
var child = node.firstChild; var child = node.firstChild;
while(child) { while(child) {
if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') { if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') {
clone.appendChild(cloneNodeIE9(child, javascriptEnabled)); clone.appendChild(cloneNode(child, javascriptEnabled));
} }
child = child.nextSibling; child = child.nextSibling;
} }
if (node.nodeType === 1) {
clone._scrollTop = node.scrollTop;
clone._scrollLeft = node.scrollLeft;
if (node.nodeName === "CANVAS") {
cloneCanvasContents(node, clone);
} else if (node.nodeName === "TEXTAREA" || node.nodeName === "SELECT") {
clone.value = node.value;
}
}
return clone; return clone;
} }
function initNode(node) {
if (node.nodeType === 1) {
node.scrollTop = node._scrollTop;
node.scrollLeft = node._scrollLeft;
var child = node.firstChild;
function isElementNode(node) { while(child) {
return node.nodeType === Node.ELEMENT_NODE; initNode(child);
child = child.nextSibling;
}
}
} }
module.exports = function(ownerDocument, containerDocument, width, height, options, x ,y) { module.exports = function(ownerDocument, containerDocument, width, height, options, x ,y) {
labelCanvasElements(ownerDocument); var documentElement = cloneNode(ownerDocument.documentElement, options.javascriptEnabled);
var documentElement = isIE9() ? cloneNodeIE9(ownerDocument.documentElement, options.javascriptEnabled) : ownerDocument.documentElement.cloneNode(true);
var container = containerDocument.createElement("iframe"); var container = containerDocument.createElement("iframe");
container.className = "html2canvas-container"; container.className = "html2canvas-container";
@ -1637,16 +1615,13 @@ module.exports = function(ownerDocument, containerDocument, width, height, optio
return new Promise(function(resolve) { return new Promise(function(resolve) {
var documentClone = container.contentWindow.document; var documentClone = container.contentWindow.document;
cloneNodeValues(ownerDocument.documentElement, documentElement, "textarea");
cloneNodeValues(ownerDocument.documentElement, documentElement, "select");
/* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle /* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle
if window url is about:blank, we can assign the url to current by writing onto the document if window url is about:blank, we can assign the url to current by writing onto the document
*/ */
container.contentWindow.onload = container.onload = function() { container.contentWindow.onload = container.onload = function() {
var interval = setInterval(function() { var interval = setInterval(function() {
if (documentClone.body.childNodes.length > 0) { if (documentClone.body.childNodes.length > 0) {
cloneCanvasContents(ownerDocument, documentClone); initNode(documentClone.documentElement);
clearInterval(interval); clearInterval(interval);
if (options.type === "view") { if (options.type === "view") {
container.contentWindow.scrollTo(x, y); container.contentWindow.scrollTo(x, y);
@ -1660,7 +1635,7 @@ module.exports = function(ownerDocument, containerDocument, width, height, optio
documentClone.write("<!DOCTYPE html><html></html>"); documentClone.write("<!DOCTYPE html><html></html>");
// Chrome scrolls the parent document for some reason after the write to the cloned window??? // Chrome scrolls the parent document for some reason after the write to the cloned window???
restoreOwnerScroll(ownerDocument, x, y); restoreOwnerScroll(ownerDocument, x, y);
documentClone.replaceChild(options.javascriptEnabled === true ? documentClone.adoptNode(documentElement) : removeScriptNodes(documentClone.adoptNode(documentElement)), documentClone.documentElement); documentClone.replaceChild(documentClone.adoptNode(documentElement), documentClone.documentElement);
documentClone.close(); documentClone.close();
}); });
}; };

File diff suppressed because one or more lines are too long

View File

@ -1,34 +1,14 @@
var log = require('./log'); var log = require('./log');
var Promise = require('./promise'); var Promise = require('./promise');
var html2canvasCanvasCloneAttribute = "data-html2canvas-canvas-clone";
var html2canvasCanvasCloneIndex = 0;
function cloneNodeValues(document, clone, nodeName) {
var originalNodes = document.getElementsByTagName(nodeName);
var clonedNodes = clone.getElementsByTagName(nodeName);
var count = originalNodes.length;
for (var i = 0; i < count; i++) {
clonedNodes[i].value = originalNodes[i].value;
}
}
function restoreOwnerScroll(ownerDocument, x, y) { function restoreOwnerScroll(ownerDocument, x, y) {
if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) { if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) {
ownerDocument.defaultView.scrollTo(x, y); ownerDocument.defaultView.scrollTo(x, y);
} }
} }
function labelCanvasElements(ownerDocument) { function cloneCanvasContents(canvas, clonedCanvas) {
[].slice.call(ownerDocument.querySelectorAll("canvas"), 0).forEach(function(canvas) {
canvas.setAttribute(html2canvasCanvasCloneAttribute, "canvas-" + html2canvasCanvasCloneIndex++);
});
}
function cloneCanvasContents(ownerDocument, documentClone) {
[].slice.call(ownerDocument.querySelectorAll("[" + html2canvasCanvasCloneAttribute + "]"), 0).forEach(function(canvas) {
try { try {
var clonedCanvas = documentClone.querySelector('[' + html2canvasCanvasCloneAttribute + '="' + canvas.getAttribute(html2canvasCanvasCloneAttribute) + '"]');
if (clonedCanvas) { if (clonedCanvas) {
clonedCanvas.width = canvas.width; clonedCanvas.width = canvas.width;
clonedCanvas.height = canvas.height; clonedCanvas.height = canvas.height;
@ -37,49 +17,47 @@ function cloneCanvasContents(ownerDocument, documentClone) {
} catch(e) { } catch(e) {
log("Unable to copy canvas content from", canvas, e); log("Unable to copy canvas content from", canvas, e);
} }
canvas.removeAttribute(html2canvasCanvasCloneAttribute);
});
} }
function removeScriptNodes(parent) { function cloneNode(node, javascriptEnabled) {
[].slice.call(parent.childNodes, 0).filter(isElementNode).forEach(function(node) {
if (node.tagName === "SCRIPT") {
parent.removeChild(node);
} else {
removeScriptNodes(node);
}
});
return parent;
}
function isIE9() {
return document.documentMode && document.documentMode <= 9;
}
// https://github.com/niklasvh/html2canvas/issues/503
function cloneNodeIE9(node, javascriptEnabled) {
var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false); var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);
var child = node.firstChild; var child = node.firstChild;
while(child) { while(child) {
if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') { if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') {
clone.appendChild(cloneNodeIE9(child, javascriptEnabled)); clone.appendChild(cloneNode(child, javascriptEnabled));
} }
child = child.nextSibling; child = child.nextSibling;
} }
if (node.nodeType === 1) {
clone._scrollTop = node.scrollTop;
clone._scrollLeft = node.scrollLeft;
if (node.nodeName === "CANVAS") {
cloneCanvasContents(node, clone);
} else if (node.nodeName === "TEXTAREA" || node.nodeName === "SELECT") {
clone.value = node.value;
}
}
return clone; return clone;
} }
function initNode(node) {
if (node.nodeType === 1) {
node.scrollTop = node._scrollTop;
node.scrollLeft = node._scrollLeft;
var child = node.firstChild;
function isElementNode(node) { while(child) {
return node.nodeType === Node.ELEMENT_NODE; initNode(child);
child = child.nextSibling;
}
}
} }
module.exports = function(ownerDocument, containerDocument, width, height, options, x ,y) { module.exports = function(ownerDocument, containerDocument, width, height, options, x ,y) {
labelCanvasElements(ownerDocument); var documentElement = cloneNode(ownerDocument.documentElement, options.javascriptEnabled);
var documentElement = isIE9() ? cloneNodeIE9(ownerDocument.documentElement, options.javascriptEnabled) : ownerDocument.documentElement.cloneNode(true);
var container = containerDocument.createElement("iframe"); var container = containerDocument.createElement("iframe");
container.className = "html2canvas-container"; container.className = "html2canvas-container";
@ -96,16 +74,13 @@ module.exports = function(ownerDocument, containerDocument, width, height, optio
return new Promise(function(resolve) { return new Promise(function(resolve) {
var documentClone = container.contentWindow.document; var documentClone = container.contentWindow.document;
cloneNodeValues(ownerDocument.documentElement, documentElement, "textarea");
cloneNodeValues(ownerDocument.documentElement, documentElement, "select");
/* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle /* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle
if window url is about:blank, we can assign the url to current by writing onto the document if window url is about:blank, we can assign the url to current by writing onto the document
*/ */
container.contentWindow.onload = container.onload = function() { container.contentWindow.onload = container.onload = function() {
var interval = setInterval(function() { var interval = setInterval(function() {
if (documentClone.body.childNodes.length > 0) { if (documentClone.body.childNodes.length > 0) {
cloneCanvasContents(ownerDocument, documentClone); initNode(documentClone.documentElement);
clearInterval(interval); clearInterval(interval);
if (options.type === "view") { if (options.type === "view") {
container.contentWindow.scrollTo(x, y); container.contentWindow.scrollTo(x, y);
@ -119,7 +94,7 @@ module.exports = function(ownerDocument, containerDocument, width, height, optio
documentClone.write("<!DOCTYPE html><html></html>"); documentClone.write("<!DOCTYPE html><html></html>");
// Chrome scrolls the parent document for some reason after the write to the cloned window??? // Chrome scrolls the parent document for some reason after the write to the cloned window???
restoreOwnerScroll(ownerDocument, x, y); restoreOwnerScroll(ownerDocument, x, y);
documentClone.replaceChild(options.javascriptEnabled === true ? documentClone.adoptNode(documentElement) : removeScriptNodes(documentClone.adoptNode(documentElement)), documentClone.documentElement); documentClone.replaceChild(documentClone.adoptNode(documentElement), documentClone.documentElement);
documentClone.close(); documentClone.close();
}); });
}; };

View File

@ -108,7 +108,7 @@
CanvasRenderer.prototype.text = function(text) { CanvasRenderer.prototype.text = function(text) {
expect(text).to.equal('updated'); expect(text).to.equal('updated');
}; };
html2canvas(document.querySelector("#block4"), {renderer: CanvasRenderer, strict: true, logging: true, removeContainer: false}).then(function(canvas) { html2canvas(document.querySelector("#block4"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
expect(canvas.width).to.equal(200); expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200); expect(canvas.height).to.equal(200);
done(); done();
@ -138,7 +138,7 @@
CanvasRenderer.prototype.text = function(text) { CanvasRenderer.prototype.text = function(text) {
expect(text).to.equal('3'); expect(text).to.equal('3');
}; };
html2canvas(document.querySelector("#block5"), {renderer: CanvasRenderer, strict: true, logging: true, removeContainer: false}).then(function(canvas) { html2canvas(document.querySelector("#block5"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
expect(canvas.width).to.equal(200); expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200); expect(canvas.height).to.equal(200);
done(); done();

View File

@ -12,6 +12,12 @@
<body> <body>
<div id="mocha"></div> <div id="mocha"></div>
<script>mocha.setup('bdd')</script> <script>mocha.setup('bdd')</script>
<div id="scroll-render" style="height: 200px; width: 200px;">
<div style="height: 500px; width: 400px;overflow: scroll;" id="scrollable">
<div style="height: 500px;background: red;"></div>
<div style="height: 650px; background: green"></div>
</div>
</div>
<div style="height: 2200px;"></div> <div style="height: 2200px;"></div>
<div style="height: 500px;background: green;"><a name="content">content</a></div> <div style="height: 500px;background: green;"><a name="content">content</a></div>
<script> <script>
@ -32,7 +38,7 @@
window.location.hash = "#content"; window.location.hash = "#content";
setTimeout(function() { setTimeout(function() {
var top = $(window).scrollTop(); var top = $(window).scrollTop();
html2canvas(document.body, {type: 'view', removeContainer: false}).then(function () { html2canvas(document.body, {type: 'view'}).then(function () {
var currentTop = $(window).scrollTop(); var currentTop = $(window).scrollTop();
window.location.hash = ""; window.location.hash = "";
expect(currentTop).to.be.greaterThan(1500); expect(currentTop).to.be.greaterThan(1500);
@ -45,7 +51,34 @@
}); });
}, 0); }, 0);
}); });
it("with content scroll", function (done) {
$("#scrollable").scrollTop(500);
setTimeout(function() {
html2canvas(document.querySelector("#scroll-render")).then(function (canvas) {
expect($("#scrollable").scrollTop()).to.equal(500);
document.body.appendChild(canvas);
expect(canvas.width).to.equal(200);
expect(canvas.height).to.equal(200);
validCanvasPixels(canvas);
done();
}).catch(function (error) {
done(error);
}); });
}, 0);
});
});
function validCanvasPixels(canvas) {
var ctx = canvas.getContext("2d");
var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
for (var i = 0, len = 200*199*4; i < len; i+=4) {
if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
console.log(i, data[i], data[i+1], data[i+2], data[i+3]);
expect().fail("Invalid canvas data");
}
}
}
after(function() { after(function() {
if (history) { if (history) {