代码拉取完成,页面将自动刷新
同步操作将从 Yaohaixiao/outline.js 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
import Base from './base'
import isString from './utils/types/isString'
import isFunction from './utils/types/isFunction'
import isElement from './utils/types/isElement'
import later from './utils/lang/later'
import at from './utils/event/at'
import on from './utils/event/on'
import off from './utils/event/off'
import stop from './utils/event/stop'
import createElement from './utils/dom/createElement'
import scrollTo from './utils/dom/scrollTo'
import addClass from './utils/dom/addClass'
import intersection from './utils/dom/intersection'
import removeClass from './utils/dom/removeClass'
import offsetTop from './utils/dom/offsetTop'
import getStyle from './utils/dom/getStyle'
import setProperty from './utils/dom/setProperty'
import _getScrollElement from './utils/dom/_getScrollElement'
import cloneDeep from './utils/lang/cloneDeep'
import _paintChapters from './_paintChapters'
class Chapters extends Base {
constructor(options) {
super()
this.attrs = cloneDeep(Chapters.DEFAULTS)
this._reset()
this.offsetWidth = 0
this.playing = false
this.scrollTimer = null
this.resizeTimer = null
this.Observer = null
if (options) {
this.initialize(options)
}
}
_reset() {
this.$el = null
this.$title = null
this.$main = null
this.$list = null
this.$placeholder = null
this.$parentElement = null
this.$scrollElement = null
this.$active = null
this.chapters = []
this.active = 0
this.offsetTop = 0
this.closed = false
return this
}
initialize(options) {
let created
let parentElement
let scrollElement
let $parent
this.attr(options)
created = this.attr('created')
parentElement = this.attr('parentElement')
scrollElement = this.attr('scrollElement')
if (isString(parentElement)) {
$parent = document.querySelector(parentElement)
} else if (isElement(parentElement)) {
$parent = parentElement
}
this.$parentElement = $parent
this.$scrollElement = _getScrollElement(scrollElement)
this.chapters = this.attr('chapters')
this.closed = this.attr('closed')
this.active = this.attr('active')
if (isFunction(created)) {
created.call(this)
}
if (this.chapters.length < 1) {
return this
}
this.render().addListeners()
this.$active = document.querySelector(`#chapter-${this.active}`)
return this
}
isClosed() {
return this.closed
}
isSticky() {
const position = this.attr('position')
return position === 'sticky'
}
isFixed() {
const position = this.attr('position')
return position === 'fixed'
}
isInside() {
return this.isFixed() || this.isSticky()
}
isOutside() {
return !this.isInside()
}
_paintEdge() {
const $fragment = document.createDocumentFragment()
const STICKY = 'outline-chapters_sticky'
const HIDDEN = 'outline-chapters_hidden'
const title = this.attr('title')
const customClass = this.attr('customClass')
const $parentElement = this.$parentElement
const contents = []
let $title = null
let $el
let $main
let $list
let $placeholder
if (!$parentElement) {
return this
}
if (this.isInside() && title) {
$title = createElement(
'h2',
{
className: 'outline-chapters__title'
},
title
)
this.$title = $title
contents.push($title)
}
$list = createElement('ul', {
// 为优化性能,添加了 _fixed 和 _hidden
// fixed 为了让 $list 脱离流布局
// hidden 让 $list 不可见
className: `outline-chapters__list`
})
this.$list = $list
$placeholder = createElement('div', {
className: 'outline-chapters__placeholder'
})
this.$placeholder = $placeholder
$main = createElement(
'div',
{
className: 'outline-chapters__main'
},
[$list, $placeholder]
)
this.$main = $main
contents.push($main)
$el = createElement(
'nav',
{
id: 'outline-chapters',
className: `outline-chapters ${HIDDEN}`
},
contents
)
this.$el = $el
if (this.isSticky()) {
this.calculateStickyHeight()
addClass($el, STICKY)
}
if (customClass) {
addClass($el, customClass)
}
$fragment.appendChild($el)
$parentElement.appendChild($fragment)
return this
}
render() {
const HIDDEN = 'outline-chapters_hidden'
const showCode = this.attr('showCode')
const mounted = this.attr('mounted')
const $parentElement = this.$parentElement
const chapters = this.chapters
let $el
let $list
if (!$parentElement || chapters.length < 1) {
return this
}
this._paintEdge()
$el = this.$el
$list = this.$list
_paintChapters($list, chapters, showCode)
removeClass($el, HIDDEN)
this.positionPlaceholder(this.active)
this.offsetTop = offsetTop($el)
this.offsetWidth = $el.offsetWidth
if (this.isFixed()) {
this.sticky()
setProperty('--outline-chapters-width', `${this.offsetWidth}px`)
}
if (isFunction(mounted)) {
mounted.call(this)
}
this.onObserver()
return this
}
positionPlaceholder(index) {
const $main = this.$main
const $list = this.$list
const $placeholder = this.$placeholder
const $anchor = $list.querySelector('.outline-chapters__anchor')
const mainPaddingTop = parseInt(getStyle($main, 'padding-top'), 10)
const mainBorderTop = parseInt(getStyle($main, 'border-top-width'), 10)
const placeholderPaddingTop = parseInt(getStyle($list, 'padding-top'), 10)
const placeholderMarginTop = parseInt(getStyle($list, 'margin-top'), 10)
const placeholderBorderTop = parseInt(
getStyle($list, 'border-top-width'),
10
)
let height = $anchor.offsetHeight
let offsetTop = 0
let top
if (mainPaddingTop) {
offsetTop += mainPaddingTop
}
if (placeholderPaddingTop) {
offsetTop += placeholderPaddingTop
}
if (placeholderMarginTop) {
offsetTop += placeholderMarginTop
}
if (mainBorderTop) {
offsetTop += mainBorderTop
}
if (placeholderBorderTop) {
offsetTop += placeholderBorderTop
}
top = height * index
// top:calc(${offsetTop}px + ${top}px);
$placeholder.style.cssText = `transform: translateY(${
offsetTop + top
}px);height:${height}px;`
return this
}
highlight(id) {
const $anchor = this.$el.querySelector(`#chapter__anchor-${id}`)
const HIGHLIGHT = 'outline-chapters_active'
if (!$anchor) {
return this
}
if (this.$active) {
removeClass(this.$active, HIGHLIGHT)
}
this.active = parseInt($anchor.getAttribute('data-id'), 10)
this.$active = $anchor
addClass(this.$active, HIGHLIGHT)
this.positionPlaceholder(this.active)
return this
}
sticky() {
const afterSticky = this.attr('afterSticky')
const FIXED = 'outline-chapters_fixed'
const $el = this.$el
const top = this.offsetTop
const scrollTop = this.$scrollElement.scrollTop
let isStickying
if (!this.isFixed()) {
return this
}
isStickying = scrollTop >= top
if (isStickying) {
addClass($el, FIXED)
} else {
removeClass($el, FIXED)
}
if (isFunction(afterSticky)) {
afterSticky.call(this, this.isClosed(), isStickying)
}
return this
}
calculateStickyHeight() {
const documentElement = document.documentElement
const height = Math.max(
documentElement.clientHeight || 0,
window.innerHeight || 0
)
setProperty('--outline-sticky-height', `${height}px`)
return this
}
scrollTo(top, after) {
const el = this.$scrollElement
scrollTo(el, top, after)
return this
}
show() {
const FOLDED = 'outline-chapters_folded'
const HIDDEN = 'outline-chapters_hidden'
const opened = this.attr('afterOpened')
const $el = this.$el
const $parent = this.$parentElement
if (this.isInside()) {
removeClass($parent, HIDDEN)
removeClass($el, HIDDEN)
later(() => {
removeClass($parent, FOLDED)
removeClass($el, FOLDED)
}, 30)
} else {
removeClass($el, HIDDEN)
}
this.closed = false
if (isFunction(opened)) {
opened.call(this)
}
return this
}
hide() {
const FOLDED = 'outline-chapters_folded'
const HIDDEN = 'outline-chapters_hidden'
const closed = this.attr('afterClosed')
const $el = this.$el
const $parent = this.$parentElement
if (this.isInside()) {
addClass($parent, FOLDED)
addClass($el, FOLDED)
later(() => {
addClass($parent, HIDDEN)
addClass($el, HIDDEN)
})
} else {
addClass($el, HIDDEN)
}
this.closed = true
if (isFunction(closed)) {
closed.call(this)
}
return this
}
toggle() {
const afterToggle = this.attr('afterToggle')
const top = this.offsetTop
const scrollTop = this.$scrollElement.scrollTop
let isStickying
if (this.isClosed()) {
this.show()
} else {
this.hide()
}
if (isFunction(afterToggle)) {
later(() => {
isStickying = scrollTop >= top
afterToggle.call(this, this.isClosed(), isStickying)
})
}
return this
}
destroy() {
const beforeDestroy = this.attr('beforeDestroy')
const afterDestroy = this.attr('afterDestroy')
if (isFunction(beforeDestroy)) {
beforeDestroy.call(this)
}
this.removeListeners()
this.$parentElement.removeChild(this.$el)
this.attr(Chapters.DEFAULTS)._reset()
if (this.scrollTimer) {
clearTimeout(this.scrollTimer)
this.scrollTimer = null
}
if (this.resizeTimer) {
clearTimeout(this.resizeTimer)
this.resizeTimer = null
}
if (isFunction(afterDestroy)) {
afterDestroy.call(this)
}
if (this.Observer) {
this.Observer = null
}
return this
}
onObserver() {
const selector = this.attr('selector')
let timer = null
this.Observer = intersection(
($heading) => {
const id = $heading.getAttribute('data-id')
if (this.playing) {
return false
}
if (timer) {
clearTimeout(timer)
}
timer = later(() => {
this.highlight(id)
}, 100)
},
{
selector,
context: this
}
)
return this
}
onSelect(evt) {
const stickyHeight = this.attr('stickyHeight')
const $anchor = evt.delegateTarget
const id = $anchor.getAttribute('data-id')
const headingId = $anchor.href.split('#')[1]
const $heading = document.querySelector(`#${headingId}`)
const top = offsetTop($heading) - (stickyHeight + 10)
const min = 0
const max = this.$scrollElement.scrollHeight
const afterScroll = this.attr('afterScroll')
const after = () => {
if (isFunction(afterScroll)) {
afterScroll.call(this, 'chapter')
}
later(() => {
this.playing = false
this.$emit('toolbar:update', {
top,
min,
max
})
})
}
this.playing = true
if (this.isFixed()) {
this.sticky()
later(() => {
this.scrollTo(top, after)
this.highlight(id)
}, 10)
} else {
this.scrollTo(top, after)
this.highlight(id)
}
stop(evt)
return this
}
onScroll() {
const $scrollElement = this.$scrollElement
if (this.scrollTimer) {
clearTimeout(this.scrollTimer)
}
this.scrollTimer = later(() => {
const top = $scrollElement.scrollTop
const min = 0
const max = $scrollElement.scrollHeight - $scrollElement.clientHeight
if (this.isFixed()) {
this.sticky()
}
this.$emit('toolbar:update', {
top,
min,
max
})
}, 100)
return this
}
onResize() {
if (this.resizeTimer) {
clearTimeout(this.resizeTimer)
}
this.resizeTimer = later(() => {
this.calculateStickyHeight()
})
return this
}
addListeners() {
const $el = this.$el
const $scrollElement = this.$scrollElement
const tagName = $scrollElement.tagName.toLowerCase()
let $element = $scrollElement
if (tagName === 'html' || tagName === 'body') {
$element = window
}
on($el, '.outline-chapters__anchor', 'click', this.onSelect, this, true)
at($element, 'scroll', this.onScroll, this, true)
if (this.isSticky()) {
at(window, 'resize', this.onResize, this, true)
}
return this
}
removeListeners() {
const selector = this.attr('selector')
const $el = this.$el
const $scrollElement = this.$scrollElement
const tagName = $scrollElement.tagName.toLowerCase()
let $element = $scrollElement
if (tagName === 'html' || tagName === 'body') {
$element = window
}
off($el, 'click', this.onSelect)
off($element, 'scroll', this.onScroll)
if (this.isSticky()) {
at(window, 'resize', this.onResize)
}
if (this.Observer) {
document.querySelectorAll(selector).forEach((section) => {
this.Observer.unobserve(section)
})
}
return this
}
}
Chapters.DEFAULTS = {
parentElement: '',
scrollElement: '',
selector: '.outline-heading',
active: 0,
closed: false,
showCode: true,
position: 'relative',
stickyHeight: 0,
chapters: [],
created: null,
mounted: null,
afterClosed: null,
afterOpened: null,
afterScroll: null,
beforeDestroy: null,
afterDestroy: null,
afterSticky: null
}
export default Chapters
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。