diff --git a/src/Parse.js b/src/Parse.js index 028f907..3089ef1 100644 --- a/src/Parse.js +++ b/src/Parse.js @@ -314,22 +314,36 @@ _html2canvas.Parse = function (images, options) { }; } - function setZ(zIndex, parentZ){ - // TODO fix static elements overlapping relative/absolute elements under same stack, if they are defined after them - var newContext; - if (!parentZ){ - newContext = h2czContext(0); - return newContext; + function setZ(element, stack, parentStack){ + var newContext, + position = stack.cssPosition, + zIndex = getCSS(element, 'zIndex'), + opacity = getCSS(element, 'opacity'), // can't use stack.opacity because it's blended + isPositioned = position !== 'static', + isFloated = getCSS(element, 'cssFloat') !== 'none'; + + // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context + // When a new stacking context should be created: + // the root element (HTML), + // positioned (absolutely or relatively) with a z-index value other than "auto", + // elements with an opacity value less than 1. (See the specification for opacity), + // on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto" (See this post) + + // z-index only applies to positioned elements. + // however, firefox may return the value set in CSS even if it's not positioned + if (!isPositioned) { zIndex = 0 ;} + stack.zIndex = newContext = h2czContext(zIndex); + newContext.isPositioned = isPositioned; + newContext.isFloated = isFloated; + + if (!parentStack || (zIndex !== 'auto' && isPositioned) || + (opacity && Number(opacity) < 1)) { + newContext.ownStacking = true; } - if (zIndex !== "auto"){ - newContext = h2czContext(zIndex); - parentZ.children.push(newContext); - return newContext; - + if (parentStack) { + parentStack.zIndex.children.push(stack); } - - return parentZ; } function renderImage(ctx, element, image, bounds, borders) { @@ -953,21 +967,21 @@ _html2canvas.Parse = function (images, options) { var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height), stack = { + el: element, // very useful when debugging ctx: ctx, - zIndex: setZ(getCSS(element, "zIndex"), (parentStack) ? parentStack.zIndex : null), opacity: setOpacity(ctx, element, parentStack), cssPosition: getCSS(element, "position"), borders: getBorderData(element), clip: (parentStack && parentStack.clip) ? _html2canvas.Util.Extend( {}, parentStack.clip ) : null }; + setZ(element, stack, parentStack); + // TODO correct overflow for absolute content residing under a static position if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){ stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds; } - stack.zIndex.children.push(stack); - return stack; } diff --git a/src/Renderer.js b/src/Renderer.js index da1e46b..ab0659b 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -1,38 +1,82 @@ _html2canvas.Renderer = function(parseQueue, options){ + // http://www.w3.org/TR/CSS21/zindex.html function createRenderQueue(parseQueue) { - var queue = []; + var queue = [], + rootContext; - var sortZ = function(zStack){ - var subStacks = [], - stackValues = []; + rootContext = (function buildStackingContext(rootNode) { + var rootContext = {}; + function insert(context, node, specialParent) { + var zi = node.zIndex.zindex, + contextForChildren = context, // the stacking context for children + isPositioned = node.zIndex.isPositioned, + isFloated = node.zIndex.isFloated, + stub = {node: node}, + childrenDest; // where children without z-index should be pushed into - zStack.children.forEach(function(stackChild) { - if (stackChild.children && stackChild.children.length > 0){ - subStacks.push(stackChild); - stackValues.push(stackChild.zindex); - } else { - queue.push(stackChild); + if (zi === 'auto') { zi = 0; } + zi = Number(zi); + if (!context[zi]) { context[zi] = []; } + if (node.zIndex.ownStacking) { + contextForChildren = stub.context = { 0: [{node:node}]}; + if (isPositioned || isFloated) { + childrenDest = contextForChildren[0][0].children = []; + } + } else if (isPositioned || isFloated) { + childrenDest = stub.children = []; } - }); - stackValues.sort(function(a, b) { - return a - b; - }); + if (zi === 0 && specialParent) { + specialParent.push(stub); + } else { + context[zi].push(stub); + } - stackValues.forEach(function(zValue) { - var index; - - subStacks.some(function(stack, i){ - index = i; - return (stack.zindex === zValue); + node.zIndex.children.forEach(function(childNode) { + insert(contextForChildren, childNode, childrenDest); }); - sortZ(subStacks.splice(index, 1)[0]); + } + insert(rootContext, rootNode); + return rootContext; + })(parseQueue); + function sortZ(context) { + Object.keys(context).sort().forEach(function(zi) { + var nonPositioned = [], + floated = [], + positioned = [], + list = []; + + // positioned after static + context[zi].forEach(function(v) { + if (v.node.zIndex.isFloated) { + floated.push(v); + } else if (v.node.zIndex.isPositioned) { + positioned.push(v); + } else { + nonPositioned.push(v); + } + }); + + (function walk(arr) { + arr.forEach(function(v) { + list.push(v); + if (v.children) { walk(v.children); } + }); + })(nonPositioned.concat(floated, positioned)); + + list.forEach(function(v) { + if (v.context) { + sortZ(v.context); + } else { + queue.push(v.node); + } + }); }); - }; + } - sortZ(parseQueue.zIndex); + sortZ(rootContext); return queue; } diff --git a/tests/cases/zindex/z-index10.html b/tests/cases/zindex/z-index10.html new file mode 100644 index 0000000..e931289 --- /dev/null +++ b/tests/cases/zindex/z-index10.html @@ -0,0 +1,120 @@ + + + +
+ +position: relative;
+ z-index: 5;
+ position: relative;
+ z-index: 2;
+ position: relative;
+ z-index: 6;
+ position: absolute;
+ z-index: 4;
+
+ position: relative;
+ z-index: 1;
+ position: absolute;
+ z-index: 3;
+