当position:sticky被触发时的事件

时间:2013-04-30 14:34:36

标签: javascript jquery css html5 position

我正在使用新的position: stickyinfo)来创建类似iOS的内容列表。

它运行良好,远远优于之前的JavaScript备选方案(example),但据我所知,触发事件时不会触发任何事件,这意味着当条形图触及顶部时,我无法执行任何操作该页面与之前的解决方案不同。

stuck的元素点击页面顶部时,我想添加一个类(例如position: sticky)。有没有办法用JavaScript来听这个?使用jQuery很好。

可以找到正在使用的新position: sticky的演示here

10 个答案:

答案 0 :(得分:13)

我找到了一种与@vsync的答案有点类似的解决方案,但是它不需要添加到样式表中的“ hack”。您只需更改IntersectionObserver的边界即可避免将元素本身移到视口之外:

const observer = new IntersectionObserver(callback, {
  rootMargin: '-1px 0px 0px 0px',
  threshold: [1],
});

observer.observe(element);

答案 1 :(得分:11)

DemoIntersectionObserver(技巧):

// 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>

top的值必须为-1px,否则元素将永远与浏览器窗口的顶部不相交(因此也不会触发相交观察器)。

要对付1px的隐藏内容,应在粘性元素的边框或填充处添加额外的1px空间。

具有老式scroll事件监听器的演示:

  1. auto-detecting first scrollable parent
  2. Throttling the scroll event
  3. 关注点分离的功能组成
  4. 事件回调缓存: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>


这是使用第一种技术的React component demo

答案 2 :(得分:10)

如果有人通过Google来到这里,他们自己的工程师之一就可以使用IntersectionObserver,自定义事件和哨兵来解决问题:

https://developers.google.com/web/updates/2017/09/sticky-headers

答案 3 :(得分:6)

目前没有原生解决方案。见Targeting position:sticky elements that are currently in a 'stuck' state。但是我有一个CoffeeScript解决方案,它既适用于本地position: sticky,也适用于实现粘性行为的polyfill。

添加&#39;粘性&#39;要粘贴的元素的类:

.sticky {
  position: -webkit-sticky;
  position: -moz-sticky;
  position: -ms-sticky;
  position: -o-sticky;
  position: sticky;
  top: 0px;
  z-index: 1;
}

CoffeeScript监控&#39;粘贴&#39;元素位置并添加“卡住”元素当他们在“粘性”课堂上课时。状态:

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

注意:此代码仅适用于垂直粘性位置。

答案 4 :(得分:3)

Chrome添加position: sticky后,发现其为not ready enoughrelegated to to --enable-experimental-webkit-features flag。保罗爱尔兰said in February“特征处于一种奇怪的状态”。

我正在使用the polyfill,直到它变得太令人头疼。它可以很好地工作,但是有一些极端情况,比如CORS问题,它通过对所有CSS链接执行XHR请求并将它们重新分析为浏览器忽略的“position:sticky”声明来减慢页面加载。

现在我正在使用ScrollToFixed,我比StickyJS更喜欢它,因为它不会使用包装器弄乱我的布局。

答案 5 :(得分:2)

我想出了一种像魅力一样的解决方案,它很小。 :)

不需要额外的元素。

它确实在窗口滚动事件上运行,但这是一个小的缺点。

var _$stickies = [].slice.call(document.querySelectorAll('.sticky'));

_$stickies.forEach(function(_$sticky){
    if (CSS.supports && CSS.supports('position', 'sticky')) {
        apply_sticky_class(_$sticky);

        window.addEventListener('scroll', function(){
            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;

    if (isStuck) {
        _$sticky.classList.add('js-is-sticky');
    } else {
        _$sticky.classList.remove('js-is-sticky');
    }
}

注意:此解决方案不考虑具有底部粘性的元素。这仅适用于粘性标头之类的东西。也许可以进行调整以考虑底部粘性。

答案 6 :(得分:1)

我知道问题已经有一段时间了,但我找到了一个很好的解决方案。插件stickybits在支持的位置使用position: sticky,并在元素被卡住时将类应用于该元素。我最近使用它并取得了良好的效果,在撰写本文时,它是积极的开发(这对我来说是一个加分):)

答案 7 :(得分:1)

只需使用香草JS。您也可以使用lodash的节流功能来防止某些性能问题。

arr[M][N]

答案 8 :(得分:0)

这样的东西也适用于固定滚动高度:

// select the header
const header = document.querySelector('header');
// add an event listener for scrolling
window.addEventListener('scroll', () => {
  // add the 'stuck' class
  if (window.scrollY >= 80) navbar.classList.add('stuck');
  // remove the 'stuck' class
  else navbar.classList.remove('stuck');
});

答案 9 :(得分:-1)

@vsync的最佳答案几乎是我所需要的,除了我通过Grunt“丑化”我的代码,并且Grunt需要一些较旧的JavaScript代码样式。这是我改用的调整后的脚本:

var stickyElm = document.getElementById('header');
var observer = new IntersectionObserver(function (_ref) {
    var e = _ref[0];
    return e.target.classList.toggle('isSticky', e.intersectionRatio < 1);
}, {
    threshold: [1]
});
observer.observe( stickyElm );

该答案的CSS不变