ページの内容が多くなった場合などに、各見出しタグ(h1~h6)へのリンクのリストを jQuery を使って動的に生成する方法。
$('<div><ul id="index_list"></ul></div>').prependTo('div.single_content'); $(':header').each(function(n) { for(var i = 3; i <= 5; i++) { var elem = 'h' + i; if($(this).is(elem)){ var idValue = elem + n; $(this).attr({id: idValue}); var li = '<li><a href="#' + idValue + '">' + $(this).text() + '</a></li>'; $('ul#index_list').append(li); } } });
上記のままだと、全てのページの全ての見出し要素に対してリンクリストを生成してしまうので、リンクリストを生成したい見出し要素にクラス「index」を指定している場合だけリンクリストを生成するようにする。(下の例の1行目)
また、下記の例では
if($(':header').is('.index')) { $('<div id="index_area"><ul id="index_list"></ul></div>').prependTo('div.content'); $('div.content :header').each(function(n) { for(var i = 3; i <= 5; i++) { var elem = 'h' + i; if($(this).is(elem)){ var idValue = elem + n; $(this).attr({id: idValue}); var li = '<li class="' + elem + '"><a href="#' + idValue + '">' + $(this).text() + '</a></li>'; $('ul#index_list').append(li); } } }); $('h3').after('<p style="float:right;"><a href="#">back to top</a></p>'); }
以下はさらにオプションを追加して、関数にした例で、パラメータに何も指定しなければ、デフォルトの値で表示される。
function show_index_link(my_options) { var settings = $.extend({ check_attr : '.h_index', //h1~h6 のいずれかの要素にこの属性(クラス名)が付いている場合にのみリストを表示 div_id : 'h_index_area', //リストを表示するエリア(div 要素)の id ul_id: 'index_list', //リスト(ul 要素)の id prependToMe : 'div#indexlistarea', //リストを表示する場所を一意に示す要素名 area : 'div.content', //h1~h6 要素を抽出する対象エリア begin : 3, //h1~h6 のどの要素を対象とするか(小さい方の数値を指定) end : 5, //h1~h6 のどの要素を対象とするか(大きい方の数値を指定) use_bakcto : false, //先頭に戻るリンクを表示するかどうか backtoElem: 'h3', //「先頭に戻るリンク」を配置(append)する要素名 backtoTitle: 'back to top', //「先頭に戻るリンク」のテキスト backtoStyle: '' //「先頭に戻るリンク」のスタイル }, my_options || {}); if($(':header').is(settings.check_attr)) { $('<div id="' + settings.div_id + '"><ul id="' + settings.ul_id + '"></ul></div>').prependTo(settings.prependToMe); $(settings.area + ' :header').each(function(n) { for(var i = settings.begin; i <= settings.end ; i++) { var elem = 'h' + i; if($(this).is(elem)){ var idValue = elem + '_index_' + n; $(this).attr({id: idValue}); var li = '<li class="' + elem + '_class"><a href="#' + idValue + '">' + $(this).text() + '</a></li>'; $('ul#' + settings.ul_id).append(li); } } }); } if(settings.use_backto) { $(settings.backtoElem).before('<p style="' + settings.backtoStyle + '"><a href="#">' + settings.backtoTitle + '</a></p>'); } show_index_link({use_backto: true, backtoStyle:'float:right; font-size: 12px; font-size: 1.2rem; font-weight: normal', backtoTitle: 'トップへ'}); }
特定の見出しを除外したい場合は、その見出しに適当なクラスを付与して、それを除外するようにする。
「noindex」というクラスを持つ見出しを除外する場合、.not() を使って18行目を以下のようにする。
$(settings.area + ' :header').not('.noindex').each(function(n) {
HTML
<div id="indexlistarea"></div> <h3 class="h_index">概要</h3> ・・・省略・・・ <h3>オプションを追加して、関数にする</h3> ・・・省略・・・ <h3>アニメーションで表示</h3>
リンク先へのジャンプをアニメーションで表示。これらは show_index_link() 関数を実行した後に記述しないと機能しない(その前ではまだ要素が生成されていないため)。
var h_index_link$ = $('#index_list li a'); h_index_link$.click(function () { var target_element_id = $(this).attr('href'); var scrolltopvalue = $(target_element_id).offset().top; $('body,html').animate({ scrollTop: scrolltopvalue - 100 }, 300 + scrolltopvalue / 25); return false; });
‘back to top’(先頭に戻るリンク)をクリックした場合のアニメーション。
var back_to_tops$ = $('a').filter(function() { return $(this).attr("href") == "#"; }).not('a.dropdown-toggle'); back_to_tops$.click(function () { $('body,html').animate({ scrollTop: 0 }, 500); return false; });
(追記)
.filter() を使わなくても「$(“[属性名=’値’]”)」というセレクタを使えば簡単に記述できる。
$('a[href=#]').not('a.dropdown-toggle').click(function () { $('body,html').animate({ scrollTop: 0 }, 500); return false; });
また、リンク先とページ先頭へのアニメーションの両方に対応するには以下のように記述できる。
(詳細は「ページ内リンクへアニメーションで移動」)
jQuery(document).ready(function($) { $('a[href^=#]').not('a.dropdown-toggle').click(function(){ var hrefval= $(this).attr('href'); var positiontop; var speed; if(hrefval == "#") { positiontop = 0; speed = 200 + $(this).offset().top /30; }else{ var targetelement = $(hrefval); positiontop = targetelement.offset().top -120; speed = 200 + (positiontop + 120) / 30; } $('body,html').animate({ scrollTop: positiontop }, speed); return false; }); });
記事が長くなると、先頭のリンクのリスト(インデックス)に戻るのは面倒なので、ある程度以上スクロールするとサイドバーに固定(fixed)して表示するようにする。
var sidebar_height = $('#sidebar').height(); var sidebar_width = $('#sidebar').width(); var is_h_index_visible = false; var h_index$ = $('#h_index_area'); //リンクのリストのセレクタ //インデックスリンクが存在すればそのコピーを作成してサイドバーに追加 if(h_index$.length > 0) { var h_index_clone$ = $('#h_index_area').clone().appendTo('#sidebar .container').css({ display: 'none', width: sidebar_width, padding: '0 10px' }).attr('id', '#h_index_area_side'); //idが重複するので変更 h_index_clone$.find('ul').attr('id', 'index_list_side'); //idが重複するので変更 $('#index_list_side').css({ listStyle: 'none', lineHeight: '1.5em' }); $('#index_list_side li.h4_class').css('margin-left', '1em'); $('#index_list_side li.h5_class').css('margin-left', '2em'); $('#index_list_side li.h6_class').css('margin-left', '3em'); var h_index_height = h_index$.height(); var h_index_offsetTop = h_index$.offset().top; var h_index_li$ = h_index_clone$.find('li'); var h_index_a$ = h_index_li$.find('a'); }
スクロールしてメインの「リンクのリスト」が見えなくなったらサイドバーに表示するようにする。
var index_headers$ = $("[id^='h'][id*='_index_']:header"); var index_headers_length = index_headers$.length; var window$ = $(window); window$.scroll(function () { var this$ = $(this); if(h_index$.length > 0 && this$.scrollTop() > h_index_height + h_index_offsetTop && this$.scrollTop() > sidebar_height) { if(!is_h_index_visible && window$.width() > 768) { h_index_clone$.fadeIn(1000); is_h_index_visible = true; } h_index_clone$.css({ top: 100, position: 'fixed' }); }else{ if(is_h_index_visible) { h_index_clone$.fadeOut(200); is_h_index_visible = false; } } });
6行目では、単純にサイドバーの高さを使用しているが、高さが可変の要素(スライドダウンする等)がある場合は、それらの要素の高さを加えるか、可能であればサイドバー内の最後の要素のポジションとその要素の高さを加えた値を使用するといいかもしれない。
//最後の要素のポジション var last_elem_pos; if($('.logo_icon').length > 0) { last_elem_pos = $('.logo_icon').position().top + $('.logo_icon').outerHeight(true); } //サイドバーの高さではなく最後の要素のポジションを使用 if(h_index$.length > 0 && this$.scrollTop() > h_index_height + h_index_offsetTop && this$.scrollTop() > last_elem_pos) { if(!is_h_index_visible && window$.width() > 768) { h_index_clone$.fadeIn(1000); is_h_index_visible = true; } h_index_clone$.css({ top: 100, position: 'fixed' }); }else{ if(is_h_index_visible) { h_index_clone$.fadeOut(200); is_h_index_visible = false; } }
スクロール量により、現在どの部分が表示されているかをわかるように、リンクのリストの表示を変える。(21行目から48行目)
var window$ = $(window); window$.scroll(function () { var this$ = $(this); //サイドバーにインデックスを表示(前述) if(h_index$.length > 0 && this$.scrollTop() > h_index_height + h_index_offsetTop && this$.scrollTop() > sidebar_height) { //省略 } //現在表示されているコンテンツのサイドバーのインデックスの表示を変更 index_headers$.each(function(index) { var this$ = $(this); var id = this$.attr('id'); var offset_top = document.getElementById(id).offsetTop; var content_height = 0; //next_id,next_offset_topには最後の場合の値を入れておく(その他の場合は次の if 文で) var next_id = index_headers$.eq(index_headers_length - 1).attr('id'); var next_offset_top = index_headers$.eq(index_headers_length - 1).offset().top; if(index < index_headers_length - 1) { content_height = index_headers$.eq(index + 1).offset().top - offset_top; next_id = index_headers$.eq(index + 1).attr('id'); next_offset_top = document.getElementById(next_id).offsetTop; } var window_st = window$.scrollTop(); if(index == index_headers_length - 1) { if(window_st > offset_top ) { h_index_a$.eq(index).addClass('selected'); }else{ h_index_a$.eq(index).removeClass('selected'); } }else{ if(window_st > offset_top -50 && window_st < next_offset_top -50) { h_index_a$.eq(index).addClass('selected'); }else{ h_index_a$.eq(index).removeClass('selected'); } } }); }); <!-- comment only -->
最初は以下のように記述したが、リンクのリストの数が増えるにつれ、位置の誤差が大きくなっていって、思ったとおりにならなかった失敗例。(また、下記では get() を使用しているが、eq() の方が良い)
//間違った方法(失敗例) index_headers$.each(function(index) { var this$ = $(this); var id = this$.attr('id'); var offset_top = document.getElementById(id).offsetTop; //var offset_top_jq = this$.offset().top; var content_height = 0; var next_id = $(index_headers$.get(index_headers_length - 1)).attr('id'); var next_offset_top = $(index_headers$.get(index_headers_length - 1)).offset().top; if(index < index_headers_length - 1) { content_height = $(index_headers$.get(index + 1)).offset().top - offset_top; next_id = $(index_headers$.get(index + 1)).attr('id'); next_offset_top = document.getElementById(next_id).offsetTop; } //console.log(this$.text() + Math.floor(content_height) +' top: ' + Math.floor(offset_top) + ' jq: ' + Math.floor(offset_top_jq) + ' diff: ' + Math.floor(offset_top_jq - offset_top)); //console.log(this$.text() + ' 高さ: ' + Math.floor(content_height) +' top: ' + Math.floor(offset_top) + ' next top: ' + Math.floor(content_height + offset_top ) + ' next_offset_top: ' +Math.floor(next_offset_top) ); window$.scroll(function() { var window_st = window$.scrollTop(); //var window_height =window$.height(); if(index == index_headers_length - 1) { if(window_st > offset_top ) { $(h_index_a$.get(index)).css('color', '#333280'); //default color: #2288cc }else{ $(h_index_a$.get(index)).css('color', '#2288cc'); } }else{ if(window_st > offset_top && window_st < next_offset_top) { $(h_index_a$.get(index)).css('color', '#333280'); }else{ $(h_index_a$.get(index)).css('color', '#2288cc'); } } }); }); <!-- comment only -->
以下はレスポンシブにしていて幅が「768px」より小さい場合は、サイドバーが一番下に来る場合。そのためその場合は非表示にする。また、逆に小さい幅から「768px」以上になった場合は表示するようにする例。
リサイズイベントは多数発生するので「setTimeout」を利用(詳細は「リサイズ $(window).resize が終了した時点で実行」)
var timer = false; $(window).resize(function(){ if(h_index$.length > 0) { if (timer !== false) { clearTimeout(timer); } timer = setTimeout(function() { if(window$.width() >= 768 && window$.scrollTop() > h_index_height + h_index_offsetTop && window$.scrollTop() > sidebar_height) { sidebar_width = $('#sidebar').width(); h_index_clone$.css({ display: 'block', width: sidebar_width, padding: '0 10px' }); is_h_index_visible = true; }else{ h_index_clone$.css('display', 'none'); is_h_index_visible = false; } }, 200); } });
以下はこのサイトの場合の例(横幅は960pxに変更)
var sidebar_height = $('#sidebar').outerHeight(true); var sidebar_width = $('#sidebar').width(); var is_h_index_visible = false; var h_index$ = $('#h_index_area'); //インデックスリンクが存在すればそのコピーを作成してサイドバーに追加 if(h_index$.length > 0) { var h_index_clone$ = $('#h_index_area').clone().appendTo('#sidebar .container').css({ display: 'none', width: sidebar_width, padding: '0 10px' }).attr('id', 'h_index_area_side'); //idが重複するので変更 h_index_clone$.find('ul').attr('id', 'index_list_side'); //idが重複するので変更 $('#index_list_side').css({ listStyle: 'none', lineHeight: '1.5em' }); if(h_index_clone$.height() > $(window).height() -70) { $('#index_list_side li.h4_class').css('display', 'none'); $('#index_list_side li.h5_class').css('display', 'none'); $('#index_list_side li.h6_class').css('display', 'none'); } $('#index_list_side li.h4_class').css('margin-left', '1em'); $('#index_list_side li.h5_class').css('margin-left', '2em'); $('#index_list_side li.h6_class').css('margin-left', '3em'); var h_index_height = h_index$.height(); var h_index_offsetTop = h_index$.offset().top; var h_index_li$ = h_index_clone$.find('li'); var h_index_a$ = h_index_li$.find('a'); } var index_headers$ = $("[id^='h'][id*='_index_']:header"); var index_headers_length = index_headers$.length; window$.scroll(function () { var this$ = $(this); var last_elem_pos; if($('.logo_icon').length > 0) { last_elem_pos = $('.logo_icon').position().top + $('.logo_icon').outerHeight(true); } //サイドバーにインデックスを表示 if(h_index$.length > 0 && this$.scrollTop() > h_index_height + h_index_offsetTop && this$.scrollTop() > last_elem_pos) { if(!is_h_index_visible && window$.width() > 960) { h_index_clone$.fadeIn(1000); is_h_index_visible = true; } h_index_clone$.css({ top: 50, position: 'fixed' }); }else{ if(is_h_index_visible) { h_index_clone$.fadeOut(200); is_h_index_visible = false; } } //現在表示されているコンテンツのサイドバーのインデックスの表示を変更 index_headers$.each(function(index) { var this$ = $(this); var id = this$.attr('id'); var offset_top = document.getElementById(id).offsetTop; var content_height = 0; //next_id,next_offset_topには最後の場合の値を入れておく/その他の場合は次のif文で var next_id = index_headers$.eq(index_headers_length - 1).attr('id'); var next_offset_top = index_headers$.eq(index_headers_length - 1).offset().top; if(index < index_headers_length - 1) { content_height = index_headers$.eq(index + 1).offset().top - offset_top; next_id = index_headers$.eq(index + 1).attr('id'); next_offset_top = document.getElementById(next_id).offsetTop; } var window_st = window$.scrollTop(); if(index == index_headers_length - 1) { if(window_st > offset_top ) { h_index_a$.eq(index).addClass('selected'); }else{ h_index_a$.eq(index).removeClass('selected'); } }else{ if(window_st > offset_top -50 && window_st < next_offset_top -50) { h_index_a$.eq(index).addClass('selected'); }else{ h_index_a$.eq(index).removeClass('selected'); } } }); }); var timer = false; $(window).resize(function(){ if (timer !== false) { clearTimeout(timer); } timer = setTimeout(function() { if(h_index$.length > 0) { if(window$.width() >= 960 && window$.scrollTop() > h_index_height + h_index_offsetTop && window$.scrollTop() > sidebar_height) { sidebar_width = $('#sidebar').width(); h_index_clone$.css({ display: 'block', width: sidebar_width, padding: '0 10px' }); is_h_index_visible = true; }else{ h_index_clone$.css('display', 'none'); is_h_index_visible = false; } } }, 200); });