之前分享过一篇通过文章内容标题生成目录实现锚点定位的实现 juejin.cn/post/703738…
最近遇到同样展示文章详情并需要展示目录的需要,尝试使用不同的方式来实现,这次利用DocumentFragment来渲染目录,以此分享下
关于 DocumentFragment ,引用自MDN的说法
DocumentFragment,文档片段接口,表示一个没有父级文件的最小文档对象。它被作为一个轻量版的Document使用,用于存储已排好版的或尚未打理好格式的XML片段。最大的区别是因为DocumentFragment不是真实DOM树的一部分,它的变化不会触发DOM树的(重新渲染) ,且不会导致性能等问题。
可以使用document.createDocumentFragment方法或者构造函数来创建一个空的DocumentFragment
从MDN的说明中,我们得知DocumentFragments是DOM节点,但并不是DOM树的一部分,可以认为是存在内存中的,所以将子元素插入到文档片段时不会引起页面回流。
这个是之前生成文章目录节点的方式
<div class="aside-body">
<ul class="aside-article-catalog">
<li v-for="(item,index) in docMenu" :key="item.id" :class="`level_${item.level}`">
<a :href="'#' + item.id" :class="{active: active === index }" @click="handlerSroll($event, item.id,index)">{{ item.text }}</a>
</li>
</ul>
</div>
initArt() {
let markMenu = []
setTimeout(() => {
const articleDom = document.querySelector('.article')
if (articleDom) {
for (let ele of articleDom.children) {
const i = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].indexOf(ele.tagName)
if (i > -1 && ele.textContent) {
ele.setAttribute('id', 'markMenu_' + markMenu.length)
ele.setAttribute('name', 'markMenu_' + markMenu.length)
markMenu.push({
level: i,
text: ele.textContent,
id: 'markMenu_' + markMenu.length,
name:'markMenu_' + markMenu.length
})
}
}
}
//docMenu为目录数据
this.docMenu = markMenu
})
},
使用DocumentFragment的方式来实现同样的效果,利用DocumentFragment将获取每个标题标签创建节点,生成文档片段最后一次性插入到文档中,而这个操作仅发生一个重渲染的操作,它的变化不会引起DOM树的重新渲染的操作(reflow) ,且不会导致性能等问题
<!-- 目录部分 -->
<div
class="aside-body"
v-html="html"
@click.prevent="handlerSroll">
</div>
// 通过
generateCatalogue() {
let fragment = document.createDocumentFragment()
const element = document.querySelector('.article')
let catalogueRoot = '<div id="side-article-catalog">'
const childrenNodes = element
? this.getTitleNodeList(element).filter((node) => node.innerText)
: []
childrenNodes.map((node, index) => {
const catalogueNode = `<div class="level_${node.tagName}" id="level_${node.tagName}_${index}" title="${node.innerText}"><a href="#${node.id}">${node.innerText}</a></div>`
catalogueRoot += catalogueNode
})
catalogueRoot += '</div>'
fragment.innerHTML = catalogueRoot
return fragment.innerHTML
},
getTitleNodeList(element) {
if (element.children) {
const menuList = element.querySelectorAll('h1,h2,h3,h4,h5,h6')
for (let i = 0; i < menuList.length; i++) {
const markMenuClass = Math.random().toString(36).slice(-8)
menuList[i].setAttribute('id', `markMenu_${markMenuClass}`)
}
return Array.from(menuList)
} else {
return []
}
},
最后一步就是点击跳转的操作,这里利用a标签的href属性达到锚点的效果,也是最关键的一步
handlerSroll(event) {
const { target } = event
const anchor = target.getAttribute('href')
if (!anchor) return
const anchorElement = document.querySelector(anchor)
const scrollConfig = {
behavior: 'smooth',
block: 'start',
inline: 'nearest'
}
if (target && target.scrollIntoView) {
anchorElement.scrollIntoView(scrollConfig)
}
}