Absolutely positioned element positions relatively

2019-05-29 14:53发布

问题:

I recreated an issue that I am encountering in a template. There is a nav that has position: relative;. Inside the nav there is a div with two lists nested.
One of the lists is position absolutely to stick to the bottom of the nav. The problem occurs when the div has a transformation applied to it.
When the div in between the absolutely and relatively positioned elements get's a transform property, the absolute list positions itself relatively to the div instead of the nav.

MDN Docs state the following about position:absolute

Do not leave space for the element. Instead, position it at a specified position relative to its closest positioned ancestor if any, or otherwise relative to the containing block. Absolutely positioned boxes can have margins, and they do not collapse with any other margins.

Does this mean a transformed element is a positioned element? Why does it do this? I tested in Edge, FF, and Chrome. They all act the same.

You can run the recreated snippet below. I am applying the transform on the div on hover of the nav.

*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
html, body{
  min-height: 100%;
  height: 100%;
}
nav{
  background: #000;
  height: 100%;
  width: 50%;
  position: relative;
}
nav:hover > div{
      transform: translateX(50px) translateY(0) translateZ(0);
}
a{
  color: #fff;
}
ul{
  padding: 16px;
}
ul.main{
  background: blue;
}

ul.lower{
  position: absolute;
  background: red;
  bottom: 0;
  width: 100%;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<nav>
  <div>
    <ul class="main">
      <li><a href="">link</a></li>
      <li><a href="">link</a></li>
      <li><a href="">link</a></li>
      <li><a href="">link</a></li>
    </ul>
    <ul class="lower">
      <li><a href="">link</a></li>
      <li><a href="">link</a></li>
      <li><a href="">link</a></li>
      <li><a href="">link</a></li>
    </ul>
  </div>
</nav>
</body>
</html>

回答1:

Basically because any element with the transform property applied, automatically triggers a new stacking order: https://developer.mozilla.org/en-US/docs/Web/CSS/transform

"If the property has a value different than none, a stacking context will be created. In that case the object will act as a containing block for position: fixed elements that it contains."

Here is some extra info about stacking context: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context



回答2:

The CSS spec states that defining a transform on an element creates a containing block:

For elements whose layout is governed by the CSS box model, any value other than none for the transform results in the creation of both a stacking context and a containing block.

And that (see spec):

In the absolute positioning model, a box is explicitly offset with respect to its containing block.




The key here is that setting the transform property of an element to anything other thant none, creates a new containing block; stacking contexts have nothing to do with the way the element is positioned.

See examples in the below snippet.

body {
  padding-top: 100px;  
}

.containing-block,
.stacking-context {
  height: 50px;
  padding-top: 50px;
}

.containing-block {
  background-color: hotpink;
  /* transform with a value other than none defines both a containing block and a stacking context. */ 
  transform: scale(1);
}

.stacking-context {
  background-color: orange;
  /* opacity with a value other than 1 defines a stacking context but no containing block. */
  opacity: .99;
}

.abs{
  position: absolute;
  top: 0;
    
  padding: 10px;

  background-color: dodgerblue;
}
<body>
  <div class="containing-block">1: transform: scale(1);
    <div class="abs">1: Containing block example</div>
  </div>

  <div class="stacking-context">2: opacity: .99
    <div class="abs">2: Stacking context example</div>
  </div>
</body>



回答3:

If you apply the transform to .main instead of div it seems to work as expected.