Skip to content

Commit 16d3bef

Browse files
committed
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+)
1 parent 0277c34 commit 16d3bef

8 files changed

Lines changed: 596 additions & 37 deletions

File tree

src/Parse.js

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -314,22 +314,36 @@ _html2canvas.Parse = function (images, options) {
314314
};
315315
}
316316

317-
function setZ(zIndex, parentZ){
318-
// TODO fix static elements overlapping relative/absolute elements under same stack, if they are defined after them
319-
var newContext;
320-
if (!parentZ){
321-
newContext = h2czContext(0);
322-
return newContext;
317+
function setZ(element, stack, parentStack){
318+
var newContext,
319+
position = stack.cssPosition,
320+
zIndex = getCSS(element, 'zIndex'),
321+
opacity = getCSS(element, 'opacity'), // can't use stack.opacity because it's blended
322+
isPositioned = position !== 'static',
323+
isFloated = getCSS(element, 'cssFloat') !== 'none';
324+
325+
// https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
326+
// When a new stacking context should be created:
327+
// the root element (HTML),
328+
// positioned (absolutely or relatively) with a z-index value other than "auto",
329+
// elements with an opacity value less than 1. (See the specification for opacity),
330+
// on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is "auto" (See this post)
331+
332+
// z-index only applies to positioned elements.
333+
// however, firefox may return the value set in CSS even if it's not positioned
334+
if (!isPositioned) { zIndex = 0 ;}
335+
stack.zIndex = newContext = h2czContext(zIndex);
336+
newContext.isPositioned = isPositioned;
337+
newContext.isFloated = isFloated;
338+
339+
if (!parentStack || (zIndex !== 'auto' && isPositioned) ||
340+
(opacity && Number(opacity) < 1)) {
341+
newContext.ownStacking = true;
323342
}
324343

325-
if (zIndex !== "auto"){
326-
newContext = h2czContext(zIndex);
327-
parentZ.children.push(newContext);
328-
return newContext;
329-
344+
if (parentStack) {
345+
parentStack.zIndex.children.push(stack);
330346
}
331-
332-
return parentZ;
333347
}
334348

335349
function renderImage(ctx, element, image, bounds, borders) {
@@ -953,21 +967,21 @@ _html2canvas.Parse = function (images, options) {
953967

954968
var ctx = h2cRenderContext((!parentStack) ? documentWidth() : bounds.width , (!parentStack) ? documentHeight() : bounds.height),
955969
stack = {
970+
el: element, // very useful when debugging
956971
ctx: ctx,
957-
zIndex: setZ(getCSS(element, "zIndex"), (parentStack) ? parentStack.zIndex : null),
958972
opacity: setOpacity(ctx, element, parentStack),
959973
cssPosition: getCSS(element, "position"),
960974
borders: getBorderData(element),
961975
clip: (parentStack && parentStack.clip) ? _html2canvas.Util.Extend( {}, parentStack.clip ) : null
962976
};
963977

978+
setZ(element, stack, parentStack);
979+
964980
// TODO correct overflow for absolute content residing under a static position
965981
if (options.useOverflow === true && /(hidden|scroll|auto)/.test(getCSS(element, "overflow")) === true && /(BODY)/i.test(element.nodeName) === false){
966982
stack.clip = (stack.clip) ? clipBounds(stack.clip, bounds) : bounds;
967983
}
968984

969-
stack.zIndex.children.push(stack);
970-
971985
return stack;
972986
}
973987

src/Renderer.js

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,82 @@
11
_html2canvas.Renderer = function(parseQueue, options){
22

3+
// http://www.w3.org/TR/CSS21/zindex.html
34
function createRenderQueue(parseQueue) {
4-
var queue = [];
5+
var queue = [],
6+
rootContext;
57

6-
var sortZ = function(zStack){
7-
var subStacks = [],
8-
stackValues = [];
8+
rootContext = (function buildStackingContext(rootNode) {
9+
var rootContext = {};
10+
function insert(context, node, specialParent) {
11+
var zi = node.zIndex.zindex,
12+
contextForChildren = context, // the stacking context for children
13+
isPositioned = node.zIndex.isPositioned,
14+
isFloated = node.zIndex.isFloated,
15+
stub = {node: node},
16+
childrenDest; // where children without z-index should be pushed into
917

10-
zStack.children.forEach(function(stackChild) {
11-
if (stackChild.children && stackChild.children.length > 0){
12-
subStacks.push(stackChild);
13-
stackValues.push(stackChild.zindex);
18+
if (zi === 'auto') { zi = 0; }
19+
zi = Number(zi);
20+
if (!context[zi]) { context[zi] = []; }
21+
if (node.zIndex.ownStacking) {
22+
contextForChildren = stub.context = { 0: [{node:node}]};
23+
if (isPositioned || isFloated) {
24+
childrenDest = contextForChildren[0][0].children = [];
25+
}
26+
} else if (isPositioned || isFloated) {
27+
childrenDest = stub.children = [];
28+
}
29+
30+
if (zi === 0 && specialParent) {
31+
specialParent.push(stub);
1432
} else {
15-
queue.push(stackChild);
33+
context[zi].push(stub);
1634
}
17-
});
1835

19-
stackValues.sort(function(a, b) {
20-
return a - b;
21-
});
36+
node.zIndex.children.forEach(function(childNode) {
37+
insert(contextForChildren, childNode, childrenDest);
38+
});
39+
}
40+
insert(rootContext, rootNode);
41+
return rootContext;
42+
})(parseQueue);
2243

23-
stackValues.forEach(function(zValue) {
24-
var index;
44+
function sortZ(context) {
45+
Object.keys(context).sort().forEach(function(zi) {
46+
var nonPositioned = [],
47+
floated = [],
48+
positioned = [],
49+
list = [];
2550

26-
subStacks.some(function(stack, i){
27-
index = i;
28-
return (stack.zindex === zValue);
51+
// positioned after static
52+
context[zi].forEach(function(v) {
53+
if (v.node.zIndex.isFloated) {
54+
floated.push(v);
55+
} else if (v.node.zIndex.isPositioned) {
56+
positioned.push(v);
57+
} else {
58+
nonPositioned.push(v);
59+
}
2960
});
30-
sortZ(subStacks.splice(index, 1)[0]);
3161

62+
(function walk(arr) {
63+
arr.forEach(function(v) {
64+
list.push(v);
65+
if (v.children) { walk(v.children); }
66+
});
67+
})(nonPositioned.concat(floated, positioned));
68+
69+
list.forEach(function(v) {
70+
if (v.context) {
71+
sortZ(v.context);
72+
} else {
73+
queue.push(v.node);
74+
}
75+
});
3276
});
33-
};
77+
}
3478

35-
sortZ(parseQueue.zIndex);
79+
sortZ(rootContext);
3680

3781
return queue;
3882
}

tests/cases/zindex/z-index10.html

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3+
4+
<html>
5+
<head>
6+
<!-- https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context?redirectlocale=en-US&redirectslug=CSS%2FUnderstanding_z-index%2FThe_stacking_context
7+
-->
8+
<title>Understanding CSS z-index: The Stacking Context: Example Source</title>
9+
<script type="text/javascript" src="../../test.js"></script>
10+
11+
<style type="text/css">
12+
* {
13+
margin: 0;
14+
}
15+
html {
16+
padding: 20px;
17+
font: 12px/20px Arial, sans-serif;
18+
}
19+
div {
20+
opacity: 0.7;
21+
position: relative;
22+
}
23+
h1 {
24+
font: inherit;
25+
font-weight: bold;
26+
}
27+
#div1, #div2 {
28+
border: 1px dashed #696;
29+
padding: 10px;
30+
background-color: #cfc;
31+
}
32+
#div1 {
33+
z-index: 5;
34+
margin-bottom: 190px;
35+
}
36+
#div2 {
37+
z-index: 2;
38+
}
39+
#div3 {
40+
z-index: 4;
41+
opacity: 1;
42+
position: absolute;
43+
top: 40px;
44+
left: 180px;
45+
width: 330px;
46+
border: 1px dashed #900;
47+
background-color: #fdd;
48+
padding: 40px 20px 20px;
49+
}
50+
#div4, #div5 {
51+
border: 1px dashed #996;
52+
background-color: #ffc;
53+
}
54+
#div4 {
55+
z-index: 6;
56+
margin-bottom: 15px;
57+
padding: 25px 10px 5px;
58+
}
59+
#div5 {
60+
z-index: 1;
61+
margin-top: 15px;
62+
padding: 5px 10px;
63+
}
64+
#div6 {
65+
z-index: 3;
66+
position: absolute;
67+
top: 20px;
68+
left: 180px;
69+
width: 150px;
70+
height: 125px;
71+
border: 1px dashed #009;
72+
padding-top: 125px;
73+
background-color: #ddf;
74+
text-align: center;
75+
}
76+
</style>
77+
78+
</head>
79+
<body>
80+
81+
<div id="div1">
82+
<h1>Division Element #1</h1>
83+
<code>position: relative;<br/>
84+
z-index: 5;</code>
85+
</div>
86+
87+
<div id="div2">
88+
<h1>Division Element #2</h1>
89+
<code>position: relative;<br/>
90+
z-index: 2;</code>
91+
</div>
92+
93+
<div id="div3">
94+
95+
<div id="div4">
96+
<h1>Division Element #4</h1>
97+
<code>position: relative;<br/>
98+
z-index: 6;</code>
99+
</div>
100+
101+
<h1>Division Element #3</h1>
102+
<code>position: absolute;<br/>
103+
z-index: 4;</code>
104+
105+
<div id="div5">
106+
<h1>Division Element #5</h1>
107+
<code>position: relative;<br/>
108+
z-index: 1;</code>
109+
</div>
110+
111+
<div id="div6">
112+
<h1>Division Element #6</h1>
113+
<code>position: absolute;<br/>
114+
z-index: 3;</code>
115+
</div>
116+
117+
</div>
118+
119+
</body>
120+
</html>

tests/cases/zindex/z-index5.html

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>z-index tests #5</title>
5+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6+
<script type="text/javascript" src="../../test.js"></script>
7+
<style type="text/css">
8+
div.lev1 {
9+
width: 250px;
10+
height: 70px;
11+
position: relative;
12+
border: 2px solid #669966;
13+
background-color: #ccffcc;
14+
padding-left: 5px;
15+
}
16+
div.background {
17+
position: fixed;
18+
top: 0;
19+
bottom: 0;
20+
width: 250px;
21+
background-color: #ffdddd;
22+
}
23+
</style></head>
24+
25+
<body>
26+
<div class="lev1">
27+
LEVEL #1
28+
</div>
29+
<div class="background"></div>
30+
<div class="lev1">
31+
<span>LEVEL #1</span>
32+
</div>
33+
</body>
34+
</html>

tests/cases/zindex/z-index6.html

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>z-index tests #6</title>
5+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
6+
<script type="text/javascript" src="../../test.js"></script>
7+
<style type="text/css">
8+
div {
9+
width: 250px;
10+
height: 70px;
11+
position: relative;
12+
border: 2px solid #669966;
13+
background-color: #ccffcc;
14+
padding-left: 5px;
15+
}
16+
div.z0 {
17+
z-index: 0;
18+
top:105px;
19+
left:20px;
20+
background-color: #ffdddd;
21+
}
22+
23+
div.z1 {
24+
z-index: 1;
25+
}
26+
</style></head>
27+
28+
<body>
29+
<div class="z0"><span>z-index:0</span></div>
30+
<div>default z-index</div>
31+
<div class="z1">z-index:1</div>
32+
</body>
33+
</html>

0 commit comments

Comments
 (0)