var spy = function () { var elems = $(":header"); if (elems.length == 0) { return; } var currentTop = $(window).scrollTop(); var currentBottom = $(window).scrollTop() + $(window).height(); var pageBottom = $('#EOF').offset().top; var meetUnread = false var currentIndex = elems.length - 1 elems.each(function (idx) { var elemTop = $(this).offset().top; var id = $(this).attr('id'); var navElem = $('#' + id + '-nav'); if (currentTop + $(this).height() >= elemTop) { navElem.addClass('toc-active'); } else { if (meetUnread == false) { meetUnread = true currentIndex = idx - 1 } navElem.removeClass('toc-active'); } }) if (currentBottom >= pageBottom) { currentIndex = -1 } //console.log(currentIndex); //Collapse them collapseOthers(currentIndex); } var onNavClick = function (name) { var elems = $(":header"); elems.each(function (idx) { var id = $(this).attr('id'); if (name == '#' + id + '-nav'){ collapseOthers(idx); return; } }); } var collapseOthers = function (currentIndex) { if (currentIndex == -1) { $(".collapse").each(function (idx) { $(this).collapse("hide"); }); return; } var elems = $(":header"); //console.log(currentIndex); currentId = "#" + elems[currentIndex].id + "-nav"; $(currentId).parents(".collapse").each(function (idx) { $(this).collapse("show"); }); $(currentId).parent().next().filter(".collapse").collapse("show"); $(".collapse").not($(currentId).parents()).not($(currentId).parent().next()).each(function (idx) { $(this).collapse("hide"); }); } $().ready(function () { spy(); $(window).bind('scroll', debounce(spy, 250, { 'maxWait': 1000 })); }); //From https://github.com/lodash/lodash/blob/master/debounce.js // and https://github.com/lodash/lodash/blob/master/isObject.js function debounce(func, wait, options) { let lastArgs, lastThis, maxWait, result, timerId, lastCallTime let lastInvokeTime = 0 let leading = false let maxing = false let trailing = true // Bypass `requestAnimationFrame` by explicitly setting `wait=0`. const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function') if (typeof func !== 'function') { throw new TypeError('Expected a function') } function isObject(value) { const type = typeof value return value != null && (type === 'object' || type === 'function') } wait = +wait || 0 if (isObject(options)) { leading = !!options.leading maxing = 'maxWait' in options maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait trailing = 'trailing' in options ? !!options.trailing : trailing } function invokeFunc(time) { const args = lastArgs const thisArg = lastThis lastArgs = lastThis = undefined lastInvokeTime = time result = func.apply(thisArg, args) return result } function startTimer(pendingFunc, wait) { if (useRAF) { root.cancelAnimationFrame(timerId) return root.requestAnimationFrame(pendingFunc) } return setTimeout(pendingFunc, wait) } function cancelTimer(id) { if (useRAF) { return root.cancelAnimationFrame(id) } clearTimeout(id) } function leadingEdge(time) { // Reset any `maxWait` timer. lastInvokeTime = time // Start the timer for the trailing edge. timerId = startTimer(timerExpired, wait) // Invoke the leading edge. return leading ? invokeFunc(time) : result } function remainingWait(time) { const timeSinceLastCall = time - lastCallTime const timeSinceLastInvoke = time - lastInvokeTime const timeWaiting = wait - timeSinceLastCall return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting } function shouldInvoke(time) { const timeSinceLastCall = time - lastCallTime const timeSinceLastInvoke = time - lastInvokeTime // Either this is the first call, activity has stopped and we're at the // trailing edge, the system time has gone backwards and we're treating // it as the trailing edge, or we've hit the `maxWait` limit. return (lastCallTime === undefined || (timeSinceLastCall >= wait) || (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)) } function timerExpired() { const time = Date.now() if (shouldInvoke(time)) { return trailingEdge(time) } // Restart the timer. timerId = startTimer(timerExpired, remainingWait(time)) } function trailingEdge(time) { timerId = undefined // Only invoke if we have `lastArgs` which means `func` has been // debounced at least once. if (trailing && lastArgs) { return invokeFunc(time) } lastArgs = lastThis = undefined return result } function cancel() { if (timerId !== undefined) { cancelTimer(timerId) } lastInvokeTime = 0 lastArgs = lastCallTime = lastThis = timerId = undefined } function flush() { return timerId === undefined ? result : trailingEdge(Date.now()) } function pending() { return timerId !== undefined } function debounced(...args) { const time = Date.now() const isInvoking = shouldInvoke(time) lastArgs = args lastThis = this lastCallTime = time if (isInvoking) { if (timerId === undefined) { return leadingEdge(lastCallTime) } if (maxing) { // Handle invocations in a tight loop. timerId = startTimer(timerExpired, wait) return invokeFunc(lastCallTime) } } if (timerId === undefined) { timerId = startTimer(timerExpired, wait) } return result } debounced.cancel = cancel debounced.flush = flush debounced.pending = pending return debounced }