web-dev-qa-db-ja.com

position:stickyがトリガーされたときに検出するイベント

新しいposition: stickyinfo )を使用して、iOSのようなコンテンツのリストを作成しています。

以前のJavaScriptの代替( )よりも優れており、はるかに優れていますが、トリガーされたときにイベントが発生しないことがわかっている限り、つまり、バーが上部に到達したときに何も実行できません以前のソリューションとは異なり、ページ。

position: stickyを含む要素がページの上部に到達したときに、クラス(例:stuck)を追加したいと思います。 JavaScriptでこれをリッスンする方法はありますか? jQueryの使用は問題ありません。

使用中の新しいposition: stickyのデモが見つかります here

36
AlecRust

誰かがGoogle経由でここに到着した場合、自分のエンジニアの1人がIntersectionObserver、カスタムイベント、およびセンチネルを使用するソリューションを持っています。

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

16
Scott Leonard

Demo with 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>

top値は-1pxである必要があります。そうしないと、要素がブラウザウィンドウの上部と交差することはありません(したがって、交差オブザーバをトリガーすることはありません)。

この1pxの非表示コンテンツに対抗するには、追加の1pxのスペースを、スティッキー要素のボーダーまたはパディングのいずれかに追加する必要があります。

昔ながらのscrollイベントリスナーを使用したデモ:

  1. 最初のスクロール可能な親を自動検出
  2. スクロールイベントのスロットリング
  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コンポーネントのデモ で、最初の手法を使用しています

16
vsync

現在、ネイティブソリューションはありません。 ターゲット位置:現在「スタック」状態にあるスティッキー要素 を参照してください。ただし、ネイティブposition: stickyとスティッキー動作を実装するポリフィルの両方で機能するCoffeeScriptソリューションがあります。

スティッキーにしたい要素に「スティッキー」クラスを追加します。

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

「スティッキー」要素の位置を監視し、「スティッキー」状態にあるときに「スタック」クラスを追加する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)

注:このコードは、垂直方向の固定位置でのみ機能します。

5
Will Koehler

私は魅力的なように機能し、かなり小さいこのソリューションを思いつきました。 :)

追加の要素は必要ありません。

小さな欠点ですが、ウィンドウスクロールイベントで実行されます。

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;

    _$sticky.classList.toggle('js-is-sticky', isStuck);
}

注:このソリューションでは、ボトムスティッキネスを持つ要素は考慮されません。これは、スティッキーヘッダーなどの場合にのみ機能します。ただし、ボトムスティッキネスを考慮に入れるように調整することもできます。

2
Daniel Tonon

Chrome追加後position: sticky、それは 不十分な準備ができている および -enable-experimental-webkit-featuresフラグに委任されている であることがわかりました。ポールアイルランド 2月に述べた 「機能は奇妙なリンボ状態atmにあります」。

ポリフィル を頭痛の種になるまで使いました。これはうまく機能しますが、CORSの問題などのまれなケースがあり、すべてのCSSリンクに対してXHRリクエストを実行し、ブラウザーが無視した「position:sticky」宣言のためにそれらを再解析することにより、ページの読み込みを遅くします。

今、私は ScrollToFixed を使用しています。これは、ラッパーでレイアウトを台無しにしないため、 StickyJS よりも優れています。

2
Turadg

質問されてからしばらく経ちましたが、これに対する良い解決策を見つけました。プラグイン stickybits は、サポートされている場合はposition: stickyを使用し、要素が「スタック」しているときにクラスを要素に適用します。私は最近それを使って良い結果を出しました、そして、執筆の時点で、それは活発な開発です(これは私にとってプラスです):)

1
Davey

バニラJSを使ってください。 lodashのスロットル機能を使用して、パフォーマンスの問題を防ぐこともできます。

const element = document.getElementById("element-id");

document.addEventListener(
  "scroll",
  _.throttle(e => {
    element.classList.toggle(
      "is-sticky",
      element.offsetTop <= window.scrollY
    );
  }, 500)
);