1 Star 0 Fork 34

堪培拉的风/outline.js

forked from Yaohaixiao/outline.js 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
anchors.js 6.38 KB
一键复制 编辑 原始数据 按行查看 历史
import isString from './utils/types/isString'
import isFunction from './utils/types/isFunction'
import isElement from './utils/types/isElement'
import cloneDeep from './utils/lang/cloneDeep'
import timeSlice from './utils/lang/timeSlice'
import toTree from './utils/lang/toTree'
import later from './utils/lang/later'
import scrollTo from './utils/dom/scrollTo'
import _getScrollElement from './utils/dom/_getScrollElement'
import offsetTop from './utils/dom/offsetTop'
import on from './utils/event/on'
import off from './utils/event/off'
import stop from './utils/event/stop'
import paint from './utils/icons/paint'
import _updateHeading from './_updateHeading'
import _removeHeading from './_removeHeading'
import getChapters from './getChapters'
import Base from './base'
class Anchors extends Base {
constructor(options) {
super()
this._default()
if (options) {
this.initialize(options)
}
}
_default() {
this.attrs = cloneDeep(Anchors.DEFAULTS)
this.$articleElement = null
this.$scrollElement = null
this.$headings = []
this.chapters = []
return this
}
initialize(options) {
const showCode = this.attr('showCode') || true
let created
let scrollElement
let selector
let $articleElement
let articleElement
this.attr(options)
articleElement = this.attr('articleElement')
scrollElement = this.attr('scrollElement')
selector = this.attr('selector')
created = this.attr('created')
if (isString(articleElement)) {
$articleElement = document.querySelector(articleElement)
} else if (isElement(articleElement)) {
$articleElement = articleElement
}
if (!$articleElement) {
return this
}
this.$articleElement = $articleElement
this.$scrollElement = _getScrollElement(scrollElement)
this.$headings = [...$articleElement.querySelectorAll(selector)]
if (this.$headings.length < 1) {
return this
}
this.chapters = getChapters(
this.$headings,
showCode,
this.attr('chapterTextFilter')
)
if (isFunction(created)) {
created.call(this)
}
this.render().addListeners()
return this
}
getChapters(isTreeStructured = false) {
const chapters = this.chapters
return isTreeStructured ? toTree(chapters, 'id', 'pid') : chapters
}
count() {
return this.chapters.length
}
render() {
const mounted = this.attr('mounted')
paint()
this._paint(this.chapters)
if (isFunction(mounted)) {
mounted.call(this)
}
return this
}
erase() {
this._remove()
return this
}
_paint(chapters) {
const LIMIT = 400
const hasAnchor = this.attr('hasAnchor')
const isAtStart = this.attr('isAtStart')
const showCode = this.attr('showCode')
const anchorURL = this.attr('anchorURL')
const count = this.count()
const $headings = [...this.$headings]
const update = (headings, group) => {
headings.forEach(($heading, i) => {
const id = i + group * LIMIT
const chapterCode = chapters[id].code
_updateHeading($heading, id, {
hasAnchor,
isAtStart,
showCode,
chapterCode,
anchorURL
})
})
}
let groupIndex = -1
// 针对超长的文章,进行 timeSlice 处理
if (count > LIMIT) {
groupIndex += 1
// 同步绘制 Limit 以内的标题链接(可以确保 50ms 完成绘制)
update($headings.splice(0, LIMIT), 0)
// 采用 timeSlice 处理机制绘制剩余的标题
while ($headings.length > 0) {
const once = $headings.splice(0, LIMIT)
timeSlice(
() => {
update(once, (groupIndex += 1))
},
() => {
this.$emit('anchors:all:paint')
}
)
}
} else {
update($headings, 0)
}
return this
}
_remove() {
const hasAnchor = this.attr('hasAnchor')
const isAtStart = this.attr('isAtStart')
const $headings = this.$headings
$headings.forEach(($heading) => {
_removeHeading($heading, hasAnchor, isAtStart)
})
return this
}
refresh(chapters) {
const $articleElement = this.$articleElement
const selector = this.attr('selector')
this.$headings = [...$articleElement.querySelectorAll(selector)]
this.chapters = chapters
this.erase()._paint(chapters)
return this
}
scrollTo(top, after) {
const el = this.$scrollElement
scrollTo(el, top, after)
return this
}
destroy() {
const beforeDestroy = this.attr('beforeDestroy')
const afterDestroy = this.attr('afterDestroy')
if (isFunction(beforeDestroy)) {
beforeDestroy.call(this)
}
this.removeListeners()._remove()._default()
if (isFunction(afterDestroy)) {
afterDestroy.call(this)
}
return this
}
addListeners() {
const $articleElement = this.$articleElement
if (this.count() < 1) {
return this
}
on(
$articleElement,
'.outline-heading__anchor',
'click',
this.onAnchorTrigger,
this,
true
)
return this
}
removeListeners() {
const $articleElement = this.$articleElement
if (this.count() < 1) {
return this
}
off($articleElement, 'click', this.onAnchorTrigger)
return this
}
onAnchorTrigger(evt) {
const anchorURL = this.attr('anchorURL')
const afterScroll = this.attr('afterScroll')
const stickyHeight = this.attr('stickyHeight')
const $anchor = evt.delegateTarget
const $heading = $anchor.parentNode
const top = offsetTop($heading) - (stickyHeight + 10)
const $scrollElement = this.$scrollElement
const min = 0
const max = $scrollElement.scrollHeight - $scrollElement.clientHeight
const after = () => {
if (isFunction(afterScroll)) {
afterScroll.call(this, 'anchor')
}
later(() => {
this.$emit('toolbar:update', {
top,
min,
max
})
})
}
this.scrollTo(top, after)
if (!anchorURL) {
stop(evt)
}
return this
}
}
Anchors.DEFAULTS = {
scrollElement: 'html,body',
articleElement: '#article',
selector: 'h1,h2,h3,h4,h5,h6',
stickyHeight: 0,
anchorURL: '',
hasAnchor: true,
isAtStart: true,
showCode: false,
created: null,
mounted: null,
afterScroll: null,
beforeDestroy: null,
afterDestroy: null,
chapterTextFilter: null
}
export default Anchors
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/lelezr/outline.js.git
git@gitee.com:lelezr/outline.js.git
lelezr
outline.js
outline.js
master

搜索帮助

0d507c66 1850385 C8b1a773 1850385