CSSでposition: sticky;を指定した要素の状態をJavaScriptのIntersection Observer APIで検知する
最終更新日: Update!!
CSSでposition: sticky;を指定することで、要素をスクロールに合わせて固定表示を自由に設定することができます。ただ、これだけですと固定表示にできるものの、固定表示になっているかの状態を検知することはできません。そこで今回はJavaScriptのIntersection Observer APIを使ってスティッキー要素の固定表示の状態を検知する処理を作成してみたいと思います。
position: sticky;とJavaScriptのIntersection Observer APIについては当サイトの過去記事でそれぞれ詳しくまとめていますので、こちらもご参考にどうぞ。
(過去の参考記事)
JavaScriptのIntersectionObserverAPIでビューポート内での表示を検知する
CSSのposition: sticky;でスクロール時にレイアウトを固定できるスティッキー要素を作成する
それぞれの細かい解説やサンプルなどはこちらの記事をご覧いただければと思います。それでは早速サンプルのコードを見ていきます。まずはHTMLでスティッキーのコンテナ要素と実際に固定表示させる要素を用意します。ただ、今回はJavaScript側で検知するために、新たに固定表示させる範囲の基準位置に置くガイド要素も作成するのがポイントになります。
【HTML】※一部抜粋
これらの情報を参照しながら、スティッキー要素が固定状態になる条件を確認していきます。まず上限のガイドとコンテナ要素については検知エリアであるビューポートに入っていることが前提になります。そして、下限のガイドについては、画面の上部より外側へ外れた時に状態が切り替わるようになります。そのため、検知のタイミングでDOMのtopの位置がマイナスになった時が対象となります。これらの条件が組み合わさったときに、スティッキー状態のフラグメントを切り替えられるようにスクロールイベントで値を更新されるようにします。場合によってはリサイズイベントも合わせて設定しておいた方が良いかもしれませんね。
実際に作成したサンプルはこちらになります。スクロールに合わせてスティッキー要素がコンテナ要素内で固定表示へと切り替わり、その状態に合わせて動的にスタイルが切り替わっていることが確認できます。このような処理は先述のスティッキー要素の固定表示を判別するフラグメント用の変数を使うことで実現できます。
今回はposition: sticky;の状態をIntersection Observer APIを使って検知する方法についてまとめてみました。下方向への順スクロールだけの検知であればここまでする必要はないですが、上方向への逆スクロールに対してもスティッキー要素の 固定表示が可能となるため、上端と下端の両方でトリガーを設定する必要があるというのがポイントになりますね。 (参考にさせて頂いたサイト) MDN 交差オブザーバー API
<section class="sticky-container js-observe-target" data-guide="container"> <!-- スティッキー範囲の上限位置のガイド --> <span class="sticky-guide-top js-observe-target" data-guide="top"></span> <div class="sticky-element">スティッキー要素</div> <!-- スティッキー範囲の下限位置のガイド --> <span class="sticky-guide-bottom js-observe-target" data-guide="bottom"></span> </section>ガイド要素やコンテナ要素は後述するJavaSciptにてDOMで取得する必要があるため、カスタムデータ属性でラベリングしておきます。合わせて、Intersection Observer APIで検知する要素の対象にもなるので、共通のclassを当てておき、ビューポートに入ったことを検知できるようにしておきます。 続いてCSSです。コンテナ要素とスティッキー要素については通常通りの設定を行いますが、ガイド要素の2つはそれぞれスティッキー範囲の上端と下端の位置に配置されるよう絶対位置指定を使って調整します。この場所がビューポートに入ったかどうかでスティッキーの状態を検知できるようになります。 【CSS】
.sticky-container { position: relative; padding: 20px 20px 180px 20px; min-height: 120vh; } .sticky-element { position: sticky; top: 20px; width: auto; height: 60px; } .sticky-guide-top, .sticky-guide-bottom { width: 100%; height: 0; display: block; position: absolute; left: 0; visibility: hidden; } .sticky-guide-top { top: 0; } .sticky-guide-bottom { bottom: calc((20px + 180px) + 60px); //コンテナ要素の下端から、コンテナ要素の内余白とスティッキー要素の高さ、スティッキー要素の固定位置の余白分を足した分だけ上になるように }ガイド要素について、コンテナ要素のpaddingやスティッキーの表示位置と画面の余白などに合わせて調整します。特に下限位置については、正しい位置になるよう、余白やスティッキー要素の高さを計算して位置を指定します。ガイド要素は念の為見えないようにしておくと良いですね。 最後にJavaScriptでビューポートを対象にした検知ができるように処理を作成していきます。まずは変数としてスティッキー要素が固定表示になっているか、そして、コンテナ要素とガイド要素がそれぞれビューポート内で検知されているかどうかのフラグメント用のデータを用意します。これらの値が条件によって切り替わるという感じですね。そして、Intersection Observer APIで対象要素を検知していきます。この時に対象要素は「IntersectionObserverEntry」としていろんな情報が格納された配列の形式でデータを得ることができます。このデータをもとにコンテナ要素やガイド要素のDOMの情報を取得し、それぞれの要素に合わせて検知された時の処理を分岐していきます。 【JavaScript】
let isActiveSticky = false; let stickyTrigger = { top: false, bottom: false, container: false }; const doObserve = () => { const targets = document.querySelectorAll('.js-observe-target'); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if(entry.target.attributes['data-guide'].value === 'top') { if (entry.isIntersecting) { // スティッキー範囲の上限位置が画面上端より外に出た場合 stickyTrigger.top = false } else { stickyTrigger.top = true } } if(entry.target.attributes['data-guide'].value === 'bottom') { if (!entry.isIntersecting && entry.boundingClientRect.top < 0) { // スティッキー範囲の下限位置が画面上端より外に出た場合 stickyTrigger.bottom = false; } else { stickyTrigger.bottom = true; } } if(entry.target.attributes['data-guide'].value === 'container') { if (entry.isIntersecting) { // スティッキー範囲の要素が画面内に入った時 stickyTrigger.container = true } else { stickyTrigger.container = false } } }); }, { root: null, rootMargin: '0px', threshold: 0 }); Array.from(targets).forEach((target) => { observer.observe(target); }); }; doObserve(); window.addEventListener('scroll', () => { if(stickyTrigger.top && stickyTrigger.container && stickyTrigger.bottom){ isActiveSticky = true; } else { isActiveSticky = false; } });IntersectionObserverEntryでは対象要素のDOMや検知の有無、また表示されている範囲の割合などの情報を下記のように参照することができます。これらを活用することで、より複雑なスクロールに合わせた処理を実現することができます。
IntersectionObserverEntry.boundingClientRect | 対象要素の座標やサイズなどの情報を取得することができます |
---|---|
IntersectionObserverEntry.isIntersecting | 対象要素が検知範囲に入ったかどうかを判定することができます |
IntersectionObserverEntry.intersectionRect | 対象要素の検知範囲に入っている領域に相当する矩形の座標やサイズなどの情報を取得することができます |
IntersectionObserverEntry.intersectionRatio | boundingClientRectに対してのintersectionRectの割合を示し、対象要素がどれくらい検知範囲に入っているかがわかります |
今回はposition: sticky;の状態をIntersection Observer APIを使って検知する方法についてまとめてみました。下方向への順スクロールだけの検知であればここまでする必要はないですが、上方向への逆スクロールに対してもスティッキー要素の 固定表示が可能となるため、上端と下端の両方でトリガーを設定する必要があるというのがポイントになりますね。 (参考にさせて頂いたサイト) MDN 交差オブザーバー API
sponserd
keyword search
recent posts
- ViteでMarkuplintとPrettierを使える環境を構築する
ViteでMarkuplintとPrettierを使える環境を構築する
- ViteでStylelintとESlintを使える環境を構築する
ViteでStylelintとESlintを使える環境を構築する
- マウスオーバーしたセルを含む行列がハイライトするテーブルを作成する:has()擬似クラスの活用例
マウスオーバーしたセルを含む行列がハイライトするテーブルを作成する:has()擬似クラスの活用例
- ViteでVue.jsとVuex(Pinia)とVue Routerを使ってみる
ViteでVue.jsとVuex(Pinia)とVue Routerを使ってみる
- ViteでHandlebarsを使った複数ページの作成に使える外部JSONファイルのデータを読み込む
ViteでHandlebarsを使った複数ページの作成に使える外部JSONファイルのデータを読み込む
- ViteでTailwindCSSとテンプレートエンジンのHandlebarsを使ったページコーディング
ViteでTailwindCSSとテンプレートエンジンのHandlebarsを使ったページコーディング
- ViteでPostCSS周りの設定やSassを使う
ViteでPostCSS周りの設定やSassを使う
- フロントエンドの開発環境にVite + TypeScriptを導入する
フロントエンドの開発環境にVite + TypeScriptを導入する
categories