Improve navbar scrolling in the live version of the docs (#3368)

This commit is contained in:
Yuri Sizov
2020-04-16 10:52:29 +03:00
committed by GitHub
parent 72dd918165
commit 4f7ee00821
2 changed files with 116 additions and 56 deletions

View File

@@ -1,71 +1,120 @@
// The number of pixels the user must scroll by before the logo is hidden.
const scrollTopPixels = 234;
// Handle page scroll and adjust sidebar accordingly.
// The margin to apply to the menu when the search bar is made fixed.
// Should roughly match the logo's height as to not hide the top menu items
// behind it.
const menuTopMargin = 90;
const menuHeightOffset_default = 338;
const menuHeightOffset_fixed = 102;
const menuHeightOffset_diff = (menuHeightOffset_default - menuHeightOffset_fixed);
// Each page has two scrolls: the main scroll, which is moving the content of the page;
// and the sidebar scroll, which is moving the navigation in the sidebar.
// We want the logo to gradually disappear as the main content is scrolled, giving
// more room to the navigation on the left. This means adjusting the height
// available to the navigation on the fly. There is also a banner below the navigation
// that must be dealt with simultaneously.
const registerOnScrollEvent = (function(){
// Configuration.
// Hide the navigation bar logo when scrolling down on desktop platforms.
// The logo is quite tall, so this helps make the rest of the navigation bar
// more readable.
function registerOnScrollEvent(mediaQuery) {
const $window = $(window);
const $menu = $('.wy-menu-vertical');
const $search = $('.wy-side-nav-search');
// The number of pixels the user must scroll by before the logo is completely hidden.
const scrollTopPixels = 234;
// The target margin to be applied to the navigation bar when the logo is hidden.
const menuTopMargin = 90;
// The max-height offset when the logo is completely visible.
const menuHeightOffset_default = 338;
// The max-height offset when the logo is completely hidden.
const menuHeightOffset_fixed = 102;
// The distance between the two max-height offset values above; used for intermediate values.
const menuHeightOffset_diff = (menuHeightOffset_default - menuHeightOffset_fixed);
if (mediaQuery.matches) {
// We're on desktop; register the scroll event.
const handleEarlyScroll = (currentScroll) => {
$search.css('margin-top', `-${currentScroll}px`);
$menu.css('margin-top', `${menuTopMargin + (scrollTopPixels - currentScroll)}px`);
// Media query handler.
return function(mediaQuery) {
// We only apply this logic to the "desktop" resolution (defined by a media query at the bottom).
// This handler is executed when the result of the query evaluation changes, which means that
// the page has moved between "desktop" and "mobile" states.
if (currentScroll > 0) {
const scrolledPercent = (scrollTopPixels - currentScroll) / scrollTopPixels;
const offsetValue = menuHeightOffset_fixed + menuHeightOffset_diff * scrolledPercent;
$menu.css('max-height', `calc(100% - ${offsetValue}px)`);
// When entering the "desktop" state, we register scroll events and adjust elements on the page.
// When entering the "mobile" state, we clean up any registered events and restore elements on the page
// to their initial state.
const $window = $(window);
const $menu = $('.wy-menu-vertical');
const $search = $('.wy-side-nav-search');
const $ethical = $('.ethical-rtd');
if (mediaQuery.matches) {
// Entering the "desktop" state.
// The scroll event handler.
// Executed as the page is scrolled and once immediatelly as the page enters this state.
const handleScroll = (currentScroll) => {
if (currentScroll >= scrollTopPixels) {
// After the page is scrolled below the threshold, we fix everything in place.
$search.css('margin-top', `-${scrollTopPixels}px`);
$menu.css('margin-top', `${menuTopMargin}px`);
$menu.css('max-height', `calc(100% - ${menuHeightOffset_fixed}px)`);
}
else {
// Between the top of the page and the threshold we calculate intermediate values
// to guarantee a smooth transition.
$search.css('margin-top', `-${currentScroll}px`);
$menu.css('margin-top', `${menuTopMargin + (scrollTopPixels - currentScroll)}px`);
if (currentScroll > 0) {
const scrolledPercent = (scrollTopPixels - currentScroll) / scrollTopPixels;
const offsetValue = menuHeightOffset_fixed + menuHeightOffset_diff * scrolledPercent;
$menu.css('max-height', `calc(100% - ${offsetValue}px)`);
} else {
$menu.css('max-height', `calc(100% - ${menuHeightOffset_default}px)`);
}
}
};
$search.addClass('fixed');
$ethical.addClass('fixed');
// Adjust the inner height of navigation so that the banner can be overlaid there later.
const ethicalOffsetBottom = $ethical.height() || 0;
if (ethicalOffsetBottom) {
$menu.css('padding-bottom', `${ethicalOffsetBottom}px`);
} else {
$menu.css('max-height', `calc(100% - ${menuHeightOffset_default}px)`);
$menu.css('padding-bottom', `0px`);
}
};
$search.addClass('fixed');
$window.scroll(function() {
const currentScroll = window.scrollY;
$window.scroll(function() {
handleScroll(window.scrollY);
});
if (currentScroll >= scrollTopPixels) {
$search.css('margin-top', `-${scrollTopPixels}px`);
$menu.css('margin-top', `${menuTopMargin}px`);
$menu.scroll(function() {
const menuScrollTop = $(this).scrollTop();
const menuScrollBottom = this.scrollHeight - (menuScrollTop + this.offsetHeight);
$menu.css('max-height', `calc(100% - ${menuHeightOffset_fixed}px)`);
}
else {
handleEarlyScroll(currentScroll);
}
});
// As the navigation is scrolled we add a shadow to the top bar hanging over it.
if (menuScrollTop > 0) {
$search.addClass('fixed-and-scrolled');
} else {
$search.removeClass('fixed-and-scrolled');
}
$menu.scroll(function() {
if ($(this).scrollTop() > 0) {
$search.addClass('fixed-and-scrolled');
} else {
$search.removeClass('fixed-and-scrolled');
}
})
// Near the bottom we start moving the sidebar banner into view.
if (menuScrollBottom < ethicalOffsetBottom) {
$ethical.css('margin-top', `-${ethicalOffsetBottom - menuScrollBottom}px`);
} else {
$ethical.css('margin-top', '0px');
}
})
handleEarlyScroll(window.scrollY);
} else {
// We're on mobile; unregister the scroll event so the logo isn't hidden
// when scrolling and clean up any changed properties.
$window.unbind('scroll');
$search.removeClass('fixed');
$search.css('margin-top', `0px`);
$menu.css('margin-top', `0px`);
$menu.css('max-height', 'initial');
handleScroll(window.scrollY);
} else {
// Entering the "mobile" state.
$window.unbind('scroll');
$menu.unbind('scroll');
$search.removeClass('fixed');
$ethical.removeClass('fixed');
$search.css('margin-top', `0px`);
$menu.css('margin-top', `0px`);
$menu.css('padding-bottom', `0px`);
$menu.css('max-height', 'initial');
$ethical.css('margin-top', '0px');
}
}
}
})();
$(document).ready(() => {
const mediaQuery = window.matchMedia('only screen and (min-width: 769px)');