From ecc7086da523397834a054cf5235cf7084b64f1e Mon Sep 17 00:00:00 2001 From: amazingrise <8315221+AmazingRise@users.noreply.github.com> Date: Thu, 4 Jun 2020 20:28:40 +0800 Subject: Replace debounce, and add an option for ToC collapsing. --- static/js/toc-collapse.js | 205 ++++++++++++++++++++++++++++++++++++++++++++++ static/js/toc.js | 52 ++++++------ 2 files changed, 228 insertions(+), 29 deletions(-) create mode 100644 static/js/toc-collapse.js (limited to 'static') diff --git a/static/js/toc-collapse.js b/static/js/toc-collapse.js new file mode 100644 index 0000000..a2780f9 --- /dev/null +++ b/static/js/toc-collapse.js @@ -0,0 +1,205 @@ +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 = -1 + elems.each(function (idx) { + var elemTop = $(this).offset().top; + var id = $(this).attr('id'); + var navElem = $('#' + id + '-nav'); + if (currentTop + $(this).height() >= elemTop || currentBottom >= pageBottom) { + navElem.addClass('toc-active'); + } else { + if (meetUnread == false) { + meetUnread = true + currentIndex = idx - 1 + } + navElem.removeClass('toc-active'); + } + }) + if (currentIndex == -1) { + currentIndex = elems.length - 1; + } + //console.log(elems[currentIndex].id); + //Collapse them + collapseOthers("#" + elems[currentIndex].id + "-nav"); +} +var collapseOthers = function (currentId) { + //console.log(currentId); + $(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 + } + \ No newline at end of file diff --git a/static/js/toc.js b/static/js/toc.js index 4fde664..61fdc93 100644 --- a/static/js/toc.js +++ b/static/js/toc.js @@ -23,39 +23,33 @@ var spy = function () { navElem.removeClass('toc-active'); } }) - if (currentIndex == -1) { - currentIndex = elems.length - 1; - } - //console.log(elems[currentIndex].id); - //Collapse them - collapseOthers("#" + elems[currentIndex].id + "-nav"); } -var collapseOthers = function (currentId) { - //console.log(currentId); - $(currentId).parents(".collapse").each(function (idx) { +$().ready(function () { + $(".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)); + $(window).bind('scroll', throttle(spy)); }); -function debounce(func, delay = 250) { - let timer = null; - - return () => { - let context = this; - let args = arguments; - - clearTimeout(timer); - timer = setTimeout(() => { - func.apply(context, args); - }, delay) +function throttle(func, timeout = 250) { + let last; + let timer; + + return function () { + const context = this; + const args = arguments; + const now = +new Date(); + + if (last && now < last + timeout) { + clearTimeout(timer) + timer = setTimeout(function () { + last = now + func.apply(context, args) + }, timeout) + } else { + last = now + func.apply(context, args) + } } -} \ No newline at end of file + } \ No newline at end of file -- cgit v1.2.3