html - 使滚动粘性元素的优先级高于更改元素位置

标签 html css

假设我有一个侧边导航,我希望侧边导航是粘性的,这样它就可以随着用户滚动。然而,侧边导航的高度不是固定的:当用户在导航中打开带有子项的元素时,它会垂直扩展以显示子项。

一旦子菜单展开,或者我想在小屏幕上,菜单可能无法像以前那样适合一个屏幕。用户的解决方案是向下滚动页面以查看导航底部并选择他们选择的链接。然而,现在侧面菜单是粘性的,向下滚动时菜单会随着它们一起滚动。只有当用户到达内容底部时,菜单才会找到其容器的末尾并停留,以便用户可以访问菜单底部。

这是一个code pen我 fork 说明了这个问题。内容非常庞大,侧边导航仅略大于屏幕高度。

.sidebar {
  width: 25%;
  height: 120vh;
  min-height: 400px;
  overflow: auto;
  position: -webkit-sticky;
  position: sticky;
  top: 3em;
}

.main {
  width: 60%;
  height: 2000vh;
  min-height: 1000px;
  display: flex;
  flex-direction: column;
  position: relative;
}

.main,
.sidebar {
  border: 5px solid #222;
  background-color: white;
  border-radius: 10px;
  color: #222;
  padding: 15px;
}

.wrapper {
  display: flex;
  justify-content: space-between;
}

body {
  padding: 3%;
  background-color: #ccc;
  font-size: 20px;
  box-sizing: border-box;
  font-family: Lato, sans-serif;
}

code, pre {
  background-color: #ccc;
  padding: 0 3px;
  border-radius: 5px;
}

.bottom {
  position: absolute;
  bottom: 0;
}
<div class="wrapper">
  <div class="main">
    <h2>Main content</h2>
    <p>Scroll down the page!</p>
    <p class='bottom'>You made it!</p>
  </div>

  <div class="sidebar">
    <h3>Sticky sidebar</h3>
    <p>I will follow you! But should wait til you hit my bottom when scrolling down before following down. If you are scrolling up, I should wait for you to hit my top before following.</p>
  </div>
</div>

我能做什么?出于性能原因,我喜欢纯 CSS 版本。我不想限制导航的高度,因为这意味着绝大多数用户将需要重新考虑他们如何使用导航,直接在导航上滚动而不是像他们习惯的那样在内容上滚动。

我找到了一个名为 sticky kit 的 JS 库这创造了我正在寻找的确切效果,特别是可滚动的粘性元素示例。然而,它已经 2 年没有更新了,我不想依赖一个 jquery 插件,它可能不会随着 web 标准的变化而工作。

有一个中间选项,我使用 JS 更改滚动上的 CSS 粘滞方向 - 如 this pen .但是当你改变方向时会有一种可怕的颠簸。

最佳答案

因此,根据我的研究,目前这还不是粘性规范的一部分——您需要使用一些 JS。

但是如果改用JS来控制粘性定位,可以减少多少JS。我 fork 了我在问题末尾发布的那支 JS 笔,并对其进行了研究以找到一个非常简单的解决方案,它只需要很少的 JS,它试图避免在滚动事件监听器上进行过多的计算。

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;
  width: 2em; /* Magic number, must be same width as the aside nav */
  /* begin: irrelevant styling */
  background-color: #81a4cd;
  color: white;
  text-align: center;
}
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>

与 CSS 解决方案相比,它并不理想,但它非常活泼并且看起来很稳定。我非常期待改进,并将在 codepen 上进行清理.其中一个解决方案使用了魔数(Magic Number)(aside 容器和 aside 的宽度必须保持相同,因为 aside 将在临时滚动期间绝对定位)。我想这可以用一个 css 变量来解决。

关于html - 使滚动粘性元素的优先级高于更改元素位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54700149/

相关文章:

jquery - 使用带有光滑 slider 的CSS的三 Angular 形

html - CSS 技巧 *+html 有什么作用?

java - 从 HTML 代码中检索图像 URL

javascript - 用新构造的 DOM 结构替换小部件元素

css 页脚内容在 html5 中无法正确显示

css - 100% 宽度的 div 在 IE 中无法正确显示

html - Google Chrome 上的 CSS 边框问题

jquery - 如何使用 jQuery 在每个表格单元格中设置增量 CSS 类?

javascript - 从 li 的子节点检索文本

javascript - 尝试动态设置 MetaTag