import Tweezer from 'tweezer.js'; import { isMobile } from '../util/env'; import * as dom from '../util/dom'; import { removeParams } from '../router/util'; 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: Math.round(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(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; sidebar.scrollTop = isInView ? wrap.scrollTop : notThan ? curOffset : cur - height; } } function getNavKey(path, id) { return `${decodeURIComponent(path)}?id=${decodeURIComponent(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 = removeParams(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('#' + 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); }