JavaScriptで目次から移動した際に任意の要素を表示させる

JavaScriptで目次から移動した際に、任意の要素を表示させる
JavaScriptで目次から移動した際に、任意の要素を表示させる

JavaScriptを使って目次を自動で作る仕組みの自作を試みていますが、今回はそれを利用したものを考えてみました。

目次が実際に使われた場合に、サイト側で何らかのアクションを連動させられればどうだろう、というのが発端です。

目次が使われる割合は多くないと思いますが、全く使われないわけではないようなので、今回の仕組みの影響は0ではないかもしれません。

構築環境
JavaScriptpure JavaScript
対応ブラウザIE10+

実現したいこと

実現したいことは以下の通り。

  • JavaScriptで目次を自動的に作成(h要素1種類に対してのみ作成)
  • 目次で移動した先でCTAを表示させる

上記を実現しようと考えた理由は以下のような仮説を立てたためです。

  1. 目次で移動した、ということはその目次が表す内容への関心が高い
  2. 満足した場合、目次から移動した付近が離脱ポイントにもなる可能性がある
  3. 離脱ポイントにCTAを置けば良い効果があるかもしれない

しかしながら、当記事末尾に記載していますが、今のところ効果は期待できない状態です…。

目次出力の部分に関する説明

冒頭で触れた目次を出力するJSの説明は、以前に書いた以下の記事をご覧ください。

サンプルコードの実際の動作

サンプルコードの実際の動作は以下のようになります。

  • h2のみを対象として、JSで目次を作成する
  • 目次をクリックして移動後、移動先のh2の次のh2の上部に任意のCTAを表示させる
  • 目次をクリックした時点で、今回のコードで既にCTAを作成していた場合は、元からあった方を消す

言葉にするとわかりにくいですが、当ページの動作で何となくでも理解してもらえるかと思います。

コード

サンプルコードは以下の通り。

以前の「JavaScriptを使って目次を自動生成する」を少し手直しした上で、新規にCTA表示用コードを追加した形です。


(!function(){
//DOMの構築が完了したら動作開始
document.addEventListener('DOMContentLoaded', myTOC());
function myTOC(){
  //CTAの中身を設定(※現在の記述は例)
  var cta     = '<a href="">目次と連動しているCTA</a>';
  //スマホなど小さなブラウザサイズ時に出力するCTA。空欄の場合はブラウザサイズが小さくても通常のCTAを表示
  var ctaSmt  = '<a href="">目次と連動しているCTA(スマホ用)</a>';

  //目次作成と表示の対象となる範囲を、ラップ要素のIDで指定 ※必須
  var range   = "main";
  //目次のタイトルを設定 ※必須
  var title   = "目次";
  //目次として取得するh要素を指定 ※必須
  var h       = "h2";
  //目次を表示する目印となるタグ(指定範囲内に存在する最初のタグの上に目次が表示される)を、取得するh要素から変更したい場合はタグ名を入力
  //※オプション
  var tag     = "";
  //IDで指定する場合はこちらを入力。
  //IDの指定がない、あるいは、IDで指定した要素がない場合は、タグ指定が優先。
  //※オプション
  var tagId   = "";
  //目次から移動した際の位置調整。
  //上からの余白を 数値 で指定。pxは不要。
  //spanにpadding-topを追加するので、不具合のでる可能性がある
  //※オプション
  var space   = "100";
  //スマホサイズでの閲覧時に設定
  //※オプション
  var spaceS  = "40";
  //スマホ用の余白を適用するためのトリガーとなる画面サイズを指定。入力しない場合はデフォルトの375pxが適用される。
  //※オプション
  var display = "";


  //目次のタイトルに使うpタグを生成
  var p      = document.createElement("p");
  //目次のラップ要素を生成
  var wrap   = document.createElement("div");
  //目次に使うolを生成
  var ol     = document.createElement("ol");
  //目次に使うliを入れる変数
  var li     = "";
  //目次に使うaを入れる変数
  var a      = "";
  //目次に使うIDを入れる変数
  var tocId  = "";
  //目次に使うアンカーテキストを入れる変数
  var tocStr = "";
  //目次のループ内の連番用変数
  var num    = "";
  //目次対象のh要素の中身を入れる変数
  var hStr   = "";

  //目次を作るための範囲をIDで指定して取得
  range = document.getElementById(range);
  //対象範囲内のh要素を取得
  h     = range.getElementsByTagName(h);
  //目次を入れる場所の指定位置を確定
  //優先順位は ID指定 > タグ指定
  tagId = document.getElementById(tagId);
  if(tagId){
    //目次を入れる目印の要素としてIDが指定されている場合
    tag = tagId;
  } else if(!tag || tag == h) {
    //タグ指定がないか、目印の要素と取得する要素が同じ場合
    tag = h[0];
  } else {
    //目印の要素と、目次として取得するh要素が違う場合
    tag = range.getElementsByTagName(tag);
    //目印の要素の中で最初の1つを取得
    tag = tag[0];
  }

  //スマホ画面サイズの余白指定がなければ375pxを設定
  if(!display) {
    display = 375;
  }

  //スマホの画面サイズ用の余白仕手があれば設定する
  if(spaceS && !isNaN(spaceS) && window.matchMedia( "screen and (max-width:" + display + "px)" ).matches ) {
    space = spaceS;
  }

  //目次から移動後の位置調整に数値があればpadding-topで設定。数値以外であれば消す。
  if(space && !isNaN(space)){
    space = ' style="padding-top:' + space + 'px;"';
  } else {
    space = "";
  }

  //最初のh2の上に目次のラップ要素を追加
  var parent = tag.parentNode;
  parent.insertBefore(wrap , tag);
  //ラップ要素にIDを設定
  wrap.id = "my-toc";

  //ラップ要素の中にタイトルのPタグを入れる
  wrap.appendChild(p);
  //目次のタイトルの文言を設定
  p.innerHTML = title;
  //目次のタイトルにクラス名を設定
  p.classList.add("my-toc-title");

  //olをラップ要素の中に入れる
  wrap.appendChild(ol);

  for (var i = 0; i < h.length; i++) {
    //構造化マークアップやクラス名に使うために1からはじまる連番を設定
    num   = i + 1;
    //目次に使うIDを設定
    tocId = "my-toc-" + num;

    //hの中身を取得
    hStr  = h[i].innerHTML;

    //hの中身を連番id付きspanで囲む
    h[i].innerHTML = '<span id="' + tocId + '"' + space + '>' + hStr + '</span>';

    //hのdata属性(data-toc)を取得
    tocStr = h[i].getAttribute("data-toc");
    if (!tocStr){
      //data属性(data-toc)がなければh要素のテキストを取得
      tocStr = hStr;
    }

    //liを生成
    li = document.createElement("li");
    //aを生成
    a  = document.createElement("a");

    //aタグにページ内リンクを設定
    a.setAttribute("href", "#" + tocId);
    //aタグにクラスを設定 計測用
    a.classList.add("my-toc-item", "my-toc-item-" + num);
    //aタグにクラスを設定 CTA表示用
    a.id = 'my-toc-item-id' + num;
    //aタグにh2のテキストかdata属性の値を設定
    a.innerHTML = tocStr;

    //liにaを入れる
    li.appendChild(a);
    //liをolの中に入れる
    ol.appendChild(li);
  }

  //処理完了後にイベント削除
  document.removeEventListener("DOMContentLoaded", myTOC,false);

  //目次をクリックしたら、CTAをその次の目次の上に追加する
  wrap.addEventListener('click',function(e){
    addCta(e);
  });

  function addCta(e){
    //クリックされた要素を入れる変数
    var obj              = e.target;
    //CTAが入る大枠の要素を入れる変数
    var ctaWrap          = "";
    //クリックした目次の親を入れる変数
    var currentOjParent  = "";
    //クリックした目次の親の、次の要素を入れる変数
    var nextObjParent    = "";
    //クリックした目次の親の、次の要素の子要素のhref属性を入れる変数
    var nextObjChildHref = "";
    //クリックした目次の移動先のh要素の、次のh要素の中のspanのidを取得
    //目次はh>span>aとなっているため、次のh要素に対して処理をする場合はspanから辿る必要がある
    var nextSpanId       = "";
    //移動先のh要素の次に存在するh要素を入れる変数
    var nextH            = "";

    //目次クリック時に、既に出力済みのCTAがあると邪魔なので、検索して存在すれば削除
    ctaWrap = document.getElementById('toc-cta');
    if(ctaWrap){
      ctaWrap.parentNode.removeChild(ctaWrap);
      ctaWrap = "";
    }
    //スマホ用のCTAがあり、かつ指定したブラウザサイズ(指定がない場合は375px)の場合に、スマホ用CTAを出力する
    if(ctaSmt && window.matchMedia( "screen and (max-width:" + display + "px)" ).matches){
      cta = ctaSmt;
    }
    //クリックした目次の親のliを取得
    currentOjParent = obj.parentNode;
    //クリックした目次の親のliの、次のliを取得
    nextObjParent   = currentOjParent.nextElementSibling;
    //クリックした目次の親のliの、次のliがあれば処理
    if(nextObjParent != null){
      //クリックした目次の親のliの、次のliのspanのhrefを取得(これが移動先のh要素の次のh要素を取得する手がかりなる)
      nextObjChildHref  = nextObjParent.firstElementChild.href;
      //取得したhrefから、#以前の文字列を削除して、ページ内リンク用のIDを取得
      nextSpanId        = nextObjChildHref.replace( /.*#/g , "" );

      //CTA用のdivタグを生成
      ctaWrap           = document.createElement("div");
      //pタグに削除用のidを設定
      ctaWrap.id        = 'toc-cta';
      //aタグにCTAの中身を挿入
      ctaWrap.innerHTML = cta;

      //次のh要素を取得
      nextH = document.getElementById(nextSpanId).parentNode;
      //CTAを目次から移動後のh要素の次のh要素の前に表示させる

      nextH.parentNode.insertBefore(ctaWrap , nextH);
    }

  }

}

}());

CTAを設定するのは以下の部分です。


//CTAの中身を設定(※現在の記述は例)
var cta     = '<a href="">目次と連動しているCTA</a>';
//スマホなど小さなブラウザサイズ時に出力するCTA。空欄の場合は通常のCTAを表示
var ctaSmt  = '<a href="">目次と連動しているCTA(スマホ用)</a>';

上記は例示用に書いていますので、実際に使う際はここを書き換える必要があります。

なお、小さなブラウザサイズの境目は以下の場所で設定可能です。


//スマホ用の余白を適用するためのトリガーとなる画面サイズを指定。入力しない場合はデフォルトの375pxが適用される。
//※オプション
var display = "";

この変数は、目次から移動後の余白をスマホ用に切り替えるかどうかの境目設定用だったのですが、それをそのまま流用する形で作っています。

補足

以下は動作などに関する補足です。

「移動した先のh要素の次のh要素」の取得方法

目的を達成するために必要だったのが「移動した先のh要素の次のh要素」の取得でしたが、今回は目次のhref属性を利用しています。

具体的には以下のような状態です。

  • 目次のaタグのhref属性は、移動先のh要素のIDと同じ
  • クリックした目次の、次の目次のhref属性の値を取得すれば、それを「移動した先のh要素の次のh要素」の特定に使える

当初は移動後のh要素から辿る方法で構築していたのですが、上記の方法に気がついたので変更しました。

最後の目次をクリックした場合

最後の目次をクリックした場合、そのままでは存在しないh要素を取得しようとしてエラーが発生します。

そこで、最後の目次をクリックした場合はCTAを表示させないようにしています。

HTMLの状態と作成したコードの指定方法次第ですが、目次の作成範囲外のh2を対象にすれば、最後の目次をクリックした場合でもCTAを表示させることは可能だと思います。

複数のh要素対応

時間と気力の関係で、複数のh要素を使うタイプでも動くのかは試していません。

ただ、目次のhrefspanのid属性の関係上、仕組み的には動くのではと思うので、エラーがでても直せる範囲ではないかと考えています。

参考URL

参考にしたサイトは以下の通りです。

結び

当ページ自体がサンプルのためやや無駄な部分が多いですが、とりあえず上記コードで動くことは確認しました。

なお、現在テストサイトで実験中ですが、少なくともこの仕組みを導入した途端に劇的な変化が起こる、という期待は全くできない状態です。

CTAの種類や状態で変化があるかもしれないため調査は続行していますが、望み薄な印象です。

Googleにはどう見えるか

クローラーは目次のリンクを動作させないと思いますので、この記事のCTAは認識しないはずです。

Fetch as Googleのレンダリングで確認したところ、表示されていないことも確認しています。

つまりこの記事のコードでで出力した要素は、隠しテキストと同種のよろしくないものだと判断される可能性があるのでは、と懸念しています。

そのためこうして作って記事にしてみたものの、実際に利用するのはあまりお勧めできないかなと思います。

0人がこの記事を評価

役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。

連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。

コメント欄