position:sticky失效问题剖析
一、position:sticky生效的原理
在 W3 官方文档中的定义是:Sticky positioning is similar to relative positioning except the offsets are automatically calculated in reference to the nearest scrollport.
通俗来说就是,Sticky 定位类似于相对定位relative,在没有触发sticky特性前是relative定位,当它表现为 sticky的特性时,是fixed定位,会根据最近的滚动容器(nearest scrollport)自动计算偏移量,其中有一个**非常重要非常重要非常重要的的概念就是 nearest scrollport,它表示 sticky 元素在即将消失前会相对它最近的 scrollport 去做定位**。
二、正常的Demo
.box1{
width: 300px;
height: 1200px;
margin-top: 200px;
padding-top: 100px;
background-color: aqua;
}
.box2{
width: 200px;
height: 300px;
background-color: red;
position: sticky;
top: 0;
}
<div class="box1">
<div class="box2"></div>
</div>
在此demo中,首先查看设置sticky属性的box2参考的滚动父级元素是body,但是实际上body之所以滚动,是因为box2的父级元素box1的高度撑开了body,所以box2设置的top是相对于body的
二、position: stick生效与失效原理
2.1 参考滚动父级元素是body
(1) 包裹的父容器设置了 overflow: hidden会失效
.box1{
width: 300px;
height: 1200px;
margin-top: 200px;
padding-top: 100px;
overflow: auto;
/* overflow: hidden;
overflow: scroll;
overflow: overlay; */ //父元素设置了除overflow:visible以外其他属性,都会使sticky失效
background-color: aqua;
}
.box2{
width: 200px;
height: 300px;
background-color: red;
position: sticky;
top: 0;
}
<div class="box1">
box1
<div class="box2">box2</div>
</div>
在此demo中,首先查看设置sticky属性的box2参考的滚动父级元素是body,但是实际上body之所以滚动,是因为box2的父级元素box1的高度撑开了body,所以box2设置的top是相对于body的,但是sticky还是失效了,失效的原因在于box2的父级元素box1设置了 overflow: hidden/auto/scroll/overlay ,此时设置sticky属性的box2元素参考的滚动元素就不是body了,而是box1,而box1的高度比box2的高度大得多,并不足以让box1实现滚动,所以无法作为box2的参考父级滚动元素,进而失效了
那么思考一个问题,在多层级的情况下,并不是在其直接父元素上设置 overflow:hidden/auto/scroll/overlay,而是在其祖先元素上设置overflow,会影响子元素的sticky属性吗?
答案是:会影响的,一样的道理,只要参考的父级滚动元素无法滚动,都是不会让其内部设置sticky的元素生效
.container{
width: 700px;
height: 2900px;
margin-top: 200px;
background-color: bisque;
padding-top: 80px;
/* overflow: auto; */
}
.parent{ //box的祖先元素
padding-top: 80px;
width: 600px;
height: 800px;
overflow: hidden; //在box的祖先元素处设置overflow:hidden
background-color: aqua;
}
.child{ //box的父级元素
width: 500px;
padding-top: 50px;
height: 500px;
background-color: red;
}
.box{
width: 500px;
height: 200px;
background-color: blueviolet;
position: sticky;
top: 0;
}
<div class="container">
container
<div class="parent">
parent
<div class="child">
child
<div class="box">box</div>
</div>
</div>
<div class="borther"></div>
</div>
所以,设定为 position: sticky 元素的任意父节点的 overflow 属性必须是 visible,否则 position:sticky 不会生效
(2) 包裹的最近的父容器高度小于等于sticky 元素的高度会失效
在多级嵌套的情况下,如图例,box的实际参考滚动父级元素是body,但是外层还是有几层父级结构child、parent、container(实际应用场景可当做是设置sticky元素的外层layout布局等),此时注意,如果包裹的最近的父容器高度小于等于sticky 元素的高度,也是不生效的,按道理说此时box应该是在参考的滚动父级元素body中进行sticky定位的,但是并不可以,虽然box的实际参考滚动父级元素是body,但是设置的sticky的元素box还是依赖于它包裹的最近的父容器child进行滚动,待child的高度滚动结束,box的sticky属性也会失效
.container{
width: 700px;
height: 2900px;
margin-top: 200px;
background-color: bisque;
padding-top: 80px;
/* overflow: auto; */
}
.parent{
padding-top: 80px;
width: 600px;
height: 500px;
background-color: aqua;
}
.child{
width: 500px;
padding-top: 50px;
height: auto; //父元素高度自动计算
background-color: red;
}
.box{
width: 500px;
height: 200px;
background-color: blueviolet;
position: sticky;
top: 0;
}
上图所示,sticky元素.box的直接父级元素.child高度没有比sticky元素大,无法实现sticky效果
再思考一个问题,在实际多层级的情况下,并不是在其直接父级上加上一个比sticky元素大的高度,而是在其祖先元素上加一个比sticky元素小的高度,可以实现sticky效果吗?
答案是:可以
.container{
width: 700px;
height: 2900px;
margin-top: 200px;
background-color: bisque;
padding-top: 80px;
/* overflow: auto; */
}
.parent{
padding-top: 80px;
width: 600px;
height: 500px; //祖先元素高度设置了500px
background-color: aqua;
}
.child{
width: 500px;
padding-top: 50px;
height: 1600; //最近父级元素设置高度1600px
background-color: red;
}
.box{
width: 500px;
height: 200px;
background-color: blueviolet;
position: sticky;
top: 0;
}
2.2 参考滚动父级元素是自己设置的内部元素
此处应该就要和上面的参考滚动父级是body做区分了,并不是在设置sticky元素的上方所有父级都不可设置overflow。此时设置sticky元素wrap1的参考滚动父级元素是wrap,而wrap的滚动是由wrap2的高度影响的,wrap2的高度比wrap高,如果不在wrap元素上设置overflow:auto/scroll/overlay,wrap是无法滚动的。而正因为wrap可以滚动,那么此时的wrap就可以当做是wrap1的参考滚动父级元素,wrap1元素的sticky效果在wrap中实现
<div class="wrap">
<div class="wrap1">固定头部</div>
<div class="wrap2">box2</div>
</div>
.wrap{
margin-top: 100px;
width: 200px;
height: 500px;
background-color: blanchedalmond;
overflow: auto;
}
.wrap1{
width: 100px;
height: 30px;
background-color: aqua;
position: sticky;
top: 0;
}
.wrap2{
width: 100px;
height: 2000px;
background-color: yellow;
}
总结
看完上面几个 DEMO,可以好好总结一下 position:sticky 的生效规则,明白了生效规则就会知道为什么有的时候设定的 sticky 会失效:
1、须确定设置sticky元素的参考滚动父级元素
2、指定 top, right, bottom 或 left 四个阈值其中之一(且达到设定的阈值),才可使粘性定位生效。否则其行为与相对定位相同;并且 top 和 bottom 同时设置时,top 生效的优先级高,left 和 right 同时设置时,left 的优先级高
3、如果sticky元素的参考滚动父级元素是body,设定为 position: sticky 的元素的任意父节点的 overflow 属性必须是 visible,不能是hidden/auto/scroll/overlay,否则 position:sticky 不会生效;且包裹的最近的父容器高度要大于sticky 元素的高度;如果sticky元素的参考滚动父级元素是自己设置的内部元素,参考滚动父级元素可以设置overflow :/auto/scroll/overlay,且参考滚动父级元素的高度要比内部元素小