CSS position:sticky イベント

要約

ヒント: 次のアプリでは、scroll イベントは必要ないかもしれません。使用 IntersectionObserver position:sticky 要素が固定されたとき、または固定されなくなったときにカスタム イベントを起動する方法を紹介します。すべて スクロール リスナーの使用。それを実証する素晴らしいデモもあります。

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder"></ph>
デモを見る | 出典

sticky-change イベントのご紹介

CSS 固定位置を使用する際の実用的な制限の一つは、 プロパティがアクティブであることを示すプラットフォーム シグナルを提供していない。 つまり、要素が固定状態になったときや、固定されたときなど、 固定されます

次の例では、画像から 10 ピクセルの <div class="sticky"> を固定しています。 その親コンテナの最上位に置くことができます。

.sticky {
  position: sticky;
  top: 10px;
}

要素がマークに達したときにブラウザがそれを認識すると、便利です。 どうやら私は私だけではないようです 生成 AI です。position:sticky のシグナルにより、さまざまなユースケースを実現できます。

  1. バナーが固定されたら、ドロップ シャドウを適用します。
  2. ユーザーがコンテンツを読んだら、アナリティクスのヒットを記録して、ユーザーの情報を把握します。 できます。
  3. ユーザーがページをスクロールしたら、フローティング TOC ウィジェットを現在のものに更新する 。

こうしたユースケースを念頭に置き、Google は、 position:sticky 要素が固定されると起動されます。名前は sticky-change イベント:

document.addEventListener('sticky-change', e => {
  const header = e.detail.target;  // header became sticky or stopped sticking.
  const sticking = e.detail.stuck; // true when header is sticky.
  header.classList.toggle('shadow', sticking); // add drop shadow when sticking.

  document.querySelector('.who-is-sticking').textContent = header.textContent;
});

このデモでは、以下を使用します。 修正するとドロップ シャドウをヘッダーに指定します。また、 新しいタイトルを付けます。

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder"></ph>
このデモでは、スクロール イベントなしでエフェクトが適用されます。

スクロール イベントのないスクロール効果

<ph type="x-smartling-placeholder">
</ph> ページの構造。
ページの構造

いくつかの用語を整理して これらの名前を参照できるようにします おさらいしましょう。

  1. スクロール コンテナ - 「ブログ投稿」のリスト。
  2. ヘッダー - position:sticky がある各セクションの青色のタイトル。
  3. 固定セクション - 各コンテンツ セクション。スクロールしなければ見えない範囲の 固定ヘッダーを使用します。
  4. 「固定モード」 - position:sticky が要素に適用される場合。

どのヘッダーが「固定モード」になっているかを判断するには、なんらかの方法で スクロール コンテナのスクロール オフセット。この方法により、 を使用して、現在表示されている header を計算できます。ただし、 scroll イベントなしで行うのは簡単ではありません。 position:sticky は、要素が固定されるとレイアウトから要素を削除します。

そのため、スクロール イベントがないと、レイアウト関連の ヘッダーに対して行います。

スクロール位置を決定するためにダンビー DOM を追加する

ここでは、scroll イベントの代わりに IntersectionObserver を使用して、 ヘッダーの固定モードの開始と終了を指定します。2 つのノードの追加 (追記)各固定セクションに 1 つずつ、上部に 1 つずつ スクロール位置を把握するためのウェイポイントとして機能します。これらの マーカーがコンテナに出入りすると、表示が変化し、 Intersection Observer がコールバックを開始します。

<ph type="x-smartling-placeholder">
</ph> 標識要素なし
隠れたセンチネル要素。

上下にスクロールする 4 つのケースに対応するには、2 つの見解が必要です。

  1. 下にスクロール - ヘッダーは、上位のセンチネルが交差するときに固定表示されます。 移動します。
  2. 下にスクロール - ヘッダーはページの一番下までスクロールすると固定モードが解除されます。 セクションとその下部の標識がコンテナの上部を横断しています。
  3. 上にスクロール - ヘッダーは最上部をスクロールすると固定モードのままになります ビューに戻ります
  4. 上にスクロール - 下部のセンチネルが後ろに交差してヘッダーが固定される 上から見ていきます

1 ~ 4 個のスクリーンキャストを発生順に表示しておくと便利です。

<ph type="x-smartling-placeholder">
</ph>
Intersection Observer は、警戒心が スクロール コンテナに入る/出る。

CSS

標識は各セクションの上部と下部に配置されます。 .sticky_sentinel--top はヘッダーの上部に配置され、 .sticky_sentinel--bottom はセクションの最下部にあります。

<ph type="x-smartling-placeholder">
</ph> 下位のセンチネルがしきい値に達しています。 上下の標識要素の位置。
:root {
  --default-padding: 16px;
  --header-height: 80px;
}
.sticky {
  position: sticky;
  top: 10px; /* adjust sentinel height/positioning based on this position. */
  height: var(--header-height);
  padding: 0 var(--default-padding);
}
.sticky_sentinel {
  position: absolute;
  left: 0;
  right: 0; /* needs dimensions */
  visibility: hidden;
}
.sticky_sentinel--top {
  /* Adjust the height and top values based on your on your sticky top position.
  e.g. make the height bigger and adjust the top so observeHeaders()'s
  IntersectionObserver fires as soon as the bottom of the sentinel crosses the
  top of the intersection container. */
  height: 40px;
  top: -24px;
}
.sticky_sentinel--bottom {
  /* Height should match the top of the header when it's at the bottom of the
  intersection container. */
  height: calc(var(--header-height) + var(--default-padding));
  bottom: 0;
}

Intersection Observer を設定する

Intersection Observer は、すべての交差点の変化を非同期的に監視します。 ターゲット要素とドキュメント ビューポートまたは親コンテナの 2 つのいずれかになります。この例では 親コンテナとの交差を確認できます

マジック ソースは IntersectionObserver です。各センチネルに IntersectionObserver を使用して、その交差点の可視性を スクロール コンテナ標識が表示可能なビューポートまでスクロールすると、 ヘッダーが修正される、または固定されなくなった。同様に 監視を終了すると 作成します。

まず、ヘッダーとフッターのセンチネルのオブザーバーを設定します。

/**
 * Notifies when elements w/ the `sticky` class begin to stick or stop sticking.
 * Note: the elements should be children of `container`.
 * @param {!Element} container
 */
function observeStickyHeaderChanges(container) {
  observeHeaders(container);
  observeFooters(container);
}

observeStickyHeaderChanges(document.querySelector('#scroll-container'));

次に、.sticky_sentinel--top 要素が渡されたときに起動するオブザーバーを追加しました。 スクロール コンテナの上部(いずれかの方向に)通過します。 observeHeaders 関数は、トップ センチネルを作成して、 各セクションに対応しますオブザーバーは、センチネルと コンテナの最上部に配置され、それがビューポートに入るか外に出るかを判断します。 情報によって、セクション ヘッダーが固定されるかどうかが決まります。

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

オブザーバーは threshold: [0] で構成されているため、すぐにコールバックが呼び出されます。 見ていきます

一番下の文(.sticky_sentinel--bottom)についても同様です。 フッターが下部を通過すると起動する 2 つ目のオブザーバーが作成されます。 スクロール コンテナのオブジェクトです。observeFooters 関数は、 各セクションにアタッチしますオブザーバーは、 センチネルとコンテナの底部の交差部分を 出入りすることもありますこの情報により、セクション ヘッダーが 気づくかもしれません。

/**
 * Sets up an intersection observer to notify when elements with the class
 * `.sticky_sentinel--bottom` become visible/invisible at the bottom of the
 * container.
 * @param {!Element} container
 */
function observeFooters(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;
      const ratio = record.intersectionRatio;

      // Started sticking.
      if (targetInfo.bottom > rootBoundsInfo.top && ratio === 1) {
        fireEvent(true, stickyTarget);
      }

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

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

オブザーバーは threshold: [1] で構成されており、 ビュー内にあります。

最後に、sticky-change カスタム イベントを発生させる 2 つのユーティリティがあります。 センチネルを生成します。

/**
 * @param {!Element} container
 * @param {string} className
 */
function addSentinels(container, className) {
  return Array.from(container.querySelectorAll('.sticky')).map(el => {
    const sentinel = document.createElement('div');
    sentinel.classList.add('sticky_sentinel', className);
    return el.parentElement.appendChild(sentinel);
  });
}

/**
 * Dispatches the `sticky-event` custom event on the target element.
 * @param {boolean} stuck True if `target` is sticky.
 * @param {!Element} target Element to fire the event on.
 */
function fireEvent(stuck, target) {
  const e = new CustomEvent('sticky-change', {detail: {stuck, target}});
  document.dispatchEvent(e);
}

これで作業は完了です。

最後のデモ

position:sticky の要素が scroll イベントを使用せずにスクロール効果を固定および追加しました。

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder"></ph>
デモを見る | 出典

まとめ

よく疑問に思ったことがあります。IntersectionObserver は イベントベースの scroll の UI パターンを置き換えるのに役立つツールです。 長年にわたって進化してきました答えは「はい」か「いいえ」です。セマンティクス すべてに使用することは困難です。IntersectionObserverしかし、 ここで紹介しましたが、これを使用していくつかの興味深い手法を使用できます。

スタイルの変更を検出する別の方法は?

そうでもありません。必要なのは、DOM 要素のスタイル変更を監視する方法でした。 残念ながらウェブ プラットフォーム API には、ユーザーが 時計のスタイルの変更。

MutationObserver は論理的な第 1 の選択肢となりますが、次の場合には当てはまりません。 使用できます。たとえば、このデモでは、sticky クラスは要素に追加されますが、要素の計算済みスタイルが変更されたときには追加されません。 sticky クラスはページの読み込み時にすでに宣言されていることを思い出してください。

将来的には、 「Style Mutation Observer」 Mutation Observer の拡張機能を使用すると、変更の監視に役立つ 要素の計算済みスタイルです。 position: sticky