docsify/src/core/event/scroll.js
Leonardo Rossi b53ea1e304
Adds option to specify an header height (#1045)
* Adds option to specify an header height

When using a template that has a sticky-header, clicking on the sidebar will
scroll the page under the header.

I added the option `headerHeight` (default = `0`) so that the content div will
be scrolled down that amount of pixels.

* updates documentation and renames variable
2020-03-09 14:19:31 +05:30

162 lines
3.7 KiB
JavaScript

import Tweezer from 'tweezer.js';
import cssEscape from 'css.escape';
import { isMobile } from '../util/env';
import * as dom from '../util/dom';
import config from '../config';
const nav = {};
let hoverOver = false;
let scroller = null;
let enableScrollEvent = true;
let coverHeight = 0;
function scrollTo(el, offset = 0) {
if (scroller) {
scroller.stop();
}
enableScrollEvent = false;
scroller = new Tweezer({
start: window.pageYOffset,
end: el.getBoundingClientRect().top + window.pageYOffset - offset,
duration: 500,
})
.on('tick', v => window.scrollTo(0, v))
.on('done', () => {
enableScrollEvent = true;
scroller = null;
})
.begin();
}
function highlight(path) {
if (!enableScrollEvent) {
return;
}
const sidebar = dom.getNode('.sidebar');
const anchors = dom.findAll('.anchor');
const wrap = dom.find(sidebar, '.sidebar-nav');
let active = dom.find(sidebar, 'li.active');
const doc = document.documentElement;
const top = ((doc && doc.scrollTop) || document.body.scrollTop) - coverHeight;
let last;
for (let i = 0, len = anchors.length; i < len; i += 1) {
const node = anchors[i];
if (node.offsetTop > top) {
if (!last) {
last = node;
}
break;
} else {
last = node;
}
}
if (!last) {
return;
}
const li =
nav[getNavKey(decodeURIComponent(path), last.getAttribute('data-id'))];
if (!li || li === active) {
return;
}
active && active.classList.remove('active');
li.classList.add('active');
active = li;
// Scroll into view
// https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297
if (!hoverOver && dom.body.classList.contains('sticky')) {
const height = sidebar.clientHeight;
const curOffset = 0;
const cur = active.offsetTop + active.clientHeight + 40;
const isInView =
active.offsetTop >= wrap.scrollTop && cur <= wrap.scrollTop + height;
const notThan = cur - curOffset < height;
const top = isInView ? wrap.scrollTop : notThan ? curOffset : cur - height;
sidebar.scrollTop = top;
}
}
function getNavKey(path, id) {
return `${path}?id=${id}`;
}
export function scrollActiveSidebar(router) {
const cover = dom.find('.cover.show');
coverHeight = cover ? cover.offsetHeight : 0;
const sidebar = dom.getNode('.sidebar');
let lis = [];
if (sidebar !== null && sidebar !== undefined) {
lis = dom.findAll(sidebar, 'li');
}
for (let i = 0, len = lis.length; i < len; i += 1) {
const li = lis[i];
const a = li.querySelector('a');
if (!a) {
continue;
}
let href = a.getAttribute('href');
if (href !== '/') {
const {
query: { id },
path,
} = router.parse(href);
if (id) {
href = getNavKey(path, id);
}
}
if (href) {
nav[decodeURIComponent(href)] = li;
}
}
if (isMobile) {
return;
}
const path = router.getCurrentPath();
dom.off('scroll', () => highlight(path));
dom.on('scroll', () => highlight(path));
dom.on(sidebar, 'mouseover', () => {
hoverOver = true;
});
dom.on(sidebar, 'mouseleave', () => {
hoverOver = false;
});
}
export function scrollIntoView(path, id) {
if (!id) {
return;
}
const topMargin = config().topMargin;
const section = dom.find('#' + cssEscape(id));
section && scrollTo(section, topMargin);
const li = nav[getNavKey(path, id)];
const sidebar = dom.getNode('.sidebar');
const active = dom.find(sidebar, 'li.active');
active && active.classList.remove('active');
li && li.classList.add('active');
}
const scrollEl = dom.$.scrollingElement || dom.$.documentElement;
export function scroll2Top(offset = 0) {
scrollEl.scrollTop = offset === true ? 0 : Number(offset);
}