Согласно 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), содержащий блок липкого элемента и ближайшего предка с полем прокрутки.
- Ближайший предок с полем прокруткипросто ближайший предок с переполнением, отличным от visibile, и по умолчанию это будет окно просмотра (как я объяснил здесь: Что такое «скроллбоксы»? ).Прокрутка этого элемента будет управлять поведением закрепления.
- Содержащий блок для закрепленного элемента такой же, как и для относительного элемента 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
}
}