hexo-theme-amane/source/js/back_to_top.js

149 lines
4.7 KiB
JavaScript

$(document).ready(() => {
const $button = $('#back-to-top');
const $footer = $('footer.footer');
const $mainColumn = $('.column-main');
const $leftSidebar = $('.column-left');
const $rightSidebar = $('.column-right');
let lastScrollTop = 0;
const rightMargin = 20;
const bottomMargin = 20;
let lastState = null;
const state = {
base: {
classname: 'card has-text-centered',
left: '',
width: 64,
bottom: bottomMargin
}
};
state['desktop-hidden'] = Object.assign({}, state.base, {
classname: state.base.classname + ' rise-up'
});
state['desktop-visible'] = Object.assign({}, state['desktop-hidden'], {
classname: state['desktop-hidden'].classname + ' fade-in'
});
state['desktop-dock'] = Object.assign({}, state['desktop-visible'], {
classname: state['desktop-visible'].classname + ' fade-in is-rounded',
width: 40
});
state['mobile-hidden'] = Object.assign({}, state.base, {
classname: state.base.classname + ' fade-in',
right: rightMargin
});
state['mobile-visible'] = Object.assign({}, state['mobile-hidden'], {
classname: state['mobile-hidden'].classname + ' rise-up'
});
function isStateEquals(prev, next) {
return ![].concat(Object.keys(prev), Object.keys(next)).some(key => {
return !Object.prototype.hasOwnProperty.call(prev, key)
|| !Object.prototype.hasOwnProperty.call(next, key)
|| next[key] !== prev[key];
});
}
function applyState(state) {
if (lastState !== null && isStateEquals(lastState, state)) {
return;
}
$button.attr('class', state.classname);
for (const prop in state) {
if (prop === 'classname') {
continue;
}
$button.css(prop, state[prop]);
}
lastState = state;
}
function isDesktop() {
return window.innerWidth >= 1078;
}
function isTablet() {
return window.innerWidth >= 768 && !isDesktop();
}
function isScrollUp() {
return $(window).scrollTop() < lastScrollTop && $(window).scrollTop() > 0;
}
function hasLeftSidebar() {
return $leftSidebar.length > 0;
}
function hasRightSidebar() {
return $rightSidebar.length > 0;
}
function getRightSidebarBottom() {
if (!hasRightSidebar()) {
return 0;
}
return Math.max.apply(null, $rightSidebar.find('.widget').map(function() {
return $(this).offset().top + $(this).outerHeight(true);
}));
}
function getScrollTop() {
return $(window).scrollTop();
}
function getScrollBottom() {
return $(window).scrollTop() + $(window).height();
}
function getButtonWidth() {
return $button.outerWidth(true);
}
function getButtonHeight() {
return $button.outerHeight(true);
}
function updateScrollTop() {
lastScrollTop = $(window).scrollTop();
}
function update() {
// desktop mode or tablet mode with only right sidebar enabled
if (isDesktop() || (isTablet() && !hasLeftSidebar() && hasRightSidebar())) {
let nextState;
const padding = ($mainColumn.outerWidth() - $mainColumn.width()) / 2;
const maxLeft = $(window).width() - getButtonWidth() - rightMargin;
const maxBottom = $footer.offset().top + (getButtonHeight() / 2) + bottomMargin;
if (getScrollTop() === 0 || getScrollBottom() < getRightSidebarBottom() + padding + getButtonHeight()) {
nextState = state['desktop-hidden'];
} else if (getScrollBottom() < maxBottom) {
nextState = state['desktop-visible'];
} else {
nextState = Object.assign({}, state['desktop-dock'], {
bottom: getScrollBottom() - maxBottom + bottomMargin
});
}
const left = $mainColumn.offset().left + $mainColumn.outerWidth() + padding;
nextState = Object.assign({}, nextState, {
left: Math.min(left, maxLeft)
});
applyState(nextState);
} else {
// mobile and tablet mode
if (!isScrollUp()) {
applyState(state['mobile-hidden']);
} else {
applyState(state['mobile-visible']);
}
updateScrollTop();
}
}
update();
$(window).resize(update);
$(window).scroll(update);
$('#back-to-top').on('click', () => {
$('body, html').animate({ scrollTop: 0 }, 400);
});
});