组件
<template>
<div class="tkCatalogue">
<CataItem
v-for="(item, index) in anchorList"
:key="item.id"
:cataData="item"
:currentTarget="currentTarget"
@toAnchor="toAnchor"
/>
</div>
</template>
<script>
import CataItem from './cataItem.vue';
import { throttle } from 'lodash';
export default {
name: 'TkCatalogue',
components: {
CataItem
},
props: {},
data() {
return {
currentTarget: '',
anchorList: [],
tagList: [],
mutationObserver: null
};
},
mounted() {
this.observer();
},
beforeDestroy() {
window.onscroll = null;
if (this.mutationObserver) {
this.mutationObserver.disconnect();
this.mutationObserver = null;
}
},
watch: {
// anchorList: {
// handler: function(val) {
// this.currentTarget = (val && val[0]?.id) || '';
// },
// immediate: true
// }
},
methods: {
// 监听dom节点变化
observer() {
const haoroomsId = document.getElementById('root');
if (!haoroomsId) {
setTimeout(() => {
this.observer();
}, 200);
return;
}
// haoroomsId.addEventListener('scroll', this.handleScroll, true);
const MutationObserver =
window.MutationObserver ||
window.webkitMutationObserver ||
window.MozMutationObserver;
let recordHeight = 0;
const selfGetNodeList = this.getNodeList;
this.mutationObserver = new MutationObserver(function(mutations) {
const height = window
.getComputedStyle(haoroomsId)
.getPropertyValue('height');
if (height === recordHeight) {
return;
}
recordHeight = height;
selfGetNodeList();
});
this.mutationObserver.observe(haoroomsId, {
childList: true, // 子节点的变动(新增、删除或者更改)
characterData: true, // 节点内容或节点文本的变动
subtree: true // 是否将观察器应用于该节点的所有后代节点
});
},
getNodeList() {
this.tagList = Array.from(document.getElementsByClassName('anchorTitle'));
this.anchorList = this.tagList.map((item, index) => {
return {
content: item.childNodes[0].innerText,
id: index,
instance: item,
offsetTop: item.offsetTop
};
});
this.currentTarget = (this.anchorList && this.anchorList[0]?.id) || '';
},
handleScroll() {
const haoroomsId = document.getElementById('app');
// this.getNodeList();
const scrollTop = haoroomsId.pageYOffset || haoroomsId.scrollTop;
const clientHeight =
document.documentElement.clientHeight || document.body.clientHeight;
const scrollHeight =
document.documentElement.scrollHeight || document.body.scrollHeight;
for (let index = 0; index < this.anchorList.length; index++) {
const item = this.anchorList[index];
// 如果是第一个节点就直接显示第一个
if (scrollTop <= this.anchorList[0].offsetTop) {
this.currentTarget = this.anchorList[0].id;
} else if (
scrollHeight > clientHeight &&
scrollTop + clientHeight === scrollHeight
) {
// 如果到底了直接显示最后一个
this.currentTarget = this.anchorList[this.anchorList.length - 1].id;
} else if (this.anchorList[index + 1]) {
if (
scrollTop >= item.offsetTop - 50 &&
scrollTop < this.anchorList[index + 1].offsetTop - 50
) {
this.currentTarget = item.id;
break;
}
} else {
this.currentTarget = item.id;
}
}
},
toAnchor(id) {
this.currentTarget = id;
}
}
};
</script>
<style lang="scss" scoped>
.tkCatalogue {
background: #ffffff;
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.2);
border-radius: 4px;
position: fixed;
right: 50px;
bottom: 15%;
padding: 20px 20px 20px 14px;
z-index: 999999;
}
</style>
子组件
<template>
<div class="cataItem" @click="toAnchor">
<div
:class="
currentTarget == cataData.id
? 'currentTarget contentWrap'
: 'contentWrap'
"
>
<div class="circle">
<div class="realCircle"></div>
</div>
<p class="context">{{ cataData.content }}</p>
</div>
<div class="coluGray"></div>
</div>
</template>
<script>
export default {
name: 'CataItem',
props: {
cataData: {
type: Object,
default: {}
},
currentTarget: {
type: String,
default: 0
}
},
methods: {
toAnchor() {
const id = this.cataData.id;
this.cataData?.instance?.scrollIntoView({
behavior: 'smooth'
});
this.$emit('toAnchor', id);
}
}
};
</script>
<style lang="scss" scoped>
p {
margin: 0;
}
.cataItem {
padding-bottom: 12px;
position: relative;
&:last-child {
padding-bottom: 0;
.coluGray {
display: none;
}
}
.currentTarget {
.realCircle {
width: 12px !important;
height: 12px !important;
background: #ff7a2a !important;
}
.context {
color: #ff7a2a !important;
}
}
.contentWrap {
display: flex;
align-items: center;
cursor: pointer;
.circle {
width: 12px;
height: 12px;
display: flex;
align-items: center;
justify-content: center;
.realCircle {
width: 8px;
height: 8px;
border-radius: 50%;
background: #fad6b2;
}
}
.context {
ont-size: 14px;
color: #333333;
font-weight: 400;
margin-left: 4px;
padding: 6px 8px;
&:hover {
background: #f2f2f3;
border-radius: 2px;
}
}
}
.coluGray {
width: 2px;
height: 36px;
background-color: #d7d8da;
position: absolute;
left: 5px;
top: 19px;
z-index: -1;
}
}
</style>
使用方式
组件引入
import TkCatalogue from '@components/tk-catalogue';
<TkCatalogue />
要定位的段落标题添加class anchorTitle