【问题标题】:Position sticky: scrollable, when longer than viewport位置粘性:可滚动,当长于视口时
【发布时间】:2017-12-03 12:18:53
【问题描述】:

当带有position: sticky 的元素“卡住”并且比视口长时,您只能在滚动到容器底部后才能看到其内容。

如果“卡住”元素随着文档滚动并在到达其底部边缘时停止,那将是很酷的。如果用户向后滚动,同样的事情会再次发生,但相反。

示例

TLDR;有一个库 (StickyKit) 可以满足我的需求,但在新的异步滚动中表现不佳。

JSFiddle with StickyKit - https://jsfiddle.net/cibulka/4nd3b0tt/ - (这符合我的描述,但表现不佳,见下文)

JSFiddle with native position: sticky - https://jsfiddle.net/cibulka/np6afe9c/1/ - https://jsfiddle.net/cibulka/pxz6e0qv/ - (这不是)

背景

长期以来,我一直是 StickyKit 的快乐用户。不幸的是,它不适用于asynchronous scrolling,越来越多的浏览器使用它来提高性能。例如,对于新的 Firefox Quantum (57),StickyKit 几乎无法使用。

我在StickyKit Github中创建了一个问题,但是包似乎被作者放弃了:https://github.com/leafo/sticky-kit/issues/252

因此,我不得不弃用 StickyKit 并迁移到原生 position:sticky(用 StickyFill 填充)。不幸的是,有几件事 position:sticky 不能做,这就是其中之一。

position:sticky 还有另一个问题:Position sticky: overlay

我在寻找什么

基本上是关于如何解决此问题的建议。我准备使用不同的 JS/jQuery 库,编写自己的代码或使用一些古怪的 CSS hack 来破解 position:sticky 功能。

【问题讨论】:

标签: javascript css sticky


【解决方案1】:

我已经接受了 jnns 的答案并对其进行了更新,以便它在滚动之间平滑,就像粘性套件可能已经过的那样。唯一的问题是它需要一个包含 div s.t. 的幻数。容器在 div 绝对定位时保持其大小 - 这可以通过 css 变量在您的代码中解决。

window.onscroll = function (e) {
  if (window.scrollY < this.prevScrollY) {
    // Track position state of nav
    // 1 == stuck to top
    // 0 == absolute positioning
    // -1 == stuck to bottom 
    this.stick_pos = scrollUpwards(this.stick_pos);
  } else {
    this.stick_pos = scrollDownwards(this.stick_pos);
  }
  this.prevScrollY = window.scrollY; 
}

function scrollUpwards(stick_pos) {
  // If the element is already stuck to the top then we are fine
  if(stick_pos === 1) return stick_pos;
  // Figure out where the new window will be after scroll
  let aside = $("aside").get(0);
  let aboveAside = aside.getBoundingClientRect().top > 0;
  // If we are going above the element then we know we must stick
  // it to the top
  if (aboveAside){
    $("aside").css("position", "sticky")
      .css("top", 0)
      .css("bottom", '')
      .css("align-self", "flex-start");
    return 1;
  }
  // If it will still be below the top of the element, then we
  // must absolutely position it to its current position - if it already is absolutely positioned then we do nothing
  if (stick_pos == 0) return stick_pos;
  
  // Undo the stick to the bottom
  // First get the current position
  $("aside")
    .css("top", aside.offsetTop)
    .css("position", "absolute")
    .css("bottom", '')
    .css("align-self", "");
  return 0;
}

function scrollDownwards(stick_pos) {
  /*
  let aside = $("aside").get(0);
  let aboveAside = aside.offsetTop >= window.scrollY;
  let browser_bottom = window.scrollY + window.innerHeight;
  let aside_bottom = aside.offsetTop + aside.offsetHeight;
  let belowAside = browser_bottom >= aside_bottom;
  if (aboveAside) {
    //console.log("stick to bottom");
    $("aside").css("top", ''); 
    $("aside").css("bottom", 0); 
    $("aside").css("align-self", "flex-end");
  }
  */
  // If the element is already stuck to the bottom then we are fine
  if(stick_pos === -1) return stick_pos;
  // Figure out where the new window will be after scroll
  let aside = $("aside").get(0);
  let browser_bottom = window.innerHeight;
  let aside_bottom = aside.getBoundingClientRect().top + aside.offsetHeight;
  let belowAside = browser_bottom > aside_bottom;
  // If we are going below the element then we know we must stick
  // it to the bottom.
  if (belowAside){
    $("aside").css("position", "sticky")
      .css("top", '')
      .css("bottom", 0)
      .css("align-self", "flex-end");
    return -1;
  }
  // If it will still be above the bottom of the element, then we
  // must absolutely position it to its current position - if it already is absolutely positioned then we do nothing
  if (stick_pos == 0) return stick_pos;
  
  // Undo the stick to the top
  // First get the current position
  // $("aside").css("position", "absolute")
  // .css("top", aside.offsetTop);
  $("aside")
    .css("top", aside.offsetTop)
    .css("position", "absolute")
    .css("bottom", '')
    .css("align-self", "");
  return 0;
}
div#section {
  /* begin: irrelevant styling */
  margin: 5em auto;
  padding: 0.625rem;
  max-width: 300px;
  font-family: sans-serif;
  font-size: 18px;
  line-height: 1.5em;
  text-align: justify;
  background-color: #dbe4ee;
  /* end: irrelevant styling */
  display: flex;
  justify-content: space-around;
}
div#section div#nav-container {
  position: relative;
  display: flex;
  min-width: 2em;
}
div#section div#nav-container aside {
  position: sticky;
  align-self: flex-start;
  /* begin: irrelevant styling */
  background-color: #81a4cd;
  color: white;
  text-align: center;
  width: 2em;
}
div#section div#nav-container aside div {
  padding: 0 .3em;
}
div#section article {
  margin-left: 0.5em;
}
div#section article p {
  margin: 0;
}
div#section article p + p {
  margin-top: 1.5em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id='section'>
  <div id='nav-container'>
  <aside>
    <div>A</div>
    <div>B</div>
    <div>C</div>
    <div>D</div>
    <div>E</div>
    <div>F</div>
    <div>G</div>
    <div>H</div>
    <div>I</div>
    <div>J</div>
    <div>K</div>
    <div>L</div>
    <div>M</div>
    <div>N</div>
    <div>O</div>
    <div>P</div>
    <div>Q</div>
    <div>R</div>
    <div>S</div>
    <div>T</div>
    <div>U</div>
    <div>V</div>
    <div>W</div>
    <div>X</div>
    <div>Y</div>
    <div>Z</div>
  </aside>
  </div>
  <article>
    <p>Perferendis ut iusto voluptatem ex temporibus aut autem amet. Sit vero in soluta. Est officia asperiores tenetur vel quam nostrum eum facere. Sed totam quasi libero at facilis doloremque. Non aut velit odio. Tempora dolore sunt recusandae sed quia
      sunt.</p>

    <p>Voluptatem optio asperiores dolorem voluptatem. Ipsa alias perspiciatis doloribus est nisi ut. Fuga aut et vitae consequatur dolor corrupti aut minima.</p>

    <p>Facilis et ut eligendi. Excepturi labore asperiores vero. Perferendis porro sunt molestiae. In sit dolorem eum esse sit inventore est. Atque perspiciatis commodi nihil.</p>

    <p>Consequatur ipsa id repellendus voluptatem perspiciatis temporibus. Praesentium eveniet nemo laudantium inventore similique impedit nihil esse. Maiores iste commodi molestiae quas odit nihil ex corrupti. Illum id amet non vero.</p>

    <p>Voluptas soluta itaque et. Aperiam quasi sint eos ullam. Assumenda facilis omnis alias numquam. Odio quia esse vel et minima soluta architecto. Qui saepe consequatur aut rerum. Et et aut voluptatibus inventore.</p>

    <p>Perferendis ut iusto voluptatem ex temporibus aut autem amet. Sit vero in soluta. Est officia asperiores tenetur vel quam nostrum eum facere. Sed totam quasi libero at facilis doloremque. Non aut velit odio. Tempora dolore sunt recusandae sed quia sunt.</p>

    <p>Voluptatem optio asperiores dolorem voluptatem. Ipsa alias perspiciatis doloribus est nisi ut. Fuga aut et vitae consequatur dolor corrupti aut minima.</p>

    <p>Facilis et ut eligendi. Excepturi labore asperiores vero. Perferendis porro sunt molestiae. In sit dolorem eum esse sit inventore est. Atque perspiciatis commodi nihil.</p>

    <p>Consequatur ipsa id repellendus voluptatem perspiciatis temporibus. Praesentium eveniet nemo laudantium inventore similique impedit nihil esse. Maiores iste commodi molestiae quas odit nihil ex corrupti. Illum id amet non vero.</p>

    <p>Voluptas soluta itaque et. Aperiam quasi sint eos ullam. Assumenda facilis omnis alias numquam. Odio quia esse vel et minima soluta architecto. Qui saepe consequatur aut rerum. Et et aut voluptatibus inventore.</p>
</div>

【讨论】:

    【解决方案2】:

    有一个名为 Sticky Sidebar 的库正是为解决该问题而设计的。

    【讨论】:

    • 谢谢,终于解决了我的问题... 1)我的侧边栏比视口高度更长,2)我需要我的侧边栏底部边缘在向下滚动时停止在视口底部,但滚动向上滚动页面时返回顶部边缘。 Sticky-kit 插件或位置:sticky css 对我不起作用。
    【解决方案3】:

    您可以尝试使用jQuery根据滚动方向从上到下切换粘性元素的锚点和位置,并且仍然使用CSS的原生position: sticky

    我让它在this codepen 中工作,但没有时间在方向改变时消除跳跃。但也许这对你来说已经足够了。

    // Use display: flex on the container
    
    window.onscroll = function (e) {
      if (window.scrollY < this.prevScrollY) {
        scrollUpwards();
      } else {
        scrollDownwards();
      }
      this.prevScrollY = window.scrollY; 
    }
    
    function scrollUpwards() {
      $("aside").css("top", 0); 
      $("aside").css("bottom", '');
      $("aside").css("align-self", "flex-start");
    
    }
    
    function scrollDownwards() {
      $("aside").css("top", ''); 
      $("aside").css("bottom", 0); 
      $("aside").css("align-self", "flex-end");
    }
    

    【讨论】:

      【解决方案4】:

      你看过Scrollama

      它使用新的Intersection Observer Web API,它基本上是浏览器在某个元素出现在视口中时告诉 JS,而 JS 不必监听滚动事件。因此,对于以高性能的方式在 JS 中实现类似position: sticky 的行为很有用。

      【讨论】:

      • 这似乎并没有解决我的问题,因为它似乎与“scrollytelling”用例有关 - 根据滚动位置切换 div,而不是滚动它们。不过谢谢!
      猜你喜欢
      • 2023-04-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-10
      • 2018-03-21
      • 2022-11-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多