diff --git a/src/js/theme/navigation.js b/src/js/theme/navigation.js index 8b8494f53b544be6157333c8f26beffa6b37cbcc..fffcad2d655c3bf0f8b0b1cfef7c9de42a5b6750 100644 --- a/src/js/theme/navigation.js +++ b/src/js/theme/navigation.js @@ -23,28 +23,96 @@ function getScroller() { Scroll to a specific hash tag in the content */ function scrollToHash(hash) { - var $scroller = getScroller(); - var dest = 0; + var $scroller = getScroller(), + dest = 0; if (hash) { - dest = $scroller.find(hash).position().top; + dest = getElementTopPosition(hash); } $scroller.animate({ scrollTop: dest }, 800, 'swing'); - // Update summary active chapter - $('.book-summary').find('.chapter.active').removeClass('active'); - $('.book-summary .chapter > a').each(function() { - var uri = url.resolve(window.location.pathname, $(this).attr('href')), - windowLocation = window.location.pathname + window.location.hash; + handleScrolling(); +} + +/* + Return the top position of an element + */ +function getElementTopPosition(id) { + // Get actual position of element if nested + var $scroller = getScroller(), + $container = $scroller.find('.page-inner'), + $el = $scroller.find(id), + $parent = $el.offsetParent(), + dest = 0; + + dest = $el.position().top; + + while (!$parent.is($container)) { + $el = $parent; + dest += $el.position().top; + $parent = $el.offsetParent(); + } + + // Return rounded value since + // jQuery scrollTop() returns an integer + return Math.floor(dest); +} + +/* + Handle updating summary at scrolling +*/ +var $chapters; +function handleScrolling() { + // Get current page scroll + var $scroller = getScroller(), + scrollTop = $scroller.scrollTop(), + scrollHeight = $scroller.prop('scrollHeight'), + clientHeight = $scroller.prop('clientHeight'), + nbChapters = $chapters.length, + foundChapter = false; + + // Set a chapter as active + function setChapterActive($chapter) { + foundChapter = true; + + $chapters.removeClass('active'); + $chapter.addClass('active'); + } + + // Find each title position in reverse order + $($chapters.get().reverse()).each(function(index) { + var $link = $(this).children('a'), + titleId = $link.attr('href').split('#')[1], + titleTop; + + if (!!titleId) titleId = '#'+titleId; + + if (!!titleId && !foundChapter) { + titleTop = getElementTopPosition(titleId); - // Same page - if (uri == windowLocation) { - $(this).parent('.chapter').addClass('active'); + // Set current chapter as active if scroller passed it + if (scrollTop >= titleTop) { + setChapterActive($(this)); + } + } + // If not found at first chapter, set as active + if (index == (nbChapters - 1) && !foundChapter) { + setChapterActive($(this)); } }); + + // ScrollTop is at 0, set first chapter anyway + if (!foundChapter && !scrollTop) { + setChapterActive($chapters.first()); + } + + // Finally, set last chapter at the bottom of page + if (!!scrollTop && (scrollHeight - scrollTop == clientHeight)) { + setChapterActive($chapters.last()); + } } /* @@ -132,14 +200,14 @@ function handleNavigation(relativeUrl, push) { $('.book').attr('class', bodyClass); $('.book-summary').scrollTop(scrollPosition); + // Update state + gitbook.state.$book = $('.book'); + preparePage(!hash); + // Scroll to hashtag position if (hash) { scrollToHash(hash); } - - // Update state - gitbook.state.$book = $('.book'); - preparePage(!hash); }) .fail(function (e) { location.href = relativeUrl; @@ -168,6 +236,25 @@ function preparePage(resetScroll) { // Reset scroll if (resetScroll !== false) $bookInner.scrollTop(0); $bookBody.scrollTop(0); + + // Get current page summary chapters + $chapters = $('.book-summary .summary .chapter') + .filter(function() { + var $link = $(this).children('a'), + href = $link.attr('href').split('#')[0]; + + var resolvedRef = url.resolve(window.location.pathname, href); + + return window.location.pathname == resolvedRef; + }); + + // Bind scrolling if summary contains more than one link to this page + var $scroller = getScroller(); + if ($chapters.length > 1) { + $scroller.scroll(function(e) { + handleScrolling($chapters); + }); + } } function isLeftClickEvent(e) {