位置:粘性 - 结合 javascript 高度调整时滚动弹跳

Posted

技术标签:

【中文标题】位置:粘性 - 结合 javascript 高度调整时滚动弹跳【英文标题】:Position: sticky - scroll bouncing when combined with javascript height adjustment 【发布时间】:2017-11-17 22:12:25 【问题描述】:

在玩了一段时间position: sticky 之后,我开始实现它以实现粘性导航并遇到了这个有趣但令人沮丧的滚动弹跳问题。

这是在许多网站上常见的导航行为类型,您通常会使用 javascript 来计算页面中相对元素的偏移量。当元素到达窗口顶部时,将添加一个“卡住”类,使用position: fixed将该元素从文档流中取出,并在其位置添加一个相同高度的虚拟元素以阻止页面从“跳跃”。此外,通常会看到 javascript 然后缩小导航的高度以在滚动时节省空间。

CSS 现在似乎用position: sticky 处理了所有这些,除了(据我所知)检测元素何时“卡住”。相反,我使用了一些 javascript 来进行卡住检测,发现一切正常,直到 粘性元素的高度需要更改

这很难解释,但它会在生产中造成严重破坏 - 所以这是我为尽可能简单地说明问题而制作的一个精简示例。

CSS sticky position height adjustment bug

当页面的高度恰到好处的长度时最好说明,所以我在元素上设置了一个固定的高度,以确保每个人都能看到相同的东西。您可以添加更多内容,但滚动过去时仍然存在问题。

结果是一些非常奇怪的行为。向下滚动时,导航卡住,随着导航栏缩小,浏览器自动创建的“虚拟元素”position: sticky 似乎与它保持同步。这意味着,当添加了卡住的类时,整个页面变小了,几分之一秒后,导航不再卡住,从而导致故障振动循环。

我测试过的每个浏览器的行为也完全不同。在 chrome 中,这种弹跳永远无法解决,它一直处于无限循环中,不断添加/删除卡住的类。更有趣的是,在 Safari 中,滚动位置被“推回”到不会出错的状态。然后在 Firefox 中,它会同时执行这两项操作,会出现一两秒钟的故障,然后再次强制滚动位置恢复。

我想知道是否有人遇到过这种情况并提出任何解决方案?我想出的任何 js 解决方法都没有真正奏效或非常好!当然,随着人气的增长,更多的人会喜欢这个......

欢迎天才的变通方法、技巧、见解或完美的解决方案!

【问题讨论】:

【参考方案1】:

在应用会改变其大小(并可能影响窗口大小/元素定位)的更改时,尝试将overflow-anchor: none; 添加到粘性元素。

更新:最终,我找到的正确解决方案是:拥有一个永远不会改变大小的外部元素(它在任何给定断点处始终保持相同的全高)。那个是粘的。但它也应该没有背景/视觉样式,并且它的有效高度应该由高度 + 底部边距定义(这样它就可以在文档中占用适量的初始空间,但实际上不会在视觉导航后阻止点击缩小并提供更多空间。

然后有一个内部元素会改变大小,无论是在现实中还是在视觉上。

您还可以使用现代属性,例如包含:布局大小;像

这样的内部元素

【讨论】:

这是解决我的滚动标题缩小问题的唯一解决方法! ty 不幸的是,这也不被 Safari 支持 是的。到目前为止,我找到的唯一可靠的解决方案是给粘性标题一个固定的高度,然后在不改变外部粘性元素的实际高度的情况下在视觉上调整它的内部部分。【参考方案2】:

(显然你需要更多的声誉来评论而不是回答......)

这似乎是一个合理的布局错误,所以我很好奇浏览器贡献者的意见可能是什么。在 Chromium 和 Firefox 错误跟踪器中提出问题,看看会发生什么:

https://bugs.chromium.org/p/chromium/issues/detail?id=734461 https://bugzilla.mozilla.org/show_bug.cgi?id=1374171

【讨论】:

哈哈。谢谢!打败我。【参考方案3】:

我可以在尝试同样的事情后确认这是一个问题。我在我的标题上使用位置粘性并同时通过 JS 添加一个类(触发一些改变高度的动画,如上面的 CodePen 描述的)

var header = document.getElementById("header");
var sticky = header.offsetTop;

window.onscroll = function () 
    if (window.scrollY > sticky) 
        header.classList.add("stuck");
     else 
        header.classList.remove("stuck");
    
;

高度变化实际上会影响窗口高度,当它变小 1px 时会触发 else 删除我的动画。删除动画会将高度更改回原始大小,然后循环重新开始。

我想知道如何在没有原生 stuck 元素/类/伪的情况下正确编码

【讨论】:

【参考方案4】:

I forked your pen.

这是我想出的一种解决方法,它在视觉上给出了相同的效果。

似乎将transform 而不是heightposition: sticky 一起转换效果很好。您不会切换常量类。

因此,如果我们想将导航的高度减半,可以通过将 scaleY 从 1 更改为 0.5 来将其压缩为一半

这反过来挤压我们的链接,因此我们将它们放大到原来的两倍以抵消挤压,将 scaleY 从 1 调整为 2。

我们要做的最后一个修复是将导航平移到页面顶部以补偿较小的高度。

片段如下。这里的关键部分如下:

nav 
  transform: scaleY(1) translateY(0);

nav a 
  transform: scaleY(1);

nav.stuck 
  transform: scaleY(0.5) translateY(-50%);

nav.stuck a 
  transform: scaleY(2);

nav, nav a 
  transition: all 0.6 ease-in-out;

前两条规则并不是绝对必要的,但我喜欢在前面和后面加入一个规则,以使事情更加清晰。

nav       = document.querySelector('nav');
section   = document.querySelector('section');

function supportSticky() 
  if(window.CSS && CSS.supports) 
    return CSS.supports("(position: sticky)") || CSS.supports("(position: -webkit-sticky)");
   else 
    var el = document.createElement("div");
    el.style.position = "sticky";
    return el.style.position == "sticky";
  


function handleScroll() 
  function isStuck(el) 
    return el.offsetTop - section.scrollTop <= 0 ? true : false;
  

  isStuck(nav) ? nav.classList.add("stuck") : nav.classList.remove("stuck");


if (supportSticky()) section.addEventListener('scroll', handleScroll);
html,
body,
h1 
  margin: 0;
  font-family: arial;


section 
  width: 100%;
  max-width: 600px;
  margin: 0px auto;
  box-shadow: 0 1px 7px #ccc;
  height: 378px;
  overflow-y: scroll;


header 
  padding: 3em;


nav 
  display: flex;
  width: 100%;
  background-color: #ddd;
  justify-content: center;
  padding: 3em;
  box-sizing: border-box;
  position: sticky;
  top: 0;
  transition: all .6s ease-in-out;
  transform: scaleY(1) translateY(0);

nav.stuck 
  background-color: red;
  transform: scaleY(0.5) translateY(-50%);

nav.stuck a 
  transform: scaleY(2);

nav a 
  text-decoration: none;
  color: #fff;
  padding: 1ch 1em;
  background-color: #bbb;
  margin-right: 1em;
  border-radius: 3px;
  transition: all .6s ease-in-out;

nav a:hover 
  background-color: #aaa;


article 
  padding: 3em;
<section>
  <header>
    <h1>CSS sticky position height adjustment bug</h1>
  </header>
  <nav>
    <a href="">Item 1</a>
    <a href="">Item 2</a>
    <a href="">Item 3</a>
    <a href="">Item 4</a>
  </nav>
  <article>
    <h1>Sticky navigation</h1>
    <p>The navigation above should shrink when it gets to the top.</p>
    
    <h1>There is no 'stuck feature' in CSS</h1>
    <p>So we need javascript to work that out, and set a stuck class.</p>
        
    <h1>But it bounces!</h1>
    <p>Because the dummy element is kept in sync with the nav height...</p>
  </article>
</section>

【讨论】:

很好,我没想到,谢谢。但是,在示例的简单上下文之外,这变得非常混乱。拥有任何具有复杂导航结构的控件将开始变得非常奇怪。我正在使用的导航具有非常特定的间距/填充的图块,在折叠时完全消失,并且相对绝对定位的伪元素中的文本需要在折叠状态下移动到确切的位置。我想您可以使用calc() 或其他东西来获得更多控制权。此外,如果您在此处使用transform-origin,则不再需要translateY 是的,我认为这是否有效取决于您的实际情况有多复杂。我是复杂的calc() 表达式的忠实粉丝,但这可能仍然无法拯救你。当然,它最终可能会比你通过一些 JavaScript 切换到 fixed 来完成更多的 CSS。但是在更简单的导航上,您可能可以使用nav.stuck * scaleY(2) 之类的东西。【参考方案5】:

这让我发疯了一阵子,但是(基于 This question/answer 的解决方案)一个好的解决方案是创建一个额外的外部元素,它是 从不改变大小的粘性元素,并且然后根据需要让内部元素在该容器内更改大小/位置。

我与观察者一起制作的一个示例小提琴,用于检测何时“卡住”(如果上面只有一个固定大小的元素,也可以使用滚动偏移):

https://jsfiddle.net/ccarnage/fveyc6nL/24/

总结是:

<div id="sticky-container" style="height:100px;">
    <div id="header-contents-shrinkable">
     ...
    </div>
</div>

#header-contents-shrinkable 将在粘性容器粘在页面顶部时更改其样式(例如,降低高度)

【讨论】:

以上是关于位置:粘性 - 结合 javascript 高度调整时滚动弹跳的主要内容,如果未能解决你的问题,请参考以下文章

不使用 CSS 位置的 Javascript / jQuery 粘性:已修复

使用“位置:粘性”将元素始终显示在屏幕上

具有不同内容高度的粘性页脚,无需页面刷新

粘性导航栏可变大小更改滚动位置

是否可以只滚动图像而不是整个站点而没有位置:粘性?

Bootstrap 4 - 粘性页脚 - 动态页脚高度