定位位置:当前处于“卡滞”状态的粘性元素


109

位置:sticky现在可在某些移动浏览器上使用,因此您可以使菜单栏随页面滚动,但是每当用户滚动通过时,便会停留在视口顶部。

但是,如果您想在当前“粘滞”时稍微改变粘滞菜单栏的样式,该怎么办?例如,您可能希望栏在页面上滚动时始终带有圆角,但是一旦它粘在视口的顶部,您就想摆脱顶部的圆角,并在其下方添加一个小阴影它。

是否有任何类型的伪选择器(例如::stuck)针对具有position: sticky 当前粘附的元素?还是浏览器供应商正在准备中类似的东西?如果没有,我将在哪里提出要求?

注意 javascript解决方案对此不利,因为在移动设备上scroll,用户释放手指时通常只会得到一个事件,因此JS无法知道滚动阈值通过的确切时间。

Answers:


103

当前没有针对当前“卡住”的元素提议选择器。所述Postioned布局模块,其中position: sticky被定义不任一提及任何此类选择器。

可以将CSS的功能请求发布到www样式的邮件列表中。我相信:stuck伪类比::stuck伪元素更有意义,因为您希望在元素处于该状态时以它们本身为目标。实际上,前一段时间:stuck已经讨论一个伪类。人们发现,最主要的复杂性就是困扰任何试图根据渲染或计算样式进行匹配的拟议选择器:循环依赖。

对于:stuck伪类,使用以下CSS会发生最简单的循环:

:stuck { position: static; /* Or anything other than sticky/fixed */ }
:not(:stuck) { position: sticky; /* Or fixed */ }

而且可能还有更多难以解决的极端情况。

尽管人们普遍认为选择器可以根据某些布局状态进行匹配是不错的选择,但不幸的是,存在一些主要限制,使得这些实现起来很不容易。我不会很快就为这个问题寻求纯CSS解决方案。


14
真可惜 我也在寻找解决这个问题的方法。简单地引入一条规则,说选择器的position属性:stuck应该被忽略,这不是很容易吗?(我的意思是浏览器供应商的规则,类似于关于left优先级如何超过right等的规则)
powerbuoy

5
不只是位置...想象一个:stucktop值从更改0300px,然后向下滚动150px...是否应该粘贴?或考虑与position: sticky,并bottom: 0:stuck可能发生变化font-size,因此元件尺寸(因此改变,它应该坚持的时刻)...
罗马

3
请参阅github.com/w3c/csswg-drafts/issues/1660,其中建议是让JS事件知道什么时候卡住/松开。那应该不会有伪选择器引入的问题。
鲁宾

27
我相信,许多已经存在的伪类都可能产生相同的循环问题(例如:hover改变宽度,而:not(:hover)再改变一次)。我很喜欢:stuck伪类,并认为开发人员应该对自己的代码中不存在循环问题负责。
MarekLisý17年

12
好吧...我不太明白这是个错误-就像说while循环设计得很糟糕,因为它允许无休止的循环:)但是,感谢您清理这个循环;)
MarekLisý

25

在某些情况下IntersectionObserver,如果情况允许在其根容器之外粘贴一个或两个像素,而不是适当冲洗,则可以轻松解决问题。这样,当它正好位于边缘之外时,观察者就会开火,我们可以出发并奔跑了。

const observer = new IntersectionObserver( 
  ([e]) => e.target.toggleAttribute('stuck', e.intersectionRatio < 1),
  {threshold: [1]}
);

observer.observe(document.querySelector('nav'));

使用将该元素刚从其容器中伸出top: -2px,然后通过stuck属性进行定位...

nav {
  background: magenta;
  height: 80px;
  position: sticky;
  top: -2px;
}
nav[stuck] {
  box-shadow: 0 0 16px black;
}

这里的例子: https //codepen.io/anon/pen/vqyQEK


1
我认为,stuck类比自定义属性要好...您是否有选择的特定原因?
collimarco

一个类也可以正常工作,但由于它是派生的属性,因此它的级别似乎比该类高一点。属性对我来说似乎更合适,但无论哪种方式,这都取决于品味。
机架式

由于标头已经固定,我的顶部必须为60px,所以我无法使您的示例正常工作
FooBar

1
尝试将顶部填充添加到被卡住的任何东西上,也许padding-top: 60px在您的情况下:)
Tim Willis

5

Google Developers博客上的某人声称找到了具有IntersectionObserver的基于JavaScript的高性能解决方案。

相关代码位在这里:

/**
 * Sets up an intersection observer to notify when elements with the class
 * `.sticky_sentinel--top` become visible/invisible at the top of the container.
 * @param {!Element} container
 */
function observeHeaders(container) {
  const observer = new IntersectionObserver((records, observer) => {
    for (const record of records) {
      const targetInfo = record.boundingClientRect;
      const stickyTarget = record.target.parentElement.querySelector('.sticky');
      const rootBoundsInfo = record.rootBounds;

      // Started sticking.
      if (targetInfo.bottom < rootBoundsInfo.top) {
        fireEvent(true, stickyTarget);
      }

      // Stopped sticking.
      if (targetInfo.bottom >= rootBoundsInfo.top &&
          targetInfo.bottom < rootBoundsInfo.bottom) {
       fireEvent(false, stickyTarget);
      }
    }
  }, {threshold: [0], root: container});

  // Add the top sentinels to each section and attach an observer.
  const sentinels = addSentinels(container, 'sticky_sentinel--top');
  sentinels.forEach(el => observer.observe(el));
}

我没有亲自复制过它,但是也许它可以帮助某个人在这个问题上绊脚石。


3

并不是真的喜欢使用js hack来设计样式(例如getBoudingClientRect,滚动侦听,调整大小侦听)以进行样式设置,但是我目前正在解决这个问题。此解决方案将对内容最小化/最大化(<details>),嵌套滚动或实际上任何曲线球的页面产生问题。话虽如此,这也是问题简单的简单解决方案。

let lowestKnownOffset: number = -1;
window.addEventListener("resize", () => lowestKnownOffset = -1);

const $Title = document.getElementById("Title");
let requestedFrame: number;
window.addEventListener("scroll", (event) => {
    if (requestedFrame) { return; }
    requestedFrame = requestAnimationFrame(() => {
        // if it's sticky to top, the offset will bottom out at its natural page offset
        if (lowestKnownOffset === -1) { lowestKnownOffset = $Title.offsetTop; }
        lowestKnownOffset = Math.min(lowestKnownOffset, $Title.offsetTop);
        // this condition assumes that $Title is the only sticky element and it sticks at top: 0px
        // if there are multiple elements, this can be updated to choose whichever one it furthest down on the page as the sticky one
        if (window.scrollY >= lowestKnownOffset) {
            $Title.classList.add("--stuck");
        } else {
            $Title.classList.remove("--stuck");
        }
        requestedFrame = undefined;
    });
})

请注意,滚动事件侦听器在主线程上执行,这使其成为性能杀手。请改用Intersection Observer API。
怀疑的儒勒

if (requestedFrame) { return; }由于动画帧批处理,它不是“性能杀手”。但是,“路口观察员”仍然是一个改进。
Seph Reed

0

当元素上方有position:sticky元素时的一种紧凑方式。它设置了stuck您可以在CSS中匹配的属性header[stuck]

HTML:

<img id="logo" ...>
<div>
  <header style="position: sticky">
    ...
  </header>
  ...
</div>

JS:

if (typeof IntersectionObserver !== 'function') {
  // sorry, IE https://caniuse.com/#feat=intersectionobserver
  return
}

new IntersectionObserver(
  function (entries, observer) {
    for (var _i = 0; _i < entries.length; _i++) {
      var stickyHeader = entries[_i].target.nextSibling
      stickyHeader.toggleAttribute('stuck', !entries[_i].isIntersecting)
    }
  },
  {}
).observe(document.getElementById('logo'))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.