【问题标题】:If you specify `bottom: 0` for position: sticky, why is it doing something different from the specs?如果您为 position:sticky 指定 `bottom: 0`,为什么它会做与规范不同的事情?
【发布时间】:2019-09-21 18:08:04
【问题描述】:

这是我在阅读 MDN position property 上的一篇文章时提出的问题。我认为那里描述的sticky 的行为与实际行为之间存在明显差异。


根据MDN,固定位置元素被视为相对位置元素,直到超过指定阈值,当超过阈值时,它们被视为固定位置元素,直到到达父元素的边界(@987654322 @)。

粘性定位可以被认为是相对定位和固定定位的混合体。粘性定位的元素被视为相对定位,直到它超过指定的阈值,此时它被视为固定,直到它到达其父元素的边界。比如……

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

...将相对定位 id 为 1 的元素,直到滚动视口,使元素距离顶部小于 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 article,这段代码“是一个相对放置元素,直到该元素的位置通过滚动视口距离视口底部小于0px,大于距离底部 0px”我在想。

但是结果是“滚动到固定位置的元素,直到该元素的位置通过滚动视口从视口的下端变得小于0px,大于0px时成为相对排列的元素”的动作从低端”。


为什么指定 bottom:0 会导致与 MDN 中显示的行为相反的行为?

指定top: 0时,当元素没有到达视口的bottom: 0时应用相对位置,到达时应用固定位置。当指定bottom: 0 时,情况正好相反。当元素没有到达视口的bottom: 0时应用相对位置,到达时应用固定位置

我读了CSS3,但它的机制很难读

【问题讨论】:

    标签: css css-position sticky


    【解决方案1】:

    根据 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. 带有滚动框的最近的祖先只是最近的溢出不同于可见的祖先,默认情况下它将是视口(正如我在这里解释的那样:What are `scrolling boxes`?)。此元素上的滚动将控制粘性行为。
    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
         }
    }
    

    【讨论】:

    • 混合代码在 Firefox 中不起作用。但它在 Chrome 中工作。另外,如何确定要与阈值(topbottom 值)进行比较的值?在我看来,top: 0bottom: 0 之间用于比较的阈值不同。
    • @dampymq 我正在添加更多细节(两者相同)
    • 我没有注意到。谢谢。
    • @dampymq 检查更新(很难用图说明,所以我尽量解释).. 混合代码在 firefox 上也应该可以正常工作
    • @DanielW。我看到您指的是 MDN,但是 MDN 这并不是一个真正的问题,因为我认为规范 (drafts.csswg.org/css-position-3/#sticky-pos) 在这种情况下,规范不是很清楚,并且在某些部分有点令人困惑,这在某种程度上是合乎逻辑的这是草稿
    【解决方案2】:

    specs 很难理解,所以我尝试根据MDN 来解释它们。首先是一些定义:

    • sticky 元素 – 带有position: sticky 的元素
    • 包含块 - 粘性元素的父级
    • 流根 - 让我们说它意味着视口

    具有 position: sticky; top: 100px; 的粘性元素定位如下:

    • 按正常流程定位
    • 并且它的上边缘将与流根的上边缘保持至少 100px 的距离
    • 并且它的底边不能低于包含块的底边

    下面的例子展示了这些规则是如何运作的:

    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; 的粘性元素的定位如下:

    • 按正常流程定位
    • 并且它的底边会与流根底边保持至少100px的距离
    • 并且它的上边缘不能超过包含块的上边缘

    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>

    我希望这是足够简单的解释。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多