z-index overhaul. relative above static; negative z-index

1. when stacking without z-index, positioned > floated > normal flow
2. supports negative z-index
3. new stacking context formed when opacity < 1 (standard)
4. new stacking context formed for position:fixed (no standard yet, done in mobile webkit and chrome 22+)
This commit is contained in:
arrix
2013-08-06 00:09:31 +08:00
parent 0277c34310
commit 16d3bef255
8 changed files with 598 additions and 39 deletions

View File

@ -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;
}

View File

@ -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;
}