css堆叠上下文这个不知道你在业务中有没有遇到过,前段时间搭建vuepress1.0就遇到这样的一个类似的问题,主要是用了vuepress-reco这个主题,去官方提了一个issueopen in new window,自己提的issue最后自己找到原因了,但是还是有小伙伴遇到同样的类似问题,今天一起探讨一下这个css堆叠上下文的问题

正文开始...

fixed失效了

我们直接用具体案例来体会css堆叠上下文,因为官方veurpess-reco1.x版本,当你开启右侧子菜单时,右侧的子菜单fixed就失效了。

我们具体写个例子分析下

<div id="app">
    <div class="wrap">
      <div class="subContent">我是fixed在最右侧</div>
      <div class="inner-content"></div>
    </div>
</div>

对应的css如下

* {
  padding: 0;
  margin: 0;
}
.wrap {
  height: 300px;
  border: 1px solid #111;
  margin: 10px;
  overflow-y: auto;
}
.subContent {
  position: fixed;
  right: 10px;
  top: 20px;
  background-color: red;
}

js中我生成了50条数据


function renderHtml() {
  const innerContent = document.getElementsByClassName("inner-content")[0];
  let str = "";
  let max = 50;
  for (let i = 0; i < max; i++) {
    str += `<p>${i}</p>`;
  }
  innerContent.innerHTML = str;
}
renderHtml();

我们知道我给了.subContent的样式是fixed,fixed是相对整个body的,所以此时当你滚动内容时,会一直固定在最右侧

但是恰巧,此时遇上了一个问题fixed失效了,也正是一行css的原因导致的

 .wrap {
    height: 300px;
    border: 1px solid #111;
    margin: 10px;
    overflow-y: auto;
    transform: translateY(0);
}

由于父级.wrap设置了transform导致子级subContentfixed失效了

fixed失效了,所以就是这个父级元素设置的transform: translateY(0)造成的

为了解决这个问题,我们重置了该样式,将其改成了transform:none,于是fixed就正常了,这也是在解决vuepress-reco1.x主题右侧子菜单fixed失效的原因。

什么样情况会造成fixed失效

除了父级设置transform不为none,还有filter不为none也会造成fixed失效

 .wrap {
    height: 300px;
    border: 1px solid #111;
    margin: 10px;
    overflow-y: auto;
    transform: scale(0.5);
    /*filter: blur(1px)*/
  }

堆叠上下文

参考张鑫旭老师的一篇博文深入理解CSS中的层叠上下文和层叠顺序open in new window,参照张鑫旭老师的一张图,大概就是这样

就是我们看到网页上显示是二维的,实际上还有三维,就是一个类似控制transform:translateZ的一个概念

我们只知道在网页中所有可见元素都是由标签组成,所有标签的排列布局其实是由一个经典的概念构成块级格式上下文也俗称BFC,所以整个网页的布局是由BFC这样的特性而构建我们的网页

看一个例子

  <div class="wrap2">
    <div class="leavel-1">leavel-1</div>
    <div class="leavel-2">leavel-2</div>
  </div>
.wrap2 {
  margin: 10px;
}
.leavel-2,.leavel-1 {
  width: 100px;
  height: 100px;

}
.leavel-1 {
    background-color: red;
}
.leavel-2 {
    background-color: green;
}

就会下面这样

正常情况参照BFC特性,两个块级元素就是这样独占一行的排列了,但是如果我给两个元素设置浮动

.leavel-1, .leavel-2 {
  float: left;
}

此时发现就会像下面这样

然后我再设置.leavel-2margin-left: -100px,你就会发现leavel-1被挡住了 初学者可能会好奇,也很容易想到,这leavel-1去哪里了,实际上是在leavel-2的下级,我们把leavel-2的宽度调整一下

隐藏出来的.leavel-1九显示出来了

所以你现在明白了层叠上下文了吗,简单的说,网页的所有元素可以像盖棉被一样,一层一层的往上盖,最新的叠在最上面

我们思考下,从浏览器默认的BFC结构到我们想要看到的堆叠上下文的效果,这中间我们主要做了哪些事情

1、设置了浮动【破坏了文档流】

2、设置.leavel-2的外边距margin-left:-100px【改变了元素的默认排列位置】

所以产生堆叠上下文,必须满足两个条件,一个是原素文档流被破坏,二是元素位置发生变化

定位产生堆叠上下文

其实除了这浮动+margin方式,其实我们还可以用定位去产生堆叠上下文,但实际上也是满足这两个基本的条件

但是如果是用定位,那么有个z-index这个属性是可以影响层叠上下文的顺序的,z-index越小,排得越下面

transform产生堆叠上下文

我们发现浮动+marginposition能产生上下文,除了这两个,新增的css3最新特性中还有transform也可以产生堆叠上下文

因此我们可以这么做

.leavel-2,
.leavel-1 {
  width: 100px;
  height: 100px;
}
.wrap2 {
  margin: 10px;
}
.leavel-1 {
  background: red;
}
.leavel-2 {
  background-color: green;
  transform: translateY(-100px);
}

我们会发现此时leavel-2就把leavel-1完全盖住,因此transform也可以产生堆叠上下文,但实际上这个特性并不是像前面两个一样,并不会破坏文档流,所以这是一个例外,他只是改变自身位置,从而形成了堆叠上下文

堆叠优先级问题

我们看到元素,优先级行内元素是不是最高,比如元素的内容文字,永远在最顶层,然后就是背景,然后就是z-index设置的可见元素

当一个元素同时设置定位transform,影响层叠上下文是怎么样

我们看到fixed会比transform的优先级更高,如果去掉transform,就是就是贴着body排的

所以这就证明,浏览器在处理层叠上下文优先级就是先执行定位,然后再执行transfrom,这只是作用在同一个元素上

回到我们刚开始的问题上,如果是作用在不同的两个父子级上呢

我们文章开头,就是这样的一个例子

父级元素设置了transform: translateY(0)

然后他的子级上设置了一个fixed,于是怪异的问题就发生了,fixed失效了

页面结构大概就是这样

 <div class="wrap">
    <div class="subContent">我是fixed在最右侧</div>
    <div class="inner-content"></div>
 </div>

对应的css如下

.wrap {
      height: 300px;
      border: 1px solid #111;
      margin: 10px;
      overflow-y: auto;
      transform: translateY(0);
      /*filter: blur(1px)*/
    }
  .subContent {
    position: fixed;
    right: 10px;
    top: 20px;
    background-color: red;
  }

那为什么会出现这样的情况?我们画个图理解下

本质上transform:translate(0,0)(10px,-10px)没有差别,图中这么画只是为了更好理解,因此我代码中设置的是translateY(0),所以其实是Y轴方向上往上偏移而已,但是这不影响我们理解这其中的本质。

因为外层父元素设置了transfrom产生了堆叠上下文,而它子元素又想逃脱出去,儿子想造反给自己设置fixed产生一个堆叠上下文,对不起,你必须听老子的,所以子元素设置的fixed就失效了,你还是得跟着老子走

如果你不想因为父级元素transform设置,你想单飞呢,你可以怎么做呢?

唯一的办法,另起炉灶....

因此你可以这么做

<div id="app">
      <div class="subContent">我是fixed在最右侧</div>
      <div class="wrap">
        <div class="inner-content"></div>
      </div>
</div>

没错,你看到的就是,子级元素已经挣脱束缚了,所以我不受被包裹元素tranform的影响了。

不知道你注意到没,其实filter也是和transform一样会产生堆叠上下文,如果子元素被包裹,父级元素设置filter,那么子级元素的fixed也会失效

是不是很惊讶,总之,一句话,父级如果产生了堆叠上下文,子级想要挣脱,对不起,必须听老子的。

好了,终于理清这个堆叠上下文的问题了,所以平时遇到那些奇怪的问题,试来试去,原来是一个css属性设置的原因造成的。

另外思考一个问题,当一个块级子级元素设置width:100%与不设置width,当我们对该元素设置margin时,此时会发生什么?元素本身的宽度是怎么样的,这是一个我们经常遇到的一个问题,想清楚了,貌似你会对margin的作用会有更深的认识。

总结

  • fixed失效的原因,主要是由于产生堆叠上下文造成的
  • 理解堆叠上下文,什么条件会形成堆叠上下文
  • 形成堆叠上下文主要由以下几种
    • 文档流破坏:float+margin,定位postion
    • css新特性:transformfilter会产生堆叠上下文
  • 同一个元素同时使用poistiontransform哪个优先级更高权重更大,首先是会执行定位,然后再执行transform,因此定位的优先级更高,先执行,但是transform权重更大,会作用在定位之上
  • 不同元素产生的堆叠上下文对子级元素造成的影响,如果一个父级产生堆叠上下文,那么它所有的子级元素都不会脱离父级,子元素设置的fixed会失效
  • 最后安利张鑫旭老师的博文,文章很多思考来自深入理解CSS中的层叠上下文和层叠顺序这篇文章
  • 本文示例源码code example
扫二维码,关注公众号
专注前端技术,分享web技术
加作者微信
扫二维码 备注 【加群】
教你聊天恋爱,助力脱单
微信扫小程序二维码,助你寻他/她
专注前端技术,分享Web技术
微信扫小程序二维码,一起学习,一起进步
前端面试大全
海量前端面试经典题,助力前端面试