I discovered a strange problem, when I render a page with an element that has a negative z-index (z-index: -1
) and call document.elementsFromPoint
, the result is that visually the element with the negative z-index
appears above the body but elementsFromPoint
places the element between the <html>
and <body>
elements.
<html>
<head></head>
<body>
<main>
<h1>A title</h1>
</main>
<img src="foo.bar" alt="foo bar">
</body>
</html>
If I apply CSS to move the image on top of the heading like this:
body {
background-color: #FFF;
}
img {
position: absolute;
}
Then the stack that I will get under the <h1>
from elementsFromPoint
will be:
<img>
<h1>
<main>
<body>
<html>
Now if I modify that CSS to make the image fall under the heading:
body {
background-color: #FFF;
}
img {
position: absolute;
z-index: -1;
}
Then visually, the image appear below the heading but still appears above the <body>
. However calling elementsFromPoint
I now get:
<h1>
<main>
<body>
<img>
<html>
Notice that the <img>
element is between the <body>
and <html>
elements. This makes sense because the <body>
will have a default z-index
of 0
, so the <img>
element should be rendered beneath it.
But if that's the case, why is the browser rendering the image above the <body>
? I have checked this in both Chrome and Firefox and the results are the same.
Edit: Here is a demonstration of this weird effect. The bread slices are both content from the <body>
tag, the other two images are sandwiched in between.
body {
background-image: url('https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/microsoft/209/bread_1f35e.png');
background-repeat: no-repeat;
font-size: 110px;
padding: 0.25em;
}
img {
position: absolute;
left: 10px;
top: 10px;
z-index: -1;
}
img + img {
top: 25px;
left: 25px;
}
🍞
<img src="https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/microsoft/209/herb_1f33f.png" alt="greens in the sandwich">
<img src="https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/microsoft/209/bacon_1f953.png" alt="meat between the bread">
First, default z-index
is auto
and not 0
. There is a difference because the last one will create a stacking context making the element inside it (even with negative z-index
) to always be rendred above. More details here: Why can't an element with a z-index value cover its child?
Then you are facing background propagation which is a special behavior when setting background color to body element which will make you think that the element is not behind.
Then your image is set to position:absolute
making it out of the flow thus it won't affect the height of the body so even if it's behind we can still see it.
Here is some examples to illustrate different cases:
body {
position: relative;
background-color: #000;
}
img {
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
}
h1 {
margin: 0;
background-color: rgba(255, 255, 255);
padding: 0.5em 1em;
text-align: center;
font-size: 2.5rem;
}
<main>
<h1>A polar bear</h1>
<pre>
</pre>
</main>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3d/Polar_Bear_AdF.jpg" alt="A Polar Bear">
In the above the background is propagated from body to html, the image is rendred behind the body and the body height is equal to h1
.
Let's disable the propagation by setting a background to html
body {
position: relative;
background-color: #000;
}
html {
background:red;
}
img {
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
}
h1 {
margin: 0;
background-color: rgba(255, 255, 255);
padding: 0.5em 1em;
text-align: center;
font-size: 2.5rem;
}
<main>
<h1>A polar bear</h1>
<pre>
</pre>
</main>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3d/Polar_Bear_AdF.jpg" alt="A Polar Bear">
Now it's more clear that we have the html (red) then the image then the body (black) and its content. You can also see that the height the body is restricted to its in-flow content.
Now let's add z-index:0
to body:
body {
position: relative;
background-color: #000;
z-index:0;
}
html {
background:red;
}
img {
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
}
h1 {
margin: 0;
background-color: rgba(255, 255, 255);
padding: 0.5em 1em;
text-align: center;
font-size: 2.5rem;
}
<main>
<h1>A polar bear</h1>
<pre>
</pre>
</main>
<img src="https://upload.wikimedia.org/wikipedia/commons/3/3d/Polar_Bear_AdF.jpg" alt="A Polar Bear">
The body is now creating a stacking context forcing the image to be painted inside and the order is now html then body then its content (the image then the heading)