Если вы указываете `bottom: 0` для position: sticky, почему он делает что-то отличное от спецификации? - PullRequest
6 голосов
/ 03 мая 2019

Это вопрос, когда я читаю статью о свойстве MDN position . Я думал, что есть четкое различие между поведением sticky, описанным там, и фактическим поведением.


Согласно MDN элементы с фиксированной позицией обрабатываются как элементы относительной позиции, пока не будет превышен указанный порог, а при превышении порога они обрабатываются как элементы с фиксированной позицией, пока не будет достигнута граница родительского элемента ( Link ).

Липкое позиционирование можно рассматривать как гибрид относительного и фиксированного позиционирования. Элемент с липким расположением обрабатывается как относительно позиционированный до тех пор, пока он не пересекает заданный порог, после чего он обрабатывается как фиксированный, пока не достигнет границы своего родителя. Например ...

#one { position: sticky; top: 10px; } 

... будет располагать элемент с идентификатором один относительно, пока область просмотра не будет прокручена так, чтобы элемент находился на расстоянии менее 10 пикселей от вершины. За пределами этого порога элемент будет зафиксирован на расстоянии 10 пикселей от вершины.

Итак, я создал следующий код и подтвердил операцию.

body {
  margin: 0;
}

.container {
  display: flex;
  flex-direction: column;
}

.container>* {
  width: 100%;
}

header {
  background: #ffa;
  height: 130vh;
}

main {
  background: #faf;
  height: 210vh;
}

footer {
  background: #faa;
  height: 8vh;
  position: sticky;
  bottom: 0;
}

.footer {
  background: #aff;
  height: 100vh;
}
<div class="container">
  <header>HEADER</header>
  <main>MAIN CONTENT</main>
  <footer>FOOTER</footer>
  <div class="footer"></div>
</div>

Согласно статье MDN этот код "является относительным элементом размещения до тех пор, пока позиция элемента не станет меньше 0px от нижней части области просмотра путем прокрутки области просмотра и не станет элементом фиксированного размещения когда это больше, чем 0px снизу ", я думал.

Тем не менее, результатом является действие «Прокрутите до элемента с фиксированной позицией, пока позиция элемента не станет меньше 0px от нижнего конца области просмотра путем прокрутки области просмотра, и станет относительным упорядоченным элементом, когда больше 0px с нижнего конца ".


Почему указание bottom:0 приводит к противоположному поведению, показанному в MDN?

Если указано top: 0, относительная позиция применяется, когда элемент не достигает bottom: 0 области просмотра, и когда он достигает, применяется фиксированная позиция. Если указано bottom: 0, верно обратное. Относительная позиция применяется, когда элемент не достигает bottom: 0 области просмотра, фиксированная позиция применяется, когда он достигается

Я прочитал CSS3 , но его механизм был труден для чтения

Ответы [ 2 ]

3 голосов
/ 03 мая 2019

Согласно MDN, элементы с фиксированной позицией обрабатываются как элементы относительной позиции, пока не будет превышен указанный порог

Здесь все зависит от языка, поскольку приведенное выше предложение не означаетэлемент обязательно начнет position:relative , затем станет фиксированным.Он говорит , пока указанный порог не будет превышен.Так что, если изначально у нас превышен указанный порог?Это на самом деле случай вашего примера.

Другими словами, position:sticky имеет два состояния.

  1. Он рассматривается как относительный
  2. Он обрабатывается как фиксированный, когдапревышен указанный порог

Какой из них будет первым, будет зависеть от вашей структуры 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), содержащий блок липкого элемента и ближайшего предка с полем прокрутки.

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

Спецификации трудно понять, поэтому я попытаюсь объяснить их на основе MDN . Сначала несколько определений:

  • липкий элемент - элемент с position: sticky
  • содержащий блок - родительский элемент липкого элемента
  • flow root - давайте просто скажем, что это означает viewport

Липкий элемент, имеющий position: sticky; top: 100px;, позиционируется следующим образом:

  • Позиционируется в соответствии с нормальным потоком
  • И его верхний край будет выдерживать расстояние не менее 100 пикселей от верхнего края корня потока
  • И его нижний край не может быть ниже нижнего края содержащего блока

В следующем примере показано, как работают эти правила:

body { font: medium sans-serif; text-align: center; }
body::after { content: ""; position: fixed; top: 100px; left: 0; right: 0; border: 1px solid #F00; }
header, footer { height: 75vh; background-color: #EEE; }
.containing-block { border-bottom: 2px solid #FA0; background: #DEF; }
.containing-block::after { content: ""; display: block; height: 100vh; }
.before-sticky { border-bottom: 2px solid #080; padding-top: 50px; }
.after-sticky { border-top: 2px solid #080; padding-bottom: 50px; }
.sticky { position: sticky; top: 100px; padding-top: 20px; padding-bottom: 20px; background-color: #CCC; }
<header>header</header>
<div class="containing-block">
  <div class="before-sticky">content before sticky</div>
  <div class="sticky">top sticky</div>
  <div class="after-sticky">content after sticky</div>
</div>
<footer>footer</footer>

Аналогично, липкий элемент, имеющий position: sticky; bottom: 100px;, позиционируется следующим образом:

  • Позиционируется в соответствии с нормальным потоком
  • И его нижний край будет выдерживать расстояние не менее 100 пикселей от нижнего края корня потока
  • И его верхний край не может идти выше верхнего края содержащего блока

body { font: medium sans-serif; text-align: center; }
body::after { content: ""; position: fixed; bottom: 100px; left: 0; right: 0; border: 1px solid #F00; }
header, footer { height: 75vh; background-color: #EEE; }
.containing-block { border-top: 2px solid #FA0; background: #DEF; }
.containing-block::before { content: ""; display: block; height: 100vh; }
.before-sticky { border-bottom: 2px solid #080; padding-top: 50px; }
.after-sticky { border-top: 2px solid #080; padding-bottom: 50px; }
.sticky { position: sticky; bottom: 100px; padding-top: 20px; padding-bottom: 20px; background-color: #CCC; }
<header>header</header>
<div class="containing-block">
  <div class="before-sticky">content before sticky</div>
  <div class="sticky">bottom sticky</div>
  <div class="after-sticky">content after sticky</div>
</div>
<footer>footer</footer>

Надеюсь, это достаточно простое объяснение.

...