refactor(core): add scroll event
This commit is contained in:
parent
27e7e749af
commit
8cae16539d
5 changed files with 86 additions and 170 deletions
|
|
@ -2,11 +2,6 @@ import { isMobile } from '../util/env'
|
||||||
import { body, on } from '../util/dom'
|
import { body, on } from '../util/dom'
|
||||||
import * as sidebar from './sidebar'
|
import * as sidebar from './sidebar'
|
||||||
|
|
||||||
export function eventMixin (Docsify) {
|
|
||||||
Docsify.prototype.$resetEvents = function () {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initEvent (vm) {
|
export function initEvent (vm) {
|
||||||
// Bind toggle button
|
// Bind toggle button
|
||||||
sidebar.btn('button.sidebar-toggle')
|
sidebar.btn('button.sidebar-toggle')
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,89 @@
|
||||||
|
import { isMobile } from '../util/env'
|
||||||
|
import * as dom from '../util/dom'
|
||||||
|
|
||||||
export function scrollActiveSidebar () {
|
export function scrollActiveSidebar () {
|
||||||
|
if (isMobile) return
|
||||||
|
|
||||||
|
let hoverOver = false
|
||||||
|
const anchors = dom.findAll('.anchor')
|
||||||
|
const sidebar = dom.find('.sidebar')
|
||||||
|
const wrap = dom.find(sidebar, '.sidebar-nav')
|
||||||
|
const height = sidebar.clientHeight
|
||||||
|
|
||||||
|
const nav = {}
|
||||||
|
const lis = dom.findAll(sidebar, 'li')
|
||||||
|
let active = dom.find(sidebar, 'li.active')
|
||||||
|
|
||||||
|
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 match = href.match('#([^#]+)$')
|
||||||
|
if (match && match.length) href = match[0].slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
nav[decodeURIComponent(href)] = li
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlight () {
|
||||||
|
const top = dom.body.scrollTop
|
||||||
|
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[last.getAttribute('data-id')]
|
||||||
|
|
||||||
|
if (!li || li === active) return
|
||||||
|
if (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 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dom.off('scroll', highlight)
|
||||||
|
dom.on('scroll', highlight)
|
||||||
|
dom.on(sidebar, 'mouseover', () => { hoverOver = true })
|
||||||
|
dom.on(sidebar, 'mouseleave', () => { hoverOver = false })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scrollIntoView () {
|
export function scrollIntoView (id) {
|
||||||
|
const section = dom.find('#' + id)
|
||||||
|
section && setTimeout(() => section.scrollIntoView(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function scroll2Top () {
|
const scrollEl = dom.$.scrollingElement || dom.$.documentElement
|
||||||
|
|
||||||
|
export function scroll2Top (offset = 0) {
|
||||||
|
scrollEl.scrollTop = offset === true ? 0 : Number(offset)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { get } from './ajax'
|
||||||
import { callHook } from '../init/lifecycle'
|
import { callHook } from '../init/lifecycle'
|
||||||
import { getRoot } from '../route/util'
|
import { getRoot } from '../route/util'
|
||||||
import { noop } from '../util/core'
|
import { noop } from '../util/core'
|
||||||
|
import { scrollIntoView } from '../event/scroll'
|
||||||
|
import { getAndActive } from '../event/sidebar'
|
||||||
|
|
||||||
export function fetchMixin (Docsify) {
|
export function fetchMixin (Docsify) {
|
||||||
let last
|
let last
|
||||||
|
|
@ -57,7 +59,8 @@ export function fetchMixin (Docsify) {
|
||||||
export function initFetch (vm) {
|
export function initFetch (vm) {
|
||||||
vm._fetchCover(vm)
|
vm._fetchCover(vm)
|
||||||
vm._fetch(result => {
|
vm._fetch(result => {
|
||||||
vm.$resetEvents()
|
scrollIntoView(vm.route.query.id)
|
||||||
|
getAndActive('nav')
|
||||||
callHook(vm, 'doneEach')
|
callHook(vm, 'doneEach')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { initMixin } from './init'
|
||||||
import { routeMixin } from './route'
|
import { routeMixin } from './route'
|
||||||
import { renderMixin } from './render'
|
import { renderMixin } from './render'
|
||||||
import { fetchMixin } from './fetch'
|
import { fetchMixin } from './fetch'
|
||||||
import { eventMixin } from './event'
|
|
||||||
import initGlobalAPI from './global-api'
|
import initGlobalAPI from './global-api'
|
||||||
|
|
||||||
function Docsify () {
|
function Docsify () {
|
||||||
|
|
@ -13,7 +12,6 @@ initMixin(Docsify)
|
||||||
routeMixin(Docsify)
|
routeMixin(Docsify)
|
||||||
renderMixin(Docsify)
|
renderMixin(Docsify)
|
||||||
fetchMixin(Docsify)
|
fetchMixin(Docsify)
|
||||||
eventMixin(Docsify)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global API
|
* Global API
|
||||||
|
|
|
||||||
159
src/event.js
159
src/event.js
|
|
@ -1,159 +0,0 @@
|
||||||
import { isMobile } from './util'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Active sidebar when scroll
|
|
||||||
* @link https://buble.surge.sh/
|
|
||||||
*/
|
|
||||||
export function scrollActiveSidebar () {
|
|
||||||
if (isMobile()) return
|
|
||||||
|
|
||||||
let hoveredOverSidebar = false
|
|
||||||
const anchors = document.querySelectorAll('.anchor')
|
|
||||||
const sidebar = document.querySelector('.sidebar')
|
|
||||||
const sidebarContainer = sidebar.querySelector('.sidebar-nav')
|
|
||||||
const sidebarHeight = sidebar.clientHeight
|
|
||||||
|
|
||||||
const nav = {}
|
|
||||||
const lis = sidebar.querySelectorAll('li')
|
|
||||||
let active = sidebar.querySelector('li.active')
|
|
||||||
|
|
||||||
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 match = href.match('#([^#]+)$')
|
|
||||||
if (match && match.length) href = match[0].slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
nav[decodeURIComponent(href)] = li
|
|
||||||
}
|
|
||||||
|
|
||||||
function highlight () {
|
|
||||||
const top = document.body.scrollTop
|
|
||||||
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[last.getAttribute('data-id')]
|
|
||||||
|
|
||||||
if (!li || li === active) return
|
|
||||||
if (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 (!hoveredOverSidebar && !sticky.noSticky) {
|
|
||||||
const currentPageOffset = 0
|
|
||||||
const currentActiveOffset = active.offsetTop + active.clientHeight + 40
|
|
||||||
const currentActiveIsInView = (
|
|
||||||
active.offsetTop >= sidebarContainer.scrollTop &&
|
|
||||||
currentActiveOffset <= sidebarContainer.scrollTop + sidebarHeight
|
|
||||||
)
|
|
||||||
const linkNotFurtherThanSidebarHeight = currentActiveOffset - currentPageOffset < sidebarHeight
|
|
||||||
const newScrollTop = currentActiveIsInView
|
|
||||||
? sidebarContainer.scrollTop
|
|
||||||
: linkNotFurtherThanSidebarHeight
|
|
||||||
? currentPageOffset
|
|
||||||
: currentActiveOffset - sidebarHeight
|
|
||||||
|
|
||||||
sidebar.scrollTop = newScrollTop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.removeEventListener('scroll', highlight)
|
|
||||||
window.addEventListener('scroll', highlight)
|
|
||||||
sidebar.addEventListener('mouseover', () => { hoveredOverSidebar = true })
|
|
||||||
sidebar.addEventListener('mouseleave', () => { hoveredOverSidebar = false })
|
|
||||||
}
|
|
||||||
|
|
||||||
export function scrollIntoView () {
|
|
||||||
const id = window.location.hash.match(/#[^#\/]+$/g)
|
|
||||||
if (!id || !id.length) return
|
|
||||||
const section = document.querySelector(decodeURIComponent(id[0]))
|
|
||||||
|
|
||||||
if (section) setTimeout(() => section.scrollIntoView(), 0)
|
|
||||||
|
|
||||||
return section
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Acitve link
|
|
||||||
*/
|
|
||||||
export function activeLink (dom, activeParent) {
|
|
||||||
const host = window.location.href
|
|
||||||
|
|
||||||
dom = typeof dom === 'object' ? dom : document.querySelector(dom)
|
|
||||||
if (!dom) return
|
|
||||||
let target
|
|
||||||
|
|
||||||
;[].slice.call(dom.querySelectorAll('a'))
|
|
||||||
.sort((a, b) => b.href.length - a.href.length)
|
|
||||||
.forEach(node => {
|
|
||||||
if (host.indexOf(node.href) === 0 && !target) {
|
|
||||||
activeParent
|
|
||||||
? node.parentNode.classList.add('active')
|
|
||||||
: node.classList.add('active')
|
|
||||||
target = node
|
|
||||||
} else {
|
|
||||||
activeParent
|
|
||||||
? node.parentNode.classList.remove('active')
|
|
||||||
: node.classList.remove('active')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sidebar toggle
|
|
||||||
*/
|
|
||||||
export function bindToggle (dom) {
|
|
||||||
dom = typeof dom === 'object' ? dom : document.querySelector(dom)
|
|
||||||
if (!dom) return
|
|
||||||
const body = document.body
|
|
||||||
|
|
||||||
dom.addEventListener('click', () => body.classList.toggle('close'))
|
|
||||||
|
|
||||||
if (isMobile()) {
|
|
||||||
const sidebar = document.querySelector('.sidebar')
|
|
||||||
sidebar.addEventListener('click', () => {
|
|
||||||
body.classList.toggle('close')
|
|
||||||
setTimeout(() => activeLink(sidebar, true), 0)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrollingElement = document.scrollingElement || document.documentElement
|
|
||||||
|
|
||||||
export function scroll2Top (offset = 0) {
|
|
||||||
scrollingElement.scrollTop = offset === true ? 0 : Number(offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sticky () {
|
|
||||||
sticky.dom = sticky.dom || document.querySelector('section.cover')
|
|
||||||
const coverHeight = sticky.dom.getBoundingClientRect().height
|
|
||||||
|
|
||||||
return (function () {
|
|
||||||
if (window.pageYOffset >= coverHeight || sticky.dom.classList.contains('hidden')) {
|
|
||||||
document.body.classList.add('sticky')
|
|
||||||
sticky.noSticky = false
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove('sticky')
|
|
||||||
sticky.noSticky = true
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue