mirror of
https://github.com/niklasvh/html2canvas.git
synced 2023-08-10 21:13:10 +03:00
Speed & accuracy improvements to pseudo element rendering.
Previously, pseudo elements would be processed as they were found in the DOM tree, which was an expensive operation as each element's computed :before and :after style was checked for 'content' styles. This commit traverses the user's stylesheets for :before and :after selectors, gathers the classes affected, selects all elements that likely have a pseudo element present, then checks computed style. If there is actually an element present, it is created but *not* appended to the DOM until after all elements have been processed. After all elements have been found and created, they are added to the DOM in a single batch, and the original pseudo elements are hidden in a single batch. This prevents the layout invalidation / relayout loop that was occuring previously, and in my tests speeds parsing by as much as 50% or more, depending on how many pseudo elements your page uses. Additionally, this commit contains a bugfix to the handling of ":before" pseudo elements; the browser effectively inserts them as the first child of the element, not before the element. This fixes a few rendering inconsistencies and complicated pages look almost perfect in my tests.
This commit is contained in:
parent
11417a16f0
commit
b35d1e585b
@ -1041,28 +1041,35 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
body = doc.body,
|
body = doc.body,
|
||||||
getCSS = Util.getCSS,
|
getCSS = Util.getCSS,
|
||||||
pseudoHide = "___html2canvas___pseudoelement",
|
pseudoHide = "___html2canvas___pseudoelement",
|
||||||
hidePseudoElements = doc.createElement('style');
|
hidePseudoElementsStyles = doc.createElement('style');
|
||||||
|
|
||||||
hidePseudoElements.innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' +
|
hidePseudoElementsStyles.innerHTML = '.' + pseudoHide +
|
||||||
'.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }';
|
'-parent:before { content: "" !important; display: none !important; }' +
|
||||||
|
'.' + pseudoHide + '-parent:after { content: "" !important; display: none !important; }';
|
||||||
|
|
||||||
body.appendChild(hidePseudoElements);
|
body.appendChild(hidePseudoElementsStyles);
|
||||||
|
|
||||||
images = images || {};
|
images = images || {};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var background = getCSS(document.documentElement, "backgroundColor"),
|
var background = getCSS(document.documentElement, "backgroundColor"),
|
||||||
transparentBackground = (Util.isTransparent(background) && element === document.body),
|
transparentBackground = (Util.isTransparent(background) && element === document.body),
|
||||||
stack = renderElement(element, null, false, transparentBackground);
|
stack = renderElement(element, null, false, transparentBackground);
|
||||||
|
|
||||||
|
// create pseudo elements in a single pass to prevent synchronous layouts
|
||||||
|
addPseudoElements(element);
|
||||||
|
|
||||||
parseChildren(element, stack, null, function() {
|
parseChildren(element, stack, function() {
|
||||||
if (transparentBackground) {
|
if (transparentBackground) {
|
||||||
background = stack.backgroundColor;
|
background = stack.backgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removePseudoElements();
|
||||||
|
|
||||||
Util.log('Done parsing, moving to Render.');
|
Util.log('Done parsing, moving to Render.');
|
||||||
|
|
||||||
body.removeChild(hidePseudoElements);
|
|
||||||
cb({
|
cb({
|
||||||
backgroundColor: background,
|
backgroundColor: background,
|
||||||
stack: stack
|
stack: stack
|
||||||
@ -1070,7 +1077,126 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
// Given a root element, find all pseudo elements below, create elements mocking pseudo element styles
|
||||||
|
// so we can process them as normal elements, and hide the original pseudo elements so they don't interfere
|
||||||
|
// with layout.
|
||||||
|
function addPseudoElements(el) {
|
||||||
|
// These are done in discrete steps to prevent a relayout loop caused by addClass() invalidating
|
||||||
|
// layouts & getPseudoElement calling getComputedStyle.
|
||||||
|
var jobs = [], classes = [];
|
||||||
|
getPseudoElementClasses();
|
||||||
|
findPseudoElements(el);
|
||||||
|
runJobs();
|
||||||
|
|
||||||
|
function getPseudoElementClasses(){
|
||||||
|
var findPsuedoEls = /:before|:after/;
|
||||||
|
var sheets = document.styleSheets;
|
||||||
|
for (var i = 0, j = sheets.length; i < j; i++) {
|
||||||
|
try {
|
||||||
|
var rules = sheets[i].cssRules;
|
||||||
|
for (var k = 0, l = rules.length; k < l; k++) {
|
||||||
|
if(findPsuedoEls.test(rules[k].selectorText)) {
|
||||||
|
classes.push(rules[k].selectorText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(e) { // will throw security exception for style sheets loaded from external domains
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim off the :after and :before (or ::after and ::before)
|
||||||
|
for (i = 0, j = classes.length; i < j; i++) {
|
||||||
|
classes[i] = classes[i].match(/(^[^:]*)/)[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using the list of elements we know how pseudo el styles, create fake pseudo elements.
|
||||||
|
function findPseudoElements(el) {
|
||||||
|
var els = document.querySelectorAll(classes.join(','));
|
||||||
|
for(var i = 0, j = els.length; i < j; i++) {
|
||||||
|
createPseudoElements(els[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pseudo elements & add them to a job queue.
|
||||||
|
function createPseudoElements(el) {
|
||||||
|
var before = getPseudoElement(el, ':before'),
|
||||||
|
after = getPseudoElement(el, ':after');
|
||||||
|
|
||||||
|
if(before) {
|
||||||
|
jobs.push({type: 'before', pseudo: before, el: el});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (after) {
|
||||||
|
jobs.push({type: 'after', pseudo: after, el: el});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a class to the pseudo's parent to prevent the original before/after from messing
|
||||||
|
// with layouts.
|
||||||
|
// Execute the inserts & addClass() calls in a batch to prevent relayouts.
|
||||||
|
function runJobs() {
|
||||||
|
// Add Class
|
||||||
|
jobs.forEach(function(job){
|
||||||
|
addClass(job.el, pseudoHide + "-parent");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert el
|
||||||
|
jobs.forEach(function(job){
|
||||||
|
if(job.type === 'before'){
|
||||||
|
job.el.insertBefore(job.pseudo, job.el.firstChild);
|
||||||
|
} else {
|
||||||
|
job.el.appendChild(job.pseudo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Delete our fake pseudo elements from the DOM. This will remove those actual elements
|
||||||
|
// and the classes on their parents that hide the actual pseudo elements.
|
||||||
|
// Note that NodeLists are 'live' collections so you can't use a for loop here. They are
|
||||||
|
// actually deleted from the NodeList after each iteration.
|
||||||
|
function removePseudoElements(){
|
||||||
|
// delete pseudo elements
|
||||||
|
body.removeChild(hidePseudoElementsStyles);
|
||||||
|
var pseudos = document.getElementsByClassName(pseudoHide + "-element");
|
||||||
|
while (pseudos.length) {
|
||||||
|
pseudos[0].parentNode.removeChild(pseudos[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pseudo hiding classes
|
||||||
|
var parents = document.getElementsByClassName(pseudoHide + "-parent");
|
||||||
|
while(parents.length) {
|
||||||
|
removeClass(parents[0], pseudoHide + "-parent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addClass (el, className) {
|
||||||
|
if (el.classList) {
|
||||||
|
el.classList.add(className);
|
||||||
|
} else {
|
||||||
|
el.className = el.className + " " + className;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeClass (el, className) {
|
||||||
|
if (el.classList) {
|
||||||
|
el.classList.remove(className);
|
||||||
|
} else {
|
||||||
|
el.className = el.className.replace(className, "").trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasClass (el, className) {
|
||||||
|
return el.className.indexOf(className) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this doesn't work in < IE8, but we don't support that anyhow
|
||||||
|
function nodeListToArray (nodeList) {
|
||||||
|
return Array.prototype.slice.call(nodeList);
|
||||||
|
}
|
||||||
|
|
||||||
function documentWidth () {
|
function documentWidth () {
|
||||||
return Math.max(
|
return Math.max(
|
||||||
@ -1831,20 +1957,25 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
|
|
||||||
function getPseudoElement(el, which) {
|
function getPseudoElement(el, which) {
|
||||||
var elStyle = window.getComputedStyle(el, which);
|
var elStyle = window.getComputedStyle(el, which);
|
||||||
if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || elStyle.display === "none") {
|
var parentStyle = window.getComputedStyle(el);
|
||||||
|
// If no content attribute is present, the pseudo element is hidden,
|
||||||
|
// or the parent has a content property equal to the content on the pseudo element,
|
||||||
|
// move along.
|
||||||
|
if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" ||
|
||||||
|
elStyle.display === "none" || parentStyle.content === elStyle.content) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var content = elStyle.content + '',
|
var content = elStyle.content + '';
|
||||||
first = content.substr( 0, 1 );
|
|
||||||
//strips quotes
|
// Strip inner quotes
|
||||||
if(first === content.substr( content.length - 1 ) && first.match(/'|"/)) {
|
if(content[0] === "'" || content[0] === "\"") {
|
||||||
content = content.substr( 1, content.length - 2 );
|
content = content.replace(/(^['"])|(['"]$)/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
var isImage = content.substr( 0, 3 ) === 'url',
|
var isImage = content.substr( 0, 3 ) === 'url',
|
||||||
elps = document.createElement( isImage ? 'img' : 'span' );
|
elps = document.createElement( isImage ? 'img' : 'span' );
|
||||||
|
|
||||||
elps.className = pseudoHide + "-before " + pseudoHide + "-after";
|
elps.className = pseudoHide + "-element ";
|
||||||
|
|
||||||
Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
|
Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
|
||||||
// Prevent assigning of read only CSS Rules, ex. length, parentRule
|
// Prevent assigning of read only CSS Rules, ex. length, parentRule
|
||||||
@ -1867,31 +1998,6 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
return (isNaN(window.parseInt(property, 10)));
|
return (isNaN(window.parseInt(property, 10)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectPseudoElements(el, stack) {
|
|
||||||
var before = getPseudoElement(el, ':before'),
|
|
||||||
after = getPseudoElement(el, ':after');
|
|
||||||
if(!before && !after) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(before) {
|
|
||||||
el.className += " " + pseudoHide + "-before";
|
|
||||||
el.parentNode.insertBefore(before, el);
|
|
||||||
parseElement(before, stack, true);
|
|
||||||
el.parentNode.removeChild(before);
|
|
||||||
el.className = el.className.replace(pseudoHide + "-before", "").trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (after) {
|
|
||||||
el.className += " " + pseudoHide + "-after";
|
|
||||||
el.appendChild(after);
|
|
||||||
parseElement(after, stack, true);
|
|
||||||
el.removeChild(after);
|
|
||||||
el.className = el.className.replace(pseudoHide + "-after", "").trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
|
function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
|
||||||
var offsetX = Math.round(bounds.left + backgroundPosition.left),
|
var offsetX = Math.round(bounds.left + backgroundPosition.left),
|
||||||
offsetY = Math.round(bounds.top + backgroundPosition.top);
|
offsetY = Math.round(bounds.top + backgroundPosition.top);
|
||||||
@ -2079,7 +2185,7 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderElement(element, parentStack, pseudoElement, ignoreBackground) {
|
function renderElement(element, parentStack, ignoreBackground) {
|
||||||
var transform = getTransform(element, parentStack),
|
var transform = getTransform(element, parentStack),
|
||||||
bounds = getBounds(element, transform),
|
bounds = getBounds(element, transform),
|
||||||
image,
|
image,
|
||||||
@ -2109,10 +2215,6 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
renderBorders(ctx, border.args, border.color);
|
renderBorders(ctx, border.args, border.color);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!pseudoElement) {
|
|
||||||
injectPseudoElements(element, stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(element.nodeName){
|
switch(element.nodeName){
|
||||||
case "IMG":
|
case "IMG":
|
||||||
if ((image = loadImage(element.getAttribute('src')))) {
|
if ((image = loadImage(element.getAttribute('src')))) {
|
||||||
@ -2153,20 +2255,20 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
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, cb) {
|
function parseElement (element, stack, cb) {
|
||||||
if (!cb) {
|
if (!cb) {
|
||||||
cb = function(){};
|
cb = function(){};
|
||||||
}
|
}
|
||||||
if (isElementVisible(element)) {
|
if (isElementVisible(element)) {
|
||||||
stack = renderElement(element, stack, pseudoElement, false) || stack;
|
stack = renderElement(element, stack, false) || stack;
|
||||||
if (!ignoreElementsRegExp.test(element.nodeName)) {
|
if (!ignoreElementsRegExp.test(element.nodeName)) {
|
||||||
return parseChildren(element, stack, pseudoElement, cb);
|
return parseChildren(element, stack, cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseChildren(element, stack, pseudoElement, cb) {
|
function parseChildren(element, stack, cb) {
|
||||||
var children = Util.Children(element);
|
var children = Util.Children(element);
|
||||||
// After all nodes have processed, finished() will call the cb.
|
// 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.
|
// We add one and kick it off so this will still work when children.length === 0.
|
||||||
@ -2185,7 +2287,7 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
|
|
||||||
function parseNode(node) {
|
function parseNode(node) {
|
||||||
if (node.nodeType === node.ELEMENT_NODE) {
|
if (node.nodeType === node.ELEMENT_NODE) {
|
||||||
parseElement(node, stack, pseudoElement, finished);
|
parseElement(node, stack, finished);
|
||||||
} else if (node.nodeType === node.TEXT_NODE) {
|
} else if (node.nodeType === node.TEXT_NODE) {
|
||||||
renderText(element, node, stack);
|
renderText(element, node, stack);
|
||||||
finished();
|
finished();
|
||||||
@ -2706,9 +2808,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
|
||||||
|
4
build/html2canvas.min.js
vendored
4
build/html2canvas.min.js
vendored
File diff suppressed because one or more lines are too long
200
src/Parse.js
200
src/Parse.js
@ -10,28 +10,35 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
body = doc.body,
|
body = doc.body,
|
||||||
getCSS = Util.getCSS,
|
getCSS = Util.getCSS,
|
||||||
pseudoHide = "___html2canvas___pseudoelement",
|
pseudoHide = "___html2canvas___pseudoelement",
|
||||||
hidePseudoElements = doc.createElement('style');
|
hidePseudoElementsStyles = doc.createElement('style');
|
||||||
|
|
||||||
hidePseudoElements.innerHTML = '.' + pseudoHide + '-before:before { content: "" !important; display: none !important; }' +
|
hidePseudoElementsStyles.innerHTML = '.' + pseudoHide +
|
||||||
'.' + pseudoHide + '-after:after { content: "" !important; display: none !important; }';
|
'-parent:before { content: "" !important; display: none !important; }' +
|
||||||
|
'.' + pseudoHide + '-parent:after { content: "" !important; display: none !important; }';
|
||||||
|
|
||||||
body.appendChild(hidePseudoElements);
|
body.appendChild(hidePseudoElementsStyles);
|
||||||
|
|
||||||
images = images || {};
|
images = images || {};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
var background = getCSS(document.documentElement, "backgroundColor"),
|
var background = getCSS(document.documentElement, "backgroundColor"),
|
||||||
transparentBackground = (Util.isTransparent(background) && element === document.body),
|
transparentBackground = (Util.isTransparent(background) && element === document.body),
|
||||||
stack = renderElement(element, null, false, transparentBackground);
|
stack = renderElement(element, null, false, transparentBackground);
|
||||||
|
|
||||||
|
// create pseudo elements in a single pass to prevent synchronous layouts
|
||||||
|
addPseudoElements(element);
|
||||||
|
|
||||||
parseChildren(element, stack, null, function() {
|
parseChildren(element, stack, function() {
|
||||||
if (transparentBackground) {
|
if (transparentBackground) {
|
||||||
background = stack.backgroundColor;
|
background = stack.backgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removePseudoElements();
|
||||||
|
|
||||||
Util.log('Done parsing, moving to Render.');
|
Util.log('Done parsing, moving to Render.');
|
||||||
|
|
||||||
body.removeChild(hidePseudoElements);
|
|
||||||
cb({
|
cb({
|
||||||
backgroundColor: background,
|
backgroundColor: background,
|
||||||
stack: stack
|
stack: stack
|
||||||
@ -39,7 +46,126 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
// Given a root element, find all pseudo elements below, create elements mocking pseudo element styles
|
||||||
|
// so we can process them as normal elements, and hide the original pseudo elements so they don't interfere
|
||||||
|
// with layout.
|
||||||
|
function addPseudoElements(el) {
|
||||||
|
// These are done in discrete steps to prevent a relayout loop caused by addClass() invalidating
|
||||||
|
// layouts & getPseudoElement calling getComputedStyle.
|
||||||
|
var jobs = [], classes = [];
|
||||||
|
getPseudoElementClasses();
|
||||||
|
findPseudoElements(el);
|
||||||
|
runJobs();
|
||||||
|
|
||||||
|
function getPseudoElementClasses(){
|
||||||
|
var findPsuedoEls = /:before|:after/;
|
||||||
|
var sheets = document.styleSheets;
|
||||||
|
for (var i = 0, j = sheets.length; i < j; i++) {
|
||||||
|
try {
|
||||||
|
var rules = sheets[i].cssRules;
|
||||||
|
for (var k = 0, l = rules.length; k < l; k++) {
|
||||||
|
if(findPsuedoEls.test(rules[k].selectorText)) {
|
||||||
|
classes.push(rules[k].selectorText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(e) { // will throw security exception for style sheets loaded from external domains
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim off the :after and :before (or ::after and ::before)
|
||||||
|
for (i = 0, j = classes.length; i < j; i++) {
|
||||||
|
classes[i] = classes[i].match(/(^[^:]*)/)[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using the list of elements we know how pseudo el styles, create fake pseudo elements.
|
||||||
|
function findPseudoElements(el) {
|
||||||
|
var els = document.querySelectorAll(classes.join(','));
|
||||||
|
for(var i = 0, j = els.length; i < j; i++) {
|
||||||
|
createPseudoElements(els[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create pseudo elements & add them to a job queue.
|
||||||
|
function createPseudoElements(el) {
|
||||||
|
var before = getPseudoElement(el, ':before'),
|
||||||
|
after = getPseudoElement(el, ':after');
|
||||||
|
|
||||||
|
if(before) {
|
||||||
|
jobs.push({type: 'before', pseudo: before, el: el});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (after) {
|
||||||
|
jobs.push({type: 'after', pseudo: after, el: el});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a class to the pseudo's parent to prevent the original before/after from messing
|
||||||
|
// with layouts.
|
||||||
|
// Execute the inserts & addClass() calls in a batch to prevent relayouts.
|
||||||
|
function runJobs() {
|
||||||
|
// Add Class
|
||||||
|
jobs.forEach(function(job){
|
||||||
|
addClass(job.el, pseudoHide + "-parent");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert el
|
||||||
|
jobs.forEach(function(job){
|
||||||
|
if(job.type === 'before'){
|
||||||
|
job.el.insertBefore(job.pseudo, job.el.firstChild);
|
||||||
|
} else {
|
||||||
|
job.el.appendChild(job.pseudo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Delete our fake pseudo elements from the DOM. This will remove those actual elements
|
||||||
|
// and the classes on their parents that hide the actual pseudo elements.
|
||||||
|
// Note that NodeLists are 'live' collections so you can't use a for loop here. They are
|
||||||
|
// actually deleted from the NodeList after each iteration.
|
||||||
|
function removePseudoElements(){
|
||||||
|
// delete pseudo elements
|
||||||
|
body.removeChild(hidePseudoElementsStyles);
|
||||||
|
var pseudos = document.getElementsByClassName(pseudoHide + "-element");
|
||||||
|
while (pseudos.length) {
|
||||||
|
pseudos[0].parentNode.removeChild(pseudos[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pseudo hiding classes
|
||||||
|
var parents = document.getElementsByClassName(pseudoHide + "-parent");
|
||||||
|
while(parents.length) {
|
||||||
|
removeClass(parents[0], pseudoHide + "-parent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addClass (el, className) {
|
||||||
|
if (el.classList) {
|
||||||
|
el.classList.add(className);
|
||||||
|
} else {
|
||||||
|
el.className = el.className + " " + className;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeClass (el, className) {
|
||||||
|
if (el.classList) {
|
||||||
|
el.classList.remove(className);
|
||||||
|
} else {
|
||||||
|
el.className = el.className.replace(className, "").trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasClass (el, className) {
|
||||||
|
return el.className.indexOf(className) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this doesn't work in < IE8, but we don't support that anyhow
|
||||||
|
function nodeListToArray (nodeList) {
|
||||||
|
return Array.prototype.slice.call(nodeList);
|
||||||
|
}
|
||||||
|
|
||||||
function documentWidth () {
|
function documentWidth () {
|
||||||
return Math.max(
|
return Math.max(
|
||||||
@ -800,20 +926,25 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
|
|
||||||
function getPseudoElement(el, which) {
|
function getPseudoElement(el, which) {
|
||||||
var elStyle = window.getComputedStyle(el, which);
|
var elStyle = window.getComputedStyle(el, which);
|
||||||
if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" || elStyle.display === "none") {
|
var parentStyle = window.getComputedStyle(el);
|
||||||
|
// If no content attribute is present, the pseudo element is hidden,
|
||||||
|
// or the parent has a content property equal to the content on the pseudo element,
|
||||||
|
// move along.
|
||||||
|
if(!elStyle || !elStyle.content || elStyle.content === "none" || elStyle.content === "-moz-alt-content" ||
|
||||||
|
elStyle.display === "none" || parentStyle.content === elStyle.content) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var content = elStyle.content + '',
|
var content = elStyle.content + '';
|
||||||
first = content.substr( 0, 1 );
|
|
||||||
//strips quotes
|
// Strip inner quotes
|
||||||
if(first === content.substr( content.length - 1 ) && first.match(/'|"/)) {
|
if(content[0] === "'" || content[0] === "\"") {
|
||||||
content = content.substr( 1, content.length - 2 );
|
content = content.replace(/(^['"])|(['"]$)/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
var isImage = content.substr( 0, 3 ) === 'url',
|
var isImage = content.substr( 0, 3 ) === 'url',
|
||||||
elps = document.createElement( isImage ? 'img' : 'span' );
|
elps = document.createElement( isImage ? 'img' : 'span' );
|
||||||
|
|
||||||
elps.className = pseudoHide + "-before " + pseudoHide + "-after";
|
elps.className = pseudoHide + "-element ";
|
||||||
|
|
||||||
Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
|
Object.keys(elStyle).filter(indexedProperty).forEach(function(prop) {
|
||||||
// Prevent assigning of read only CSS Rules, ex. length, parentRule
|
// Prevent assigning of read only CSS Rules, ex. length, parentRule
|
||||||
@ -836,31 +967,6 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
return (isNaN(window.parseInt(property, 10)));
|
return (isNaN(window.parseInt(property, 10)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectPseudoElements(el, stack) {
|
|
||||||
var before = getPseudoElement(el, ':before'),
|
|
||||||
after = getPseudoElement(el, ':after');
|
|
||||||
if(!before && !after) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(before) {
|
|
||||||
el.className += " " + pseudoHide + "-before";
|
|
||||||
el.parentNode.insertBefore(before, el);
|
|
||||||
parseElement(before, stack, true);
|
|
||||||
el.parentNode.removeChild(before);
|
|
||||||
el.className = el.className.replace(pseudoHide + "-before", "").trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (after) {
|
|
||||||
el.className += " " + pseudoHide + "-after";
|
|
||||||
el.appendChild(after);
|
|
||||||
parseElement(after, stack, true);
|
|
||||||
el.removeChild(after);
|
|
||||||
el.className = el.className.replace(pseudoHide + "-after", "").trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
|
function renderBackgroundRepeat(ctx, image, backgroundPosition, bounds) {
|
||||||
var offsetX = Math.round(bounds.left + backgroundPosition.left),
|
var offsetX = Math.round(bounds.left + backgroundPosition.left),
|
||||||
offsetY = Math.round(bounds.top + backgroundPosition.top);
|
offsetY = Math.round(bounds.top + backgroundPosition.top);
|
||||||
@ -1048,7 +1154,7 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderElement(element, parentStack, pseudoElement, ignoreBackground) {
|
function renderElement(element, parentStack, ignoreBackground) {
|
||||||
var transform = getTransform(element, parentStack),
|
var transform = getTransform(element, parentStack),
|
||||||
bounds = getBounds(element, transform),
|
bounds = getBounds(element, transform),
|
||||||
image,
|
image,
|
||||||
@ -1078,10 +1184,6 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
renderBorders(ctx, border.args, border.color);
|
renderBorders(ctx, border.args, border.color);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!pseudoElement) {
|
|
||||||
injectPseudoElements(element, stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(element.nodeName){
|
switch(element.nodeName){
|
||||||
case "IMG":
|
case "IMG":
|
||||||
if ((image = loadImage(element.getAttribute('src')))) {
|
if ((image = loadImage(element.getAttribute('src')))) {
|
||||||
@ -1122,20 +1224,20 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
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, cb) {
|
function parseElement (element, stack, cb) {
|
||||||
if (!cb) {
|
if (!cb) {
|
||||||
cb = function(){};
|
cb = function(){};
|
||||||
}
|
}
|
||||||
if (isElementVisible(element)) {
|
if (isElementVisible(element)) {
|
||||||
stack = renderElement(element, stack, pseudoElement, false) || stack;
|
stack = renderElement(element, stack, false) || stack;
|
||||||
if (!ignoreElementsRegExp.test(element.nodeName)) {
|
if (!ignoreElementsRegExp.test(element.nodeName)) {
|
||||||
return parseChildren(element, stack, pseudoElement, cb);
|
return parseChildren(element, stack, cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseChildren(element, stack, pseudoElement, cb) {
|
function parseChildren(element, stack, cb) {
|
||||||
var children = Util.Children(element);
|
var children = Util.Children(element);
|
||||||
// After all nodes have processed, finished() will call the cb.
|
// 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.
|
// We add one and kick it off so this will still work when children.length === 0.
|
||||||
@ -1154,7 +1256,7 @@ _html2canvas.Parse = function (images, options, cb) {
|
|||||||
|
|
||||||
function parseNode(node) {
|
function parseNode(node) {
|
||||||
if (node.nodeType === node.ELEMENT_NODE) {
|
if (node.nodeType === node.ELEMENT_NODE) {
|
||||||
parseElement(node, stack, pseudoElement, finished);
|
parseElement(node, stack, finished);
|
||||||
} else if (node.nodeType === node.TEXT_NODE) {
|
} else if (node.nodeType === node.TEXT_NODE) {
|
||||||
renderText(element, node, stack);
|
renderText(element, node, stack);
|
||||||
finished();
|
finished();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user