今回は特定のエリアで追従するタイトルを実装するコードについて解説していきたいと思います!

追従タイトルの実装といえば、position stickyなどがありますが、親要素や祖先要素にoverflow: hiddenを指定しているとposition stickyが利用できません。
今回はそういった場合に使用できるコードになります!

実際のjavascriptのコードを見てみましょう!

コード

See the Pen 追従タイトル by 八木秀一郎 (@huuglfoo-the-solid) on CodePen.

コードが長いですが、ひとつひとつ解説していきますので、ご安心ください!!

追従させる要素やエリアの指定

const areaFixedFunk = (fixedElm, fixedArea, offsetStart = null, fixedTop = null, offsetEnd = null) => {
const areas = document.querySelectorAll(fixedArea);
if (areas.length === 0) return;

このコードは関数を定義しています。
以下はそれぞれの引数の解説になります!

  • fixedElm
    追従させる要素(タイトル)を指定します。
  • fixedArea
    追従が発生するエリアを指定します。
  • offsetStart
    追従を開始するスクロール位置を指定します。
    数値を指定した場合: 追従を開始するスクロール位置を微調整できます。
    例:offsetStart = 100 なら、area の開始位置から 100px スクロールされたときに追従処理が開始されます。
    null の場合: デフォルト動作(area.getBoundingClientRect().top の値)に依存します。
  • fixedTop
    追従する要素の top の位置を指定します。
    数値を指定した場合: 追従する要素がページの上部からどれだけ離れるかをピクセル単位で指定します。
    例: fixedTop = 50 なら、追従する要素は常に上から 50px 離れた位置に配置されます。
    null の場合: デフォルト動作(要素のスタイルに依存)に従います。
  • offsetEnd
    追従を解除する位置を指定します。
    数値を指定した場合: 固定解除の位置を指定されたピクセル数分調整します。
    例:offsetEnd = 100 なら、エリアの終端から 100px 手前で追従が解除されます。
    null の場合: デフォルト動作に従います(area.getBoundingClientRect().top + areaHeight の値)。

追従させるために必要な各値の取得

const checkFixed = (target, area) => {
 const startPosi = area.getBoundingClientRect().top;
 const targetHeight = target.clientHeight;
 const areaHeight = area.clientHeight;
 const areaLeft = area.getBoundingClientRect().left;

checkFixed 関数の引数から解説していきます。

  • target
    追従の対象となる要素。fixedElmで指定した要素です。
  • area
    追従が発生するエリア。fixedAreaでで指定した要素です。

次は各定数がどういった意味を持つのか見ていきましょう!

  • startPosi
    値:area.getBoundingClientRect().top;
    エリアの上端の位置を ビューポートの上端からの距離です。
  • targetHeight
    値:target.clientHeight;
    追従する要素(target)の高さです。
  • areaHeight
    値:area.clientHeight
    追従が発生するエリア全体の高さです。
  • areaLeft
    値:エリアの左端の位置を ビューポートの左端からの距離 で取得しています。
    これにより、ウィンドウサイズに依存せず正確に水平位置を固定できます。

条件分岐を使用してクラスの付与と追従させたい要素の位置調整

 if (startPosi <= offsetStart && (startPosi + areaHeight) > offsetStart + targetHeight + offsetEnd) {
  target.classList.add(‘is-fixed’);
  target.style.top = fixedTop + ‘px’;
  target.style.left = areaLeft + ‘px’;
 } else if (startPosi > offsetStart) { target.classList.remove(‘is-fixed’);
  target.style.top = ”;
  target.style.left = ”;
 } else {
  target.classList.remove(‘is-fixed’);
  target.style.top = (areaHeight – targetHeight) + ‘px’;
  target.style.left = ”;
 }

このコードでは追従させる要素の状態を判定し、条件に応じてクラスの付与と削除を行っているコードになります。

では最初のif文について解説します!

条件式(追従状態)

  • startPosi <= offsetStart
    エリアの上端位置(startPosi)が追従開始位置(offsetStart)に達した場合。
  • (startPosi + areaHeight) > offsetStart + targetHeight + offsetEnd
    追従要素がエリアの範囲内に収まっている場合。

動作

  • クラスの付与:
    is-fixed クラスを追加して、固定表示の状態に変更します。
  • スタイルの設定
    top: 要素をfixedTopで指定した位置(ビューポート上端からの距離)に配置します。
    left: エリアの左端(areaLeft)に合わせて水平位置を固定します。

続いてはelseif の条件式です

条件式(追従開始前

  • startPosi > offsetStart
    エリアがまだ追従開始位置(offsetStart)に達していない場合。

動作

  • クラスの削除:
    is-fixed クラスを削除して、追従状態を解除します。
  • スタイルの初期化
    top と left を空に設定して、要素を元の位置に戻します。

最後にelseの条件を詳しく見ていきましょう。

条件式(追従を解除)

  • 追従要素の下端がエリアの終端を超えた場合。

動作

  • クラスの削除:
    追従要素をエリア内の終端に固定するために、(areaHeight – targetHeight) の位置に配置します。
  • スタイルの初期化
    top::追従要素をエリア内の終端に固定するために、(areaHeight – targetHeight) の位置に配置します。
    left: 水平方向の固定は解除します。

このようにこのコードでは、追従された際にクラスの付与と削除を行い、cssのスタイルを調整しています!

画面幅に応じて追従を解除

const handleScrollOrResize = () => {
 areas.forEach((area) => {
  const target =  area.querySelector(fixedElm);
   if (target && window.innerWidth >= 0) {
   checkFixed(target, area);
  } else {
   target.classList.remove(‘is-fixed’);
   target.style.top = ”;
   target.style.left = ”;
  }
 });
};
handleScrollOrResize();
window.addEventListener(‘resize’, () => { handleScrollOrResize(); });

window.addEventListener(‘scroll’, () => {
 if (!ticking) {
  window.requestAnimationFrame(() => {
   handleScrollOrResize(); ticking = false;
   });
   ticking = true;
 }
}, { passive: true });

長々と書いていますが、ここまでくれば後は簡単です!
簡潔に説明しますね!

このコードはスクロールやリサイズに応じて、追従要素の状態を更新する関数です。

今回のコードではwindow.innerWidth >= を0に設定していますが、
仮に600を設定した場合、画面幅が600px以上の時のみ要素が追従し、それ以下では追従しないようになります。

デザインの都合上、画面幅が狭いときに追従を解除したい場合などに、活用することができます!

CSS

.fixedArea {
 position: relative;
}
.fixedTitle {
 position: absolute;
}
.fixedTitle.is-fixed {
 position: fixed;
}

  • .fixedArea
    fixedTitleがfixedAreaを基準に配置されるようにしています。
  • .fixedTitle
    fixedAreaの終端に達した際に、終端で固定するために指定しています。
  • .fixedTitle.is-fixed
    スクロール時にfixedArea内で追従させるために必要です。

上記はCSSのスタイリングになります。

これを記載すればタイトルが追従するようになるはずです!

まとめ

今回は、特定のエリアで追従するタイトルを実装するコードについて解説しました!
position: stickyが使えない状況でも、position: fixedや条件分岐を活用することで、追従処理が可能になります。

デザインの一部を追従させたい場面や、特定のエリア内だけで表示位置をコントロールしたい場面で、このコードは役に立ちます!
よろしければ実装して試してみてください!