目标位置:当前处于“卡住”状态的粘性元素

问题描述 投票:0回答:6

位置:粘性现在适用于某些移动浏览器,因此您可以使菜单栏随页面滚动,但每当用户滚动经过它时,它就会粘在视口的顶部。

但是,如果您想在粘性菜单栏当前“粘住”时稍微重新设计其样式,该怎么办?例如,您可能希望该栏在随页面滚动时具有圆角,但是一旦它粘在视口的顶部,您就希望摆脱顶部圆角,并在下面添加一点阴影它。

是否有任何类型的伪选择器(例如

::stuck
)来定位当前具有
position: sticky
的元素?或者浏览器供应商是否有类似的计划?如果没有的话我该去哪里索取呢?

注意。 javascript 解决方案对此并不适用,因为在移动设备上,当用户释放手指时,通常只会收到一个

scroll
事件,因此 JS 无法知道通过滚动阈值的确切时刻。

css css-selectors css-position
6个回答
165
投票

目前没有针对当前“卡住”的元素提出选择器。定义 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 解决方案来解决这个问题。


77
投票
在某些情况下,如果情况允许粘附到根容器外部的一两个像素,而不是正确地齐平,则简单的

IntersectionObserver

 就可以解决问题。这样,当它刚好超出边缘时,观察者就会开火,我们就开始奔跑。

const observer = new IntersectionObserver( ([e]) => e.target.toggleAttribute('data-stuck', e.intersectionRatio < 1), {threshold: [1]} ); observer.observe(document.querySelector('nav'));
使用 

top: -2px

 将元素从容器中取出,然后通过 
stuck
 属性定位...

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

https://codepen.io/anon/pen/vqyQEK


24
投票
我想要一个纯 CSS 解决方案,允许设置“卡住”元素的样式,就好像存在

::stuck

 伪选择器一样(唉,2021 年仍然不存在)。

我创建了一个纯CSS hack,无需JS即可达到效果,符合我的需求。它的工作原理是拥有该元素的两个副本,一个是

sticky

,另一个不是(
unstuck
 一个),后一个副本会覆盖 
sticky
 元素,直到您滚动它为止。

演示:

https://codepen.io/TomAnthony/pen/qBqgErK

替代演示:

https://codepen.io/TomAnthony/pen/mdOvJYw(这个版本更符合我的需求,我希望粘性项目仅在“卡住”后才出现 - 这也意味着没有重复的内容。 )

HTML:

<div class="sticky"> <div class="unstuck"> <div> Box header. Italic when 'stuck'. </div> </div> <div class="stuck"> <div> Box header. Italic when 'stuck'. </div> </div> </div>
CSS:

.sticky { height: 20px; display: inline; background-color: pink; } .stuck { position: -webkit-sticky; position: sticky; top: 0; height: 20px; font-style: italic; } .unstuck { height: 0; overflow-y: visible; position: relative; z-index: 1; } .unstuck > div { position: absolute; width: 100%; height: 20px; background-color: inherit; }
    

8
投票

Google 开发者博客上有人声称已经找到了一种基于 JavaScript 的高性能解决方案,带有 IntersectionObserver

相关代码位在这里:

/** * 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 hacks 来设计样式(即 getBoudingClientRect、滚动监听、调整大小监听),但这就是我目前解决问题的方式。此解决方案对于具有可最小化/可最大化内容 (

)、嵌套滚动或任何曲线球的页面会出现问题。话虽如此,当问题很简单时,这也是一个简单的解决方案。

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; }); })
    

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'))
    

3
投票

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'))
    
© www.soinside.com 2019 - 2024. All rights reserved.