アンカーリンクに対応したアコーディオンを作成する。

アンカーリンクに対応したアコーディオンを作成する。

こんにちは。アドベントカレンダー15日目担当の山崎です。
今日はアンカーリンクに対応したアコーディオンを作ろうと思います。
例えばアコーディオンで1ページにまとめられているページがあって、アンカーリンクで指定されたアコーディオンまで移動しそれを開きたい。
または、アンカーリンク先がアコーディオンのコンテンツの中にある場合、アコーディオンを開いた後にその要素に移動したい。
といった時に下記の順番で対象の要素まで移動するようにします。

  1. 対象のアコーディオンまで移動
  2. そのアコーディオンを開く
  3. アンカーリンク先のIDの要素が.accordion-bodyの中にあった場合、さらにその位置まで移動

デモ動画は下記になります。

実装する

アコーディオンを作成

まず最初にアコーディオンのみを作ります。bodyタグの中に以下を入れてください。

<header>ヘッダー</header>
<div class="accordion-list">
    <div class="accordion-item" id="ac01">
        <div class="accordion-head">項目1</div>
        <div class="accordion-body">項目1のコンテンツ</div>
    </div>
    <div class="accordion-item" id="ac02">
        <div class="accordion-head">項目2</div>
        <div class="accordion-body">
            <dl>
                <dt id="ac02-01"></dt>
                <dd>項目2-1</dd>
                <dt id="ac02-02"></dt>
                <dd>項目2-2</dd>
                <dt id="ac02-03"></dt>
                <dd>項目2-3</dd>
            </dl>
        </div>
    </div>
    <div class="accordion-item" id="ac03">
        <div class="accordion-head">項目3</div>
        <div class="accordion-body">項目3のコンテンツ</div>
    </div>
</div>
<footer>フッター</footer>

.accordion-itemというdivタグの中に.accordion-head.accordion-bodyを作ります。.accordion-bodyはCSSで非表示にして、
.accordion-headをクリックしたら開くようにします。

function toggleAccordion($el) {
  $el.children('.accordion-body').slideToggle(500);
}

$('.accordion-head').click(function () {
  toggleAccordion($(this).parent());
});

アコーディオンを展開する処理自体はURLにハッシュタグがついていた場合でも使用するので関数化します。
引数に.accordion-itemが入るようにして、その直下の子要素の.accordion-bodyを開きます。

スクロールアニメーションを作成

function scrollAnimation(targetPosition) {
  $('body, html').animate({
    scrollTop: targetPosition
  }, 500);
}

こちらは引数に対象の要素のY座標が入るようにします。

URLにアンカーがついていたらその位置にスクロールしてアコーディオンを開くようにする。

var urlHash = location.hash;
if (urlHash) {
  $('body, html').stop().scrollTop(0);
  var $targetAc = $(urlHash).closest('.accordion-item'); //closestはその要素自身も指すのでアンカーリンク先のIDが.accordion-item自身でもその中の要素でも取得することができる。
  var targetAcPosition = $targetAc.offset().top;
  setTimeout(function () {
    scrollAnimation(targetAcPosition);
  }, 300);
  setTimeout(function () {
    toggleAccordion($targetAc);
  }, 800);
  if ($(urlHash).closest('.accordion-body').length) {
    setTimeout(function () {
    var targetPosition = $(urlHash).offset().top;
    scrollAnimation(targetPosition);
    }, 1300);
  }
}

まず、location.hashでURLについたハッシュを取得します。
URLのhashが取れたら、3行目で一旦スクロールトップを元に戻します。
4行目でスクロール先の対象の.accordion-itemを取得し、5行目でそのY座標を取得し、それを引数にしてscrollAnimationを実行します。

jQueryのclosestメソッドはその要素から引数に指定した親要素で一番近い要素を取得するメソッドですが、その要素自身も取れるので、.accordion-itemが対象だったとしても取得することができます。

対象のアコーディオンの項目までスクロールしたら次にアコーディオンを開く処理を行いたいのですが、scrollAnimationは500m秒でスクロールするように指定してあり、それをsetTimeOutで300m秒遅延させて実行しているので、合計800m秒送らせてtoggleAcordionを実行します。
アコーディオンを開くことができたら、最後にアンカーリンク先の要素が.accordion-bodyの中にある場合その位置までスクロールしたいので、12行目で対象のリンク先の要素が.accordion-bodyの中に入っているか(.accordion-bodyを親に持つか)確認します。
toggleAccordionslideToggleは500m秒で指定しているので、条件がtrueだった場合、setTimeOutで1300m秒遅らせてもう一度scrollAnimationを実行します。

ここまできたら、

  1. 対象のアコーディオンまで移動
  2. そのアコーディオンを開く
  3. アンカーリンク先のIDの要素が.accordion-bodyの中にあった場合、さらにその位置まで移動

ができると思います。

最後に全コードを貼っておきます。今回は簡単なサンプルなので、CSSもJSも一つのHTMLファイルにまとめています。

<html>
<head>
    <meta charset="UTF-8">
    <style>
        html,body {
            padding: 0;
            margin: 0;
        }
        header {
            background-color: #000;
            color: #fff;
            text-align: center;
        }
        footer {
            height: 100vh;
            line-height: 100vh;
        }
        .accordion-item {
            border-bottom: 1px solid;
        }
        .accordion-head {
            cursor: pointer;
            background-color: #f5f5f5;
        }
        .accordion-body {
            display: none;
        }
    </style>
</head>
<body>
    <header>ヘッダー</header>
    <div class="accordion-list">
        <div class="accordion-item" id="ac01">
            <div class="accordion-head">項目1</div>
            <div class="accordion-body">項目1のコンテンツ</div>
        </div>
        <div class="accordion-item" id="ac02">
            <div class="accordion-head">項目2</div>
            <div class="accordion-body">
                <dl>
                    <dt id="ac02-01"></dt>
                    <dd>項目2-1</dd>
                    <dt id="ac02-02"></dt>
                    <dd>項目2-2</dd>
                    <dt id="ac02-03"></dt>
                    <dd>項目2-3</dd>
                </dl>
            </div>
        </div>
        <div class="accordion-item" id="ac03">
            <div class="accordion-head">項目3</div>
            <div class="accordion-body">項目3のコンテンツ</div>
        </div>
    </div>
    <footer>フッター</footer>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script type="text/javascript">
      function toggleAccordion($el) {
        $el.children('.accordion-body').slideToggle(500);
      }

      function scrollAnimation(targetPosition) {
        $('body, html').animate({
          scrollTop: targetPosition
        }, 500);
      }

      $('.accordion-head').click(function () {
          toggleAccordion($(this).parent());
      });

      var urlHash = location.hash;
      if (urlHash) {
        $('body, html').stop().scrollTop(0);
        var $targetAc = $(urlHash).closest('.accordion-item'); //closestはその要素自身も指すのでアンカーリンク先のIDが.accordion-item自身でもその中の要素でも取得することができる。
        var targetAcPosition = $targetAc.offset().top;
        setTimeout(function () {
          scrollAnimation(targetAcPosition);
        }, 300);
        setTimeout(function () {
          toggleAccordion($targetAc);
        }, 800);
        if ($(urlHash).closest('.accordion-body').length) {
          setTimeout(function () {
            var targetPosition = $(urlHash).offset().top;
            scrollAnimation(targetPosition);
          }, 1300);
        }
      }
    </script>
</body>
</html>

CSSは本当に適当に書きましたが、以降の要素に十分な高さがないとスクロールの上限に達してしまい、スクロール位置まで辿りつけない場合があるので、
フッターを高さ100vhで指定しています。

以上になります。
アドベントカレンダー最終日は社長新留が担当です。お楽しみに。

  • このエントリーをはてなブックマークに追加

この記事を読んだ人にオススメ