Create a gist now

Instantly share code, notes, and snippets.

@twilyze / readme.md
Last active Mar 31, 2017

What would you like to do?
サイドバーに現在位置を表示して追尾する目次【レスポンシブ対応などテスト】 http://twilyze.hatenablog.jp/entry/hatena-blog-custom4

主な変更点(現状)

まだ決定してないところが多いのでそのつもりで(編集途中のファイルを抜き出してるので上手く動かないかも)

追加

  • レスポンシブ対応
  • アーカイブ・トップページでページ内の記事一覧を表示
  • ウィンドウの縦幅よりも大きくなったらスクロールバーを表示する
  • モジュールタイトルを記事タイトルにする機能追加
  • モジュールタイトルを記事トップへのリンクに(仮)
  • タイトル要素(モジュール名)があれば削除してから追加
  • 横スクロールにくっついてこないように
  • モジュールを最後以外にセットした時の対応
  • 設定変更しやすいように上の方にまとめた

変更

  • CSS .sectionList 削除
  • CSS sectionListSide から sidebar-toc に変更
  • ページ下に固定する時の位置を変更
    • container-inner → main-inner

TODO

  • スクリプト読み込み(実行)のタイミングを変える
  • jQuery無しで作る
  • Passive Event Listeners調べる
  • その他細々
/* 目次(サイドバー) */
#box2-inner {
position: relative;
}
#sidebar-toc {
overflow-y: auto;
}
#sidebar-toc ol {
padding: 0px;
margin: 0px;
list-style-type: none;
}
#sidebar-toc .chapter {
padding-left: 10px;
}
#sidebar-toc .toc-anchor {
color: #7d9ab7;
padding: 2px 0px 2px 4px;
display: block;
}
#sidebar-toc .toc-anchor:hover {
color: #263f5a;
background-color: #efefef;
text-decoration: none;
}
#sidebar-toc .current {
background-color: #efefef;
}
#sidebar-toc::-webkit-scrollbar {
width: 8px;
background: #f1f1f1;
}
#sidebar-toc::-webkit-scrollbar-button {
display: none;
}
#sidebar-toc::-webkit-scrollbar-thumb {
background: #c1c1c1;
}
/* test_none_jquery.js */
#sidebar-toc-module.fade-in {
animation: fadeIn 200ms;
}
@keyframes fadeIn {
0% {opacity: 0}
100% {opacity: 1}
}
<!-- 追尾する目次 ver3 test -->
<div id='sidebar-toc'></div>
<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.slim.min.js'></script>
<script>
//TODO: タイミング変えたほうがいい気がする
$(window).on('load', function() {
'use strict';
//----------------------
/* ↓設定ここから↓
ENTRY:記事, INDEX:トップ, ARCHIVE:アーカイブ
*/
// 各ページのモジュールタイトル('':空文字で非表示)
// ENTRYは'entry-title'にすると記事タイトル表示
var MODULE_TITLE_ENTRY = '目次';
var MODULE_TITLE_INDEX = 'このページの記事一覧';
var MODULE_TITLE_ARCHIVE = 'このページの記事一覧';
// 各ページの動作オンオフ(true, false)
var PAGE_ENTRY = true;
var PAGE_INDEX = true;
var PAGE_ARCHIVE = true;
// 目次を固定した時の余白(px)
var MARGIN_TOP = 10;
// 処理の開始を遅らせる時間(ミリ秒)
var DELAY_TIME = 0;
// スムーズスクロールにかける時間(ミリ秒)
var SMOOTH_SCROLL_TIME = 400;
// スムーズスクロールのイージング('swing', 'linear')
var SMOOTH_SCROLL_EASE = 'swing';
// 表示する見出しタグ(順番注意)
var HEADLINE_QUERY = ['h3','h4','h5'];
// 見出しがn個以下なら表示しない(0以上)
var HEADLINE_MIN = 1;
// 現在位置取得時に余裕をもたせる(px)
var SCROLL_MARGIN = 50;
// 目次内のスクロールを自動追尾(true, false)
var TOC_INSIDE_SCROLL = true;
// ページ内リンクをブラウザ履歴に追加(true, false)
var HISTORY_PUSH = true;
// ↑設定ここまで↑
//----------------------
(function() {
var $sidebarToc = $('#sidebar-toc');
var $sidebarTocModule = $sidebarToc.parent().parent();
var sidebarTocModule = $sidebarTocModule[0];
var bodyClassList = document.body.classList;
var IS_PAGE_ENTRY = PAGE_ENTRY ? bodyClassList.contains('page-entry') : false;
var IS_PAGE_INDEX = PAGE_INDEX ? bodyClassList.contains('page-index') : false;
var IS_PAGE_ARCHIVE = PAGE_ARCHIVE ? bodyClassList.contains('page-archive') : false;
// トップ・記事・アーカイブ・カテゴリページの時だけ
if ( !(IS_PAGE_ENTRY || IS_PAGE_INDEX || IS_PAGE_ARCHIVE) ) {
$sidebarTocModule.hide();
return;
}
console.log('%c---sidebar toc--- DELAY_TIME:' + DELAY_TIME, 'color:blue');
console.time('sidebarToc render');
var elMainInner = document.getElementById('main-inner');
var elContentList;
// 表示する見出しの一覧とモジュールタイトル取得
if (IS_PAGE_ENTRY)
elContentList = elMainInner.querySelectorAll(HEADLINE_QUERY.join());
else {
elContentList = (function() {
var elems = document.getElementsByClassName('entry-title'), ret = [];
for(var i=0, len=elems.length; i < len; i++)
ret.push(elems[i].children[0]);
return ret;
}());
}
// 見出しがn個以下なら目次を表示しない
if (elContentList.length <= HEADLINE_MIN) {
$sidebarTocModule.hide();
console.log('---sidebar toc hide---');
return;
}
//----------------------
var CLASS_HMT = 'hatena-module-title';
var CLASS_ENTRY = 'entry-title';
var CLASS_CURRENT = 'current';
var CLASS_TRACKING = 'tracking';
var STYLE_DEFAULT = {position: 'static'};
//----------------------
var elHatenaToc = elMainInner.getElementsByClassName('table-of-contents');
var hatenaTocId = [];
var hatenaTocFlg = false;
var list = [];
var currentLevel = 0;
// 目次記法を使ってる時はそちらからIDを取得
if (IS_PAGE_ENTRY && elHatenaToc.length) {
var elHatenaTocAnchor = elHatenaToc[0].getElementsByTagName('a');
var sliceIndex = elHatenaTocAnchor[0].href.split('#')[0].length + 1;
for(var i=0, len=elHatenaTocAnchor.length; i<len; i++) {
hatenaTocId.push(elHatenaTocAnchor[i].href.slice(sliceIndex));
elHatenaTocAnchor[i].addEventListener('click', smoothScroll);
}
hatenaTocFlg = true;
}
// タグの設定
$.each(elContentList, function(i, elem) {
var idName = 'section' + i;
var level = 0;
// a要素作成
if (hatenaTocFlg)
idName = hatenaTocId[i];
else
$(elem).attr('id', idName);
list.push('<li><a href="#' + idName + '" class="toc-anchor">' + $(elem).text() + '</a>');
// 段落作成
for (var j=1, len=HEADLINE_QUERY.length; j<len; j++) {
if (elem.nodeName.toLowerCase() === HEADLINE_QUERY[j]) {
level = j;
break;
}
}
while (currentLevel < level) {
list[i] = '<ol class="chapter">' + list[i];
currentLevel++;
}
while (currentLevel > level) {
list[i] = '</ol></li>' + list[i];
currentLevel--;
}
});
// モジュールタイトルの追加
var sidebarTocTitle;
if (IS_PAGE_ENTRY && MODULE_TITLE_ENTRY !== '') {
//document.getElementsByClassName(CLASS_ENTRY)[0].children[0].textContent
if (MODULE_TITLE_ENTRY === CLASS_ENTRY)
sidebarTocTitle = $('.' + CLASS_ENTRY).children()[0].textContent;
else
sidebarTocTitle = MODULE_TITLE_ENTRY;
}
else if (IS_PAGE_INDEX && MODULE_TITLE_INDEX !== '')
sidebarTocTitle = MODULE_TITLE_INDEX;
else if (IS_PAGE_ARCHIVE && MODULE_TITLE_ARCHIVE !== '')
sidebarTocTitle = MODULE_TITLE_ARCHIVE;
$sidebarTocModule.children('.' + CLASS_HMT).remove();
if (sidebarTocTitle)
$sidebarTocModule.prepend('<div class="' + CLASS_HMT + '"><a href="#main">' + sidebarTocTitle + '</a></div>');
// 目次本体の追加
$sidebarToc.append('<ol>' + list.join('') + '</ol>');
$sidebarTocModule.attr('id', 'sidebar-toc-module');
console.log('%c--add toc--', 'color:blue');
// a要素一覧の取得とスムーズスクロールの設定
var $sidebarTocAnchor = $('a', $sidebarToc.children());
$sidebarTocAnchor.on('click', smoothScroll);
//----------------------
// スクロール用
var $win = $(window);
var $box2 = $('#box2');
var $mainInner = $(elMainInner);
var $tocTitle = $sidebarTocModule.children('.' + CLASS_HMT).eq(0);
var $gh = $('#globalheader-container');
var ghHeight = $gh.outerHeight();
var MODULE_OUTSIDE_HEIGHT = $sidebarTocModule.outerHeight() - $sidebarTocModule.height();
var MODULE_STATIC_HEIGHT = $sidebarTocModule.outerHeight(true);
var TOC_LAST_INDEX = list.length - 1;
var headlineTopList = [];
var scrollRange, scrollFixed, scrollAbsolute, move, tracking;
var ghFixedHeight, marginComp, box2Top, leftMargin;
var tocMaxHeight, tocScrollbar, tocScrollHeight;
var tocAHeight = $sidebarTocAnchor.eq(0).outerHeight();
console.log('tocAHeight:' + tocAHeight);
// 目次をサイドバーの最後に設置してる時はその一つ前、それ以外の時は最後のモジュールを取得
var $lastModule = $('#box2-inner').children().last();
var IS_LAST_MODULE_TOC = $lastModule[0] === $sidebarTocModule[0];
var $guideModule = IS_LAST_MODULE_TOC ? $lastModule.prev() : $lastModule;
//----------------------
//TODO
setTimeout(function() {
// ページ表示時に一度実行しておく
setTrackingPoint();
// ウィンドウのリサイズ操作が終わった時に処理する
var timer;
$win.on('resize.toc', function() {
clearTimeout(timer);
timer = setTimeout(setTrackingPoint, 200);
});
//
console.timeEnd('sidebarToc render');
}, DELAY_TIME);
//----------------------
// 関数式
// 現在位置を表示するためのクラス設定とスクロール処理
var setCurrent = (function() {
var current = -1;
return function(i) {
if (i !== current) {
$sidebarTocAnchor.eq(current).removeClass(CLASS_CURRENT);
$sidebarTocAnchor.eq(i).addClass(CLASS_CURRENT);
if (TOC_INSIDE_SCROLL && tocScrollbar && tracking) {
var currentTop;
if (TOC_LAST_INDEX === i)
currentTop = tocScrollHeight;
else
currentTop = $sidebarTocAnchor.eq(i)[0].getBoundingClientRect().top + $sidebarToc.scrollTop();
$sidebarToc.scrollTop(currentTop - tocMaxHeight);
}
current = i;
}
};
}());
// モジュール固定位置が変わったかチェック
var checkPosition = (function() {
var current = 0;
return function(i, callback) {
if (i === void 0)
return current;
if (i !== current) {
if (callback)
callback(current);
current = i;
return true;
}
};
}());
// スクロールイベントの追加と削除
var setScrollEvent = (function() {
var current = false;
return function(b) {
var event = 'scroll.toc';
if (b !== current) {
if (current)
$win.off(event);
else
$win.on(event, updateScroll);
current = b;
}
};
}());
//----------------------
// 関数宣言
// スムーズスクロールと履歴を設定
function smoothScroll(e) {
var hash = e.currentTarget.hash;
var top = Math.min($(hash).offset().top - ghFixedHeight, scrollRange);
$('html,body').animate({scrollTop: top}, SMOOTH_SCROLL_TIME, SMOOTH_SCROLL_EASE);
if (HISTORY_PUSH) window.history.pushState(null, hash, hash);
return false;
}
// モジュール内スクロールバー表示を設定
function setTocScrollBar(bool) {
$sidebarToc.css({'max-height': bool ? tocMaxHeight : ''});
tocScrollbar = bool;
}
// 目次モジュールの設定
function setTocModuleOption(style, bool) {
$sidebarTocModule.css(style);
$sidebarTocModule.toggleClass(CLASS_TRACKING, bool);
setTocScrollBar(bool);
tracking = bool;
}
// 追尾処理に必要な値を設定
function setTrackingPoint() {
var winHeight = window.innerHeight;
var scrollbarX = winHeight - document.documentElement.clientHeight;
console.log('scrollbarX:' + scrollbarX);
scrollRange = Math.max(document.documentElement.scrollHeight - winHeight, 0);
ghFixedHeight = $gh.css('position') === 'fixed' ? ghHeight : 0;
marginComp = ghFixedHeight + MARGIN_TOP;
leftMargin = $guideModule.offset().left;
tocScrollHeight = $sidebarToc[0].scrollHeight;
tocMaxHeight = winHeight - scrollbarX - marginComp - MODULE_OUTSIDE_HEIGHT - $tocTitle.outerHeight(true);
// 各見出しの位置を保存
$.each(elContentList, function(i, elem) {
headlineTopList[i] = $(elem).offset().top - ghFixedHeight;
});
// 横幅を合わせる
$sidebarTocModule.css({'width': $guideModule.width()});
// サイドバーが横に表示されていない時
// if (window.matchMedia('(min-width:1000px)').matches) {
if ($box2.css('float') === 'none') {
console.log('-scrollEvent:OFF-');
setScrollEvent(false);
checkPosition(0);
setTocModuleOption(STYLE_DEFAULT, false);
$sidebarTocAnchor.removeClass(CLASS_CURRENT);
}
// サイドバーより記事の方が小さい時
else if ($box2.outerHeight() > $('#main').outerHeight()) {
console.log('-scrollEvent:ON- move:OFF');
setScrollEvent(true);
move = false;
checkPosition(0);
setTocModuleOption(STYLE_DEFAULT, false);
updateScroll();
}
else {
console.log('-scrollEvent:ON- move:ON');
setScrollEvent(true);
move = true;
// ウィンドウに固定する位置
scrollFixed = $guideModule.offset().top + $guideModule.outerHeight(true) - ghFixedHeight;
var now = checkPosition() !== 0; // 目次モジュールの[現在]位置が初期位置以外か
if (IS_LAST_MODULE_TOC)
scrollFixed -= MARGIN_TOP; // 目次モジュールの[設置]位置が最後の時
else if (now)
scrollFixed += MODULE_STATIC_HEIGHT;
setTocScrollBar(now);
// 下までスクロールした時にページへ固定する位置
var tocModuleMaxHeight = Math.min(winHeight - marginComp, $sidebarTocModule.outerHeight());
scrollAbsolute = $mainInner.offset().top + $mainInner.outerHeight() - tocModuleMaxHeight - marginComp;
box2Top = $box2.offset().top;
console.log('scrollFixed:' + scrollFixed);
console.log('scrollAbsolute:' + scrollAbsolute);
updateScroll();
}
}
// 現在のスクロール位置をもとに表示を更新
function updateScroll() {
var scrollTop = window.pageYOffset;
var scrollLeft = window.pageXOffset;
// 現在位置のクラス設定
if (scrollTop <= SCROLL_MARGIN) {
setCurrent(0);
}
else if ((scrollRange - scrollTop) <= SCROLL_MARGIN) {
setCurrent(TOC_LAST_INDEX);
}
else {
for (var i = TOC_LAST_INDEX; i >= 0; i--) {
if (scrollTop > headlineTopList[i] - SCROLL_MARGIN) {
setCurrent(i);
break;
}
}
}
// モジュールを追従させる
if (move) {
if (scrollAbsolute < scrollTop) {
// ページに固定(下)
if (checkPosition(2)) {
setTocModuleOption({
position: 'absolute', left: '',
top: scrollAbsolute - box2Top + marginComp + 'px'
}, true);
}
}
else if (scrollFixed < scrollTop) {
// ウィンドウに固定
checkPosition(1, function(current) {
var style = {position: 'fixed', top: marginComp + 'px'};
// サイドバーの最後以外に設置されていて直前が初期位置の時はフェードインさせる
var fade = !IS_LAST_MODULE_TOC && current === 0;
if (fade) style.display = 'none';
setTocModuleOption(style, true);
if (fade) $sidebarTocModule.fadeIn(200);
});
}
else {
// 初期位置
if (checkPosition(0))
setTocModuleOption(STYLE_DEFAULT, false);
}
}
// 横方向のスクロールに合わせる
if (checkPosition() === 1)
sidebarTocModule.style.left = leftMargin - scrollLeft + 'px';
}
}());
});
</script>
<!-- 追尾する目次 ver3 jQuery無しテスト -->
<nav id='sidebar-toc'></nav>
<script>
(function() {
'use strict';
//----------------------
/* ↓設定ここから↓
ENTRY:記事, INDEX:トップ, ARCHIVE:アーカイブ
*/
// 各ページのモジュールタイトル('':空文字で非表示)
// ENTRYは'entry-title'にすると記事タイトル表示
var MODULE_TITLE_ENTRY = '目次';
var MODULE_TITLE_INDEX = 'このページの記事一覧';
var MODULE_TITLE_ARCHIVE = 'このページの記事一覧';
// 各ページの動作オンオフ(true, false)
var PAGE_ENTRY = true;
var PAGE_INDEX = true;
var PAGE_ARCHIVE = true;
// 目次を固定した時の余白(px)
var MARGIN_TOP = 10;
// 追尾処理の開始を遅らせる時間(ミリ秒)
var DELAY_TIME = 200;
// スムーズスクロールにかける時間(ミリ秒)
var SMOOTH_SCROLL_TIME = 400;
// スムーズスクロールのイージング('swing', 'linear')
var SMOOTH_SCROLL_EASE = 'swing';
// 表示する見出しタグ(順番注意)
var HEADLINE_QUERY = ['h3','h4','h5'];
// 見出しがn個以下なら表示しない(0以上)
var HEADLINE_MIN = 1;
// 現在位置取得時に余裕をもたせる(px)
var SCROLL_MARGIN = 50;
// 目次内のスクロールを自動追尾(true, false)
var TOC_INSIDE_SCROLL = true;
// ページ内リンクをブラウザ履歴に追加(true, false)
var HISTORY_PUSH = true;
// ↑設定ここまで↑
//----------------------
(function() {
var win = window;
var doc = document;
var elSidebarToc = doc.getElementById('sidebar-toc');
var elSidebarTocModule = elSidebarToc.parentNode.parentNode;
var bodyClassList = doc.body.classList;
var IS_PAGE_ENTRY = PAGE_ENTRY ? bodyClassList.contains('page-entry') : false;
var IS_PAGE_INDEX = PAGE_INDEX ? bodyClassList.contains('page-index') : false;
var IS_PAGE_ARCHIVE = PAGE_ARCHIVE ? bodyClassList.contains('page-archive') : false;
// トップ・記事・アーカイブ・カテゴリページの時だけ
if ( !(IS_PAGE_ENTRY || IS_PAGE_INDEX || IS_PAGE_ARCHIVE) ) {
elSidebarTocModule.style.display = 'none';
return;
}
console.log('%c---sidebar toc--- DELAY_TIME:' + DELAY_TIME, 'color:blue');
console.time('sidebarToc add');
console.time('sidebarToc render');
var elMainInner = doc.getElementById('main-inner');
var elContentList;
// 表示する見出しの一覧とモジュールタイトル取得
if (IS_PAGE_ENTRY)
elContentList = elMainInner.querySelectorAll(HEADLINE_QUERY.join());
else {
elContentList = (function() {
var elems = doc.getElementsByClassName('entry-title'), ret = [];
for (var i=0, len=elems.length; i < len; i++)
ret.push(elems[i].children[0]);
return ret;
}());
}
// 見出しがn個以下なら目次を表示しない
if (elContentList.length <= HEADLINE_MIN) {
elSidebarTocModule.style.display = 'none';
console.log('---sidebar toc hide---');
return;
}
//----------------------
var TITLE_ID = 'main'; // TODO setting
var CLASS_HMT = 'hatena-module-title';
var CLASS_ENTRY = 'entry-title';
var CLASS_CURRENT = 'current';
var CLASS_TRACKING = 'tracking';
var CLASS_FADE_IN = 'fade-in';
// var CLASS_ANCHOR = 'toc-anchor';
var STYLE_DEFAULT = {position: 'static'};
var SUM_MARGIN = ['marginTop', 'marginBottom'];
//----------------------
var elHatenaToc = elMainInner.getElementsByClassName('table-of-contents');
var hatenaTocId = [];
var hatenaTocFlg = false;
var list = [];
var currentLevel = 0;
// 目次記法を使ってる時はそちらからIDを取得
if (IS_PAGE_ENTRY && elHatenaToc.length) {
var elHatenaTocAnchor = elHatenaToc[0].getElementsByTagName('a');
var sliceIndex = elHatenaTocAnchor[0].href.split('#')[0].length + 1;
for (var i=0, len=elHatenaTocAnchor.length; i<len; i++) {
hatenaTocId.push(elHatenaTocAnchor[i].href.slice(sliceIndex));
elHatenaTocAnchor[i].addEventListener('click', clickEvent, false);
}
hatenaTocFlg = true;
}
// タグの設定
for (var i=0, len=elContentList.length; i<len; i++) {
var elem = elContentList[i];
var idName = 'section' + i;
var level = 0;
// a要素作成
if (hatenaTocFlg)
idName = hatenaTocId[i];
else
elem.setAttribute('id', idName);
list.push('<li><a href="#' + idName + '" class="toc-anchor">' + elem.textContent + '</a>');
// 段落作成
for (var j=1, len2=HEADLINE_QUERY.length; j<len2; j++) {
if (elem.nodeName.toLowerCase() === HEADLINE_QUERY[j]) {
level = j;
break;
}
}
while (currentLevel < level) {
list[i] = '<ol class="chapter">' + list[i];
currentLevel++;
}
while (currentLevel > level) {
list[i] = '</ol></li>' + list[i];
currentLevel--;
}
}
// モジュールタイトルの追加
var sidebarTocTitle;
if (IS_PAGE_ENTRY && MODULE_TITLE_ENTRY !== '') {
if (MODULE_TITLE_ENTRY === CLASS_ENTRY)
sidebarTocTitle = doc.getElementsByClassName(CLASS_ENTRY)[0].children[0].textContent;
else
sidebarTocTitle = MODULE_TITLE_ENTRY;
}
else if (IS_PAGE_INDEX && MODULE_TITLE_INDEX !== '')
sidebarTocTitle = MODULE_TITLE_INDEX;
else if (IS_PAGE_ARCHIVE && MODULE_TITLE_ARCHIVE !== '')
sidebarTocTitle = MODULE_TITLE_ARCHIVE;
elSidebarTocModule.removeChild(elSidebarTocModule.getElementsByClassName(CLASS_HMT)[0]);
if (sidebarTocTitle) {
var div = doc.createElement('div');
div.className = CLASS_HMT;
div.innerHTML = '<a href="#' + TITLE_ID + '">' + sidebarTocTitle + '</a>';
elSidebarTocModule.insertBefore(div, elSidebarTocModule.firstElementChild);
}
// 目次本体の追加
var ol = doc.createElement('ol');
ol.innerHTML = list.join('');
elSidebarToc.appendChild(ol);
elSidebarTocModule.setAttribute('id', 'sidebar-toc-module');
console.log('%c--add toc--', 'color:blue');
// a要素一覧の取得とスムーズスクロールの設定
var elSidebarTocAnchor = elSidebarToc.getElementsByTagName('a');
for (var i=0, len=elSidebarTocAnchor.length; i<len; i++) {
elSidebarTocAnchor[i].addEventListener('click', clickEvent, false);
}
//----------------------
// スクロール用
var elMain = doc.getElementById('main');
var elBox2 = doc.getElementById('box2');
// var $mainInner = $(elMainInner);
var elTocTitle = elSidebarTocModule.getElementsByClassName(CLASS_HMT)[0];
var elGh = doc.getElementById('globalheader-container');
var GH_HEIGHT = getOuterHeight(elGh);
// var MODULE_OUTSIDE_HEIGHT = sumElementProps(elSidebarTocModule, ['paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth']);
var MODULE_OUTSIDE_HEIGHT = getOuterHeight(elSidebarTocModule) - sumElementProps(elSidebarTocModule, ['height']);
var MODULE_STATIC_HEIGHT = getOuterHeight(elSidebarTocModule, true);
var TOC_LAST_INDEX = list.length - 1;
var headlineTopList = [];
var scrollRange, scrollFixed, scrollAbsolute, move, tracking;
var ghFixedHeight, marginComp, box2Top, leftMargin;
var tocMaxHeight, tocScrollbar, tocScrollHeight;
var tocAHeight = getOuterHeight(elSidebarTocAnchor[0]);
// 目次をサイドバーの最後に設置してる時はその一つ前、それ以外の時は最後のモジュールを取得
var elLastModule = doc.getElementById('box2-inner').lastElementChild;
var IS_LAST_MODULE_TOC = elLastModule === elSidebarTocModule;
var elGuideModule = IS_LAST_MODULE_TOC ? elLastModule.previousElementSibling : elLastModule;
// console.log(elGuideModule.getBoundingClientRect().top);
console.timeEnd('sidebarToc add');
//----------------------
// イベント登録
// ウィンドウのリサイズ操作が終わった時に処理する
var timer;
win.addEventListener('resize', function() {
clearTimeout(timer);
timer = setTimeout(setTrackingPoint, 200);
}, false);
// CSSのフェード終了時
elSidebarTocModule.addEventListener('animationend', function(){
elSidebarTocModule.classList.remove(CLASS_FADE_IN);
console.log('fade animation end');
}, false);
//----------------------
//TODO
win.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
// 最初に一度実行する
setTrackingPoint();
console.timeEnd('sidebarToc render');
// TODO
win.addEventListener('load', function() {
setTrackingPoint();
}, false);
}, DELAY_TIME);
}, false);
//----------------------
// 関数式
// 現在位置を表示するためのクラス設定とスクロール処理
var setCurrent = (function() {
var current = -1;
return function(i) {
if (i !== current) {
elSidebarTocAnchor[Math.max(current, 0)].classList.remove(CLASS_CURRENT);
elSidebarTocAnchor[i].classList.add(CLASS_CURRENT);
if (TOC_INSIDE_SCROLL && tocScrollbar && tracking) {
var currentTop;
if (TOC_LAST_INDEX === i)
currentTop = tocScrollHeight;
else
currentTop = getScrollTop(elSidebarTocAnchor[i], elSidebarToc.scrollTop);
elSidebarToc.scrollTop = currentTop - tocMaxHeight;
}
current = i;
}
};
}());
// モジュール固定位置が変わったかチェック
var checkPosition = (function() {
var current = 0;
return function(i, callback) {
if (i === void 0)
return current;
if (i !== current) {
if (callback)
callback(current);
current = i;
return true;
}
};
}());
// スクロールイベントの追加と削除
var setScrollEvent = (function() {
var current = false;
return function(b) {
if (b !== current) {
var event = 'scroll';
if (current)
win.removeEventListener(event, updateScroll, false);
else
win.addEventListener(event, updateScroll, true);
current = b;
}
};
}());
// 縦方向のスムーズスクロール
var smoothScrollY = (function() {
var requestId;
var RAF = (function() {
return win.requestAnimationFrame ||
win.webkitRequestAnimationFrame ||
win.mozRequestAnimationFrame ||
win.oRequestAnimationFrame ||
function(callback) {
win.setTimeout(callback, 1000.0 / 60.0);
};
}());
var CAF = (function () {
return win.cancelAnimationFrame ||
win.webkitCancelAnimationFrame ||
win.mozCancelAnimationFrame ||
win.oCancelAnimationFrame ||
function(id) {
win.clearTimeout(id);
};
}());
var now = win.performance && performance.now;
var getTime = function() {
return (now && now.call(performance)) || (Date.now());
};
return function(target, duration) {
var elapsedTime;
var startTime = getTime();
var startY = win.pageYOffset;
var startX = win.pageXOffset;
var distance = target - startY;
CAF(requestId);
loop();
function loop() {
requestId = RAF(loop);
elapsedTime = getTime() - startTime;
if (elapsedTime >= duration) {
CAF(requestId);
win.scrollTo(startX, target);
return;
}
win.scrollTo(startX, easeInOutQuad(elapsedTime / duration) * distance + startY);
}
function easeInOutQuad(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
};
}());
//----------------------
// 関数宣言
// スムーズスクロールと履歴を設定
function clickEvent(e) {
e.preventDefault();
var hash = e.currentTarget.hash;
// console.log('hash:' + hash);
var top = getScrollTop(doc.getElementById(hash.substr(1)));
smoothScrollY(Math.min(top - ghFixedHeight, scrollRange), SMOOTH_SCROLL_TIME);
if (HISTORY_PUSH) win.history.pushState(null, hash, hash);
return false;
}
// 指定したプロパティ値を足し合わせて返す
function sumElementProps(elem, props) {
var ret = 0;
var css = getComputedStyle(elem, null);
for (var i=0, len=props.length; i<len; i++) {
var num = parseInt(css[props[i]], 10);
if (isNaN(num)) continue;
ret += num;
}
return ret;
}
// $.outerHeight()
function getOuterHeight(elem, bool) {
var ret = elem.offsetHeight;
if (bool)
ret += sumElementProps(elem, SUM_MARGIN);
return ret;
}
// $.scrollTop()
function getScrollTop(elem, scrollTop) {
return elem.getBoundingClientRect().top + (scrollTop || win.pageYOffset);
}
// モジュール内スクロールバー表示を設定
function setTocScrollBar(bool) {
elSidebarToc.style.maxHeight = bool ? tocMaxHeight + 'px' : '';
tocScrollbar = bool;
}
// 目次モジュールの設定
function setTocModuleOption(style, bool) {
for (var p in style)
elSidebarTocModule.style[p] = style[p];
elSidebarTocModule.classList.toggle(CLASS_TRACKING, bool);
setTocScrollBar(bool);
tracking = bool;
}
// 追尾処理に必要な値を設定
function setTrackingPoint() {
var winHeight = win.innerHeight;
var scrollbarX = winHeight - doc.documentElement.clientHeight;
scrollRange = Math.max(doc.documentElement.scrollHeight - winHeight, 0);
ghFixedHeight = getComputedStyle(elGh, null).position === 'fixed' ? GH_HEIGHT : 0;
marginComp = ghFixedHeight + MARGIN_TOP;
leftMargin = elGuideModule.getBoundingClientRect().left + win.pageXOffset;
tocScrollHeight = elSidebarToc.scrollHeight;
tocMaxHeight = winHeight - scrollbarX - marginComp - MODULE_OUTSIDE_HEIGHT - getOuterHeight(elTocTitle, true);
// 各見出しの位置を保存
// TODO:関数に分ける?
for (var i=0, len=elContentList.length; i<len; i++)
headlineTopList[i] = getScrollTop(elContentList[i]) - ghFixedHeight;
// 横幅を合わせる
elSidebarTocModule.style.width = getComputedStyle(elGuideModule, null).width;
// サイドバーが横に表示されていない時
// if (window.matchMedia('(min-width:1000px)').matches) {
if (getComputedStyle(elBox2, null).float === 'none') {
console.log('-scrollEvent:OFF-');
setScrollEvent(false);
checkPosition(0);
setTocModuleOption(STYLE_DEFAULT, false);
for (var i=0, len=elSidebarTocAnchor.length; i<len; i++)
elSidebarTocAnchor[i].classList.remove(CLASS_CURRENT);
}
// サイドバーより記事の方が小さい時
else if (getOuterHeight(elBox2) > getOuterHeight(elMain)) {
console.log('-scrollEvent:ON- move:OFF');
setScrollEvent(true);
move = false;
checkPosition(0);
setTocModuleOption(STYLE_DEFAULT, false);
updateScroll();
}
else {
console.log('-scrollEvent:ON- move:ON');
setScrollEvent(true);
move = true;
// ウィンドウに固定する位置
scrollFixed = getScrollTop(elGuideModule) + getOuterHeight(elGuideModule, true) - ghFixedHeight;
console.log(elGuideModule.getBoundingClientRect().top);
var now = checkPosition() !== 0; // 目次モジュールの[現在]位置が初期位置以外か
if (IS_LAST_MODULE_TOC)
scrollFixed -= MARGIN_TOP; // 目次モジュールの[設置]位置が最後の時
else if (now)
scrollFixed += MODULE_STATIC_HEIGHT;
setTocScrollBar(now);
// 下までスクロールした時にページへ固定する位置
var tocModuleMaxHeight = Math.min(winHeight - marginComp, getOuterHeight(elSidebarTocModule));
scrollAbsolute = getScrollTop(elMainInner) + getOuterHeight(elMainInner) - tocModuleMaxHeight - marginComp;
box2Top = getScrollTop(elBox2);
console.log('scrollFixed:' + scrollFixed);
console.log('scrollAbsolute:' + scrollAbsolute);
updateScroll();
}
}
// 現在のスクロール位置をもとに表示を更新
function updateScroll() {
var scrollTop = win.pageYOffset;
var scrollLeft = win.pageXOffset;
// 現在位置のクラス設定
if (scrollTop <= SCROLL_MARGIN) {
setCurrent(0);
}
else if ((scrollRange - scrollTop) <= SCROLL_MARGIN) {
setCurrent(TOC_LAST_INDEX);
}
else {
for (var i = TOC_LAST_INDEX; i >= 0; i--) {
if (scrollTop > headlineTopList[i] - SCROLL_MARGIN) {
setCurrent(i);
break;
}
}
}
// モジュールを追従させる
if (move) {
if (scrollAbsolute < scrollTop) {
// ページに固定(下)
if (checkPosition(2)) {
setTocModuleOption({
position: 'absolute', left: '',
top: scrollAbsolute - box2Top + marginComp + 'px'
}, true);
}
}
else if (scrollFixed < scrollTop) {
// ウィンドウに固定
checkPosition(1, function(current) {
var style = {position: 'fixed', top: marginComp + 'px'};
// サイドバーの最後以外に設置されていて直前が初期位置の時はフェードインさせる
if (!IS_LAST_MODULE_TOC && current === 0)
elSidebarTocModule.classList.add(CLASS_FADE_IN);
setTocModuleOption(style, true);
});
}
else {
// 初期位置
if (checkPosition(0))
setTocModuleOption(STYLE_DEFAULT, false);
}
}
// 横方向のスクロールに合わせる
if (checkPosition() === 1)
elSidebarTocModule.style.left = leftMargin - scrollLeft + 'px';
}
}());
}());
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment