根据 MDN,固定位置元素被视为相对位置元素,直到超过指定阈值
这里完全是语言问题,因为上面的句子并不意味着元素必须以position:relative 然后开始固定。它说直到超过指定的阈值。那么如果最初我们超过了指定的阈值怎么办?这实际上是您的示例的情况。
换句话说,position:sticky 有两种状态。
- 它被视为相对
- 超过指定阈值时视为已修复
哪个是第一个取决于您的 HTML 结构。
下面是一个基本的例子来说明:
body {
height:150vh;
margin:0;
display:flex;
flex-direction:column;
border:2px solid;
margin:50px;
}
.b {
margin-top:auto;
position:sticky;
bottom:0;
}
.a {
position:sticky;
top:0;
}
<div class="a">
I will start relative then I will be fixed
</div>
<div class="b">
I will start fixed then I will be relative
</div>
您也可以混合使用。我们开始固定,变得相对,然后再次固定:
body {
height:250vh;
margin:0;
display:flex;
flex-direction:column;
border:2px solid;
margin:50px;
}
body:before,
body:after {
content:"";
flex:1;
}
.a {
position:sticky;
top:0;
bottom:0;
}
<div class="a">
I will start fixed then relative then fixed
</div>
正如您在上面的示例中看到的,两个状态都是独立的。如果position:fixed 的条件为真,那么我们有position:fixed,如果不是,那么它是相对的。
我们可以认为浏览器会实现这个伪代码:
on_scroll_event() {
if(threshold exceeded)
position <- fixed
else
position <- relative
}
为了更准确和完整地理解机制,您需要考虑 3 个要素。粘性元素(以及 top/bottom/left/right 的值)、粘性元素的包含块和最近的带有滚动框的祖先。
- 带有滚动框的最近的祖先只是最近的溢出不同于可见的祖先,默认情况下它将是视口(正如我在这里解释的那样:What are `scrolling boxes`?)。此元素上的滚动将控制粘性行为。
- 粘性元素的包含块与相对元素的包含块相同ref
左/上/下/右是相对于滚动框计算的,包含块将定义粘性元素的限制。
这里有一个例子来说明:
body {
margin:0;
}
.wrapper {
width:300px;
height:150px;
border:2px solid red;
overflow:auto;
}
.parent {
height:200%;
margin:100% 0;
border:2px solid;
}
.sticky {
position:sticky;
display:inline-block;
margin:auto;
top:20px;
background:red;
}
.non-sticky {
display:inline-block;
background:blue;
}
<div class="wrapper"><!-- our scrolling box -->
<div class="parent"><!-- containing block -->
<div class="sticky">I am sticky</div>
<div class="non-sticky">I am the relative position</div>
</div>
</div>
最初我们的元素是隐藏的,这是合乎逻辑的,因为它不能超出其包含块(其限制)。一旦我们开始滚动,我们将看到我们的粘性元素和相对元素的行为完全相同。当粘性元素和滚动框的顶部边缘之间的距离为20px 时,我们达到阈值并开始使用position:fixed 直到我们再次达到底部包含块的限制(即我们不再为粘性行为留出空间)
现在让我们用底部替换顶部
body {
margin:0;
}
.wrapper {
width:300px;
height:150px;
border:2px solid red;
overflow:auto;
}
.parent {
height:200%;
margin:100% 0;
border:2px solid;
}
.sticky {
position:sticky;
display:inline-block;
margin:auto;
bottom:20px;
background:red;
}
.non-sticky {
display:inline-block;
background:blue;
}
<div class="wrapper"><!-- our scrolling box -->
<div class="parent"><!-- containing block -->
<div class="sticky">I am sticky</div>
<div class="non-sticky">I am the relative position</div>
</div>
</div>
什么都不会发生,因为当元素和滚动框底部边缘之间的距离为20px 时,粘性元素已经接触到包含块的顶部边缘并且它不能向外移动。
让我们在之前添加一个元素:
body {
margin:0;
}
.wrapper {
width:300px;
height:150px;
border:2px solid red;
overflow:auto;
}
.parent {
height:200%;
margin:100% 0;
border:2px solid;
}
.sticky {
position:sticky;
display:inline-block;
margin:auto;
bottom:20px;
background:red;
}
.non-sticky {
display:inline-block;
background:blue;
}
.elem {
height:50px;
width:100%;
background:green;
}
<div class="wrapper"><!-- our scrolling box -->
<div class="parent"><!-- containing block -->
<div class="elem">elemen before</div>
<div class="sticky">I am sticky</div>
<div class="non-sticky">I am the relative position</div>
</div>
</div>
现在我们创建了50px 的空间来实现粘性行为。让我们将顶部与底部相加:
body {
margin:0;
}
.wrapper {
width:300px;
height:150px;
border:2px solid red;
overflow:auto;
}
.parent {
height:200%;
margin:100% 0;
border:2px solid;
}
.sticky {
position:sticky;
display:inline-block;
margin:auto;
bottom:20px;
top:20px;
background:red;
}
.non-sticky {
display:inline-block;
background:blue;
}
.elem {
height:50px;
width:100%;
background:green;
}
<div class="wrapper"><!-- our scrolling box -->
<div class="parent"><!-- containing block -->
<div class="elem">elemen before</div>
<div class="sticky">I am sticky</div>
<div class="non-sticky">I am the relative position</div>
</div>
</div>
现在我们从顶部和底部都有了行为,逻辑可以恢复如下:
on_scroll_event() {
if( top_sticky!=auto && distance_top_sticky_top_scrolling_box <20px && distance_bottom_sticky_bottom_containing_block >0) {
position <- fixed
} else if(bottom_sticky!=auto && distance_bottom_sticky_bottom_scrolling_box <20px && distance_top_sticky_top_containing_block >0) {
position <- fixed
} else (same for left) {
position <- fixed
} else (same for right) {
position <- fixed
} else {
position <- relative
}
}