css堆叠上下文
这个不知道你在业务中有没有遇到过,前段时间搭建vuepress1.0
就遇到这样的一个类似的问题,主要是用了vuepress-reco
这个主题,去官方提了一个issue,自己提的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
导致子级subContent
的fixed
失效了
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中的层叠上下文和层叠顺序,参照张鑫旭老师的一张图,大概就是这样
就是我们看到网页上显示是二维的,实际上还有三维,就是一个类似控制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-2
的margin-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产生堆叠上下文
我们发现浮动+margin
,position
能产生上下文,除了这两个,新增的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新特性:
transform
、filter
会产生堆叠上下文
- 文档流破坏:
- 同一个元素同时使用
poistion
与transform
哪个优先级更高权重更大,首先是会执行定位,然后再执行transform,因此定位的优先级更高,先执行,但是transform权重更大,会作用在定位之上 - 不同元素产生的堆叠上下文对子级元素造成的影响,如果一个父级产生堆叠上下文,那么它所有的子级元素都不会脱离父级,子元素设置的
fixed
会失效 - 最后安利张鑫旭老师的博文,文章很多思考来自
深入理解CSS中的层叠上下文和层叠顺序
这篇文章 - 本文示例源码code example