jQuery 检测 position:sticky 何时触发的事件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16302483/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Event to detect when position:sticky is triggered
提问by AlecRust
I'm using the new position: sticky
(info) to create an iOS-like list of content.
我正在使用 new position: sticky
( info) 创建类似 iOS 的内容列表。
It's working well and far superior than the previous JavaScript alternative (example) however as far as I know no event is fired when it's triggered, which means I can't do anything when the bar hits the top of the page, unlike with the previous solution.
它运行良好,远优于之前的 JavaScript 替代方案(示例),但是据我所知,触发时没有触发任何事件,这意味着当条到达页面顶部时我无法执行任何操作,这与之前的不同解决方案。
I'd like to add a class (e.g. stuck
) when an element with position: sticky
hits the top of the page. Is there a way to listen for this with JavaScript? Usage of jQuery is fine.
我想stuck
在元素position: sticky
点击页面顶部时添加一个类(例如)。有没有办法用 JavaScript 来监听这个?jQuery 的使用很好。
A demo of the new position: sticky
in use can be found here.
position: sticky
可以在此处找到新使用的演示。
回答by vsync
Demowith IntersectionObserver(use a trick):
使用IntersectionObserver 进行演示(使用技巧):
// get the sticky element
const stickyElm = document.querySelector('header')
const observer = new IntersectionObserver(
([e]) => e.target.classList.toggle('isSticky', e.intersectionRatio < 1),
{threshold: [1]}
);
observer.observe(stickyElm)
body{ height: 200vh; font:20px Arial; }
section{
background: lightblue;
padding: 2em 1em;
}
header{
position: sticky;
top: -1px; /* ? the trick */
padding: 1em;
padding-top: calc(1em + 1px); /* ? compensate for the trick */
background: salmon;
transition: .1s;
}
/* styles for when the header is in sticky mode */
header.isSticky{
font-size: .8em;
opacity: .5;
}
<section>Space</section>
<header>Sticky Header</header>
The top
value needs to be -1px
or the element will never intersect with the top of the browser window (thus never triggering the intersection observer).
该top
值必须是,-1px
否则元素将永远不会与浏览器窗口的顶部相交(因此永远不会触发相交观察者)。
To counter this 1px
of hidden content, an additional 1px
of space should be added to either the border or the padding of the sticky element.
为了解决1px
隐藏内容的问题,1px
应该在粘性元素的边框或填充中添加额外的空间。
Demo with old-fashioned scroll
event listener:
带有老式scroll
事件侦听器的演示:
- auto-detecting first scrollable parent
- Throttling the scroll event
- Functional composition for concerns-separation
- Event callback caching:
scrollCallback
(to be able to unbind if needed)
- 自动检测第一个可滚动的父级
- 限制滚动事件
- 关注点分离的功能组合
- 事件回调缓存:(
scrollCallback
如果需要可以解除绑定)
// get the sticky element
const stickyElm = document.querySelector('header');
// get the first parent element which is scrollable
const stickyElmScrollableParent = getScrollParent(stickyElm);
// save the original offsetTop. when this changes, it means stickiness has begun.
stickyElm._originalOffsetTop = stickyElm.offsetTop;
// compare previous scrollTop to current one
const detectStickiness = (elm, cb) => () => cb & cb(elm.offsetTop != elm._originalOffsetTop)
// Act if sticky or not
const onSticky = isSticky => {
console.clear()
console.log(isSticky)
stickyElm.classList.toggle('isSticky', isSticky)
}
// bind a scroll event listener on the scrollable parent (whatever it is)
// in this exmaple I am throttling the "scroll" event for performance reasons.
// I also use functional composition to diffrentiate between the detection function and
// the function which acts uppon the detected information (stickiness)
const scrollCallback = throttle(detectStickiness(stickyElm, onSticky), 100)
stickyElmScrollableParent.addEventListener('scroll', scrollCallback)
// OPTIONAL CODE BELOW ///////////////////
// find-first-scrollable-parent
// Credit: https://stackoverflow.com/a/42543908/104380
function getScrollParent(element, includeHidden) {
var style = getComputedStyle(element),
excludeStaticParent = style.position === "absolute",
overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;
if (style.position !== "fixed")
for (var parent = element; (parent = parent.parentElement); ){
style = getComputedStyle(parent);
if (excludeStaticParent && style.position === "static")
continue;
if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX))
return parent;
}
return window
}
// Throttle
// Credit: https://jsfiddle.net/jonathansampson/m7G64
function throttle (callback, limit) {
var wait = false; // Initially, we're not waiting
return function () { // We return a throttled function
if (!wait) { // If we're not waiting
callback.call(); // Execute users function
wait = true; // Prevent future invocations
setTimeout(function () { // After a period of time
wait = false; // And allow future invocations
}, limit);
}
}
}
header{
position: sticky;
top: 0;
/* not important styles */
background: salmon;
padding: 1em;
transition: .1s;
}
header.isSticky{
/* styles for when the header is in sticky mode */
font-size: .8em;
opacity: .5;
}
/* not important styles*/
body{ height: 200vh; font:20px Arial; }
section{
background: lightblue;
padding: 2em 1em;
}
<section>Space</section>
<header>Sticky Header</header>
Here's a React component demowhich uses the first technique
这是使用第一种技术的React 组件演示
回答by Scott Leonard
If anyone gets here via Google one of their own engineers has a solution using IntersectionObserver, custom events, and sentinels:
如果有人通过 Google 到达这里,他们自己的工程师之一会使用 IntersectionObserver、自定义事件和哨兵提供解决方案:
https://developers.google.com/web/updates/2017/09/sticky-headers
https://developers.google.com/web/updates/2017/09/sticky-headers
回答by Will Koehler
There is currently no native solution. See Targeting position:sticky elements that are currently in a 'stuck' state. However I have a CoffeeScript solution that works with both native position: sticky
and with polyfills that implement the sticky behavior.
目前没有本地解决方案。请参阅定位位置:当前处于“卡住”状态的粘性元素。但是,我有一个 CoffeeScript 解决方案,它适用于本机position: sticky
和实现粘性行为的 polyfill。
Add 'sticky' class to elements you want to be sticky:
将 'sticky' 类添加到要粘贴的元素中:
.sticky {
position: -webkit-sticky;
position: -moz-sticky;
position: -ms-sticky;
position: -o-sticky;
position: sticky;
top: 0px;
z-index: 1;
}
CoffeeScript to monitor 'sticky' element positions and add the 'stuck' class when they are in the 'sticky' state:
CoffeeScript 监视“粘性”元素位置并在它们处于“粘性”状态时添加“卡住”类:
$ -> new StickyMonitor
class StickyMonitor
SCROLL_ACTION_DELAY: 50
constructor: ->
$(window).scroll @scroll_handler if $('.sticky').length > 0
scroll_handler: =>
@scroll_timer ||= setTimeout(@scroll_handler_throttled, @SCROLL_ACTION_DELAY)
scroll_handler_throttled: =>
@scroll_timer = null
@toggle_stuck_state_for_sticky_elements()
toggle_stuck_state_for_sticky_elements: =>
$('.sticky').each ->
$(this).toggleClass('stuck', this.getBoundingClientRect().top - parseInt($(this).css('top')) <= 1)
NOTE: This code only works for vertical sticky position.
注意:此代码仅适用于垂直粘性位置。
回答by mattrick
I found a solution somewhat similar to @vsync's answer, but it doesn't require the "hack" that you need to add to your stylesheets. You can simply change the boundaries of the IntersectionObserver to avoid needing to move the element itself outside of the viewport:
我找到了一个类似于@vsync 的答案的解决方案,但它不需要您需要添加到样式表中的“hack”。您可以简单地更改 IntersectionObserver 的边界以避免需要将元素本身移动到视口之外:
const observer = new IntersectionObserver(callback, {
rootMargin: '-1px 0px 0px 0px',
threshold: [1],
});
observer.observe(element);
回答by Turadg
After Chrome added position: sticky
, it was found to be not ready enoughand relegated to to --enable-experimental-webkit-features flag. Paul Irish said in February"feature is in a weird limbo state atm".
添加 Chrome 后position: sticky
,发现它没有准备好并降级为 --enable-experimental-webkit-features 标志。保罗爱尔兰在 2 月份表示“功能处于一个奇怪的边缘状态 atm”。
I was using the polyfilluntil it become too much of a headache. It works nicely when it does, but there are corner cases, like CORS problems, and it slows page loads by doing XHR requests for all your CSS links and reparsing them for the "position: sticky" declaration that the browser ignored.
我一直在使用polyfill,直到它变得非常令人头疼。当它运行时它工作得很好,但是有一些极端情况,比如 CORS 问题,它通过对所有 CSS 链接执行 XHR 请求并为浏览器忽略的“位置:粘性”声明重新解析它们来减慢页面加载速度。
Now I'm using ScrollToFixed, which I like better than StickyJSbecause it doesn't mess up my layout with a wrapper.
现在我正在使用ScrollToFixed,我比StickyJS更喜欢它,因为它不会用包装器弄乱我的布局。
回答by Daniel Tonon
I came up with this solution that works like a charm and is pretty small. :)
我想出了这个解决方案,它就像一个魅力,而且非常小。:)
No extra elements needed.
不需要额外的元素。
It does run on the window scroll event though which is a small downside.
它确实在窗口滚动事件上运行,尽管这是一个小缺点。
apply_stickies()
window.addEventListener('scroll', function() {
apply_stickies()
})
function apply_stickies() {
var _$stickies = [].slice.call(document.querySelectorAll('.sticky'))
_$stickies.forEach(function(_$sticky) {
if (CSS.supports && CSS.supports('position', 'sticky')) {
apply_sticky_class(_$sticky)
}
})
}
function apply_sticky_class(_$sticky) {
var currentOffset = _$sticky.getBoundingClientRect().top
var stickyOffset = parseInt(getComputedStyle(_$sticky).top.replace('px', ''))
var isStuck = currentOffset <= stickyOffset
_$sticky.classList.toggle('js-is-sticky', isStuck)
}
Note: This solution doesn't take elements that have bottom stickiness into account. This only works for things like a sticky header. It can probably be adapted to take bottom stickiness into account though.
注意:此解决方案不考虑具有底部粘性的元素。这仅适用于粘性标题之类的东西。不过,它可能可以适应以考虑底部粘性。
回答by Jassim Abdul Latheef
Just use vanilla JS for it. You can use throttle function from lodash to prevent some performance issues as well.
只需使用 vanilla JS 即可。您也可以使用 lodash 的油门功能来防止一些性能问题。
const element = document.getElementById("element-id");
document.addEventListener(
"scroll",
_.throttle(e => {
element.classList.toggle(
"is-sticky",
element.offsetTop <= window.scrollY
);
}, 500)
);
回答by Davey
I know it has been some time since the question was asked, but I found a good solution to this. The plugin stickybitsuses position: sticky
where supported, and applies a class to the element when it is 'stuck'. I've used it recently with good results, and, at time of writing, it is active development (which is a plus for me) :)
我知道这个问题已经有一段时间了,但我找到了一个很好的解决方案。插件stickybitsposition: sticky
在支持的地方使用,并在元素“卡住”时将类应用于元素。我最近使用它取得了良好的效果,并且在撰写本文时,它正在积极开发(这对我来说是一个加分项):)