这是我参与「第四届青训营 」笔记创作活动的第5天
仿掘金项目之实现文章预览|青训营笔记 - 掘金 (juejin.cn)
仿掘金项目之文章目录实现|青训营笔记 - 掘金 (juejin.cn)
书接上文
前面我已经实现了目录的基本功能,点击跳转和点击跳转时高亮当前目录
但是当我们通过鼠标滚轮滚动时,目录高亮内容并不会跟着页面内容变化,接下来就是解决这个问题
我们要通过当前页面距离顶部的距离和h标签距离顶部的距离相比较,来判断该高亮哪一个目录内容
获取当前页面距离顶部的距离
const scroll = () => {
window.addEventListener(
"scroll",
(fun = () => {
if (timer) {
return;
}
timer = setTimeout(() => {
let _scrollTop =
window.scrollY ||
window.pageYOffset ||
document.documentElement.scrollTop;
height.value = _scrollTop + 100;
timer = null;
}, 500);
})
);
};
这里我监听scroll事件,当页面滚动时就去获取当前页面距离顶部的距离
这里fun是一个变量名,指向srcoll的回调函数,用于页面销毁时,销毁srcoll事件
height是一个响应式数据,保存当前页面距离顶部的距离,这里我加了100是因为掘金上滚动不是到可视区域的顶部才改变高亮内容.这里有一段距离的
这里我用了节流来控制计算距离顶部的次数,如果不控制的话,会浪费性能,而且频繁计算也没多大用处
所以为啥不用防抖呢?
其实我一开始用的也是防抖,但是我在测试的时候发现,防抖用简单的话讲,事件被触发之后只会执行最后一次.
这里不妨想象一下,如果我不停的滚动,页面的h标签已经过了好几个了,但是你会发现目录还是一开始的那个内容高亮,等你停下来之后,还得过个500ms(我这里是500ms)才能高亮当前目录.而且还是从一开始的高亮内容直接跳到当前内容,这明显没有我们想要的滚动高亮效果
使用节流,简单的讲就是事件被触发后一段时间内只执行第一次 同样的场景,我不停的滚动,但是只要我滚动了,在接下来的500ms之后,我就能获取一次当前页面距离顶部的值.所以我不停的滚动并不会影响我每500ms获取一次值.值的变化可以带来目录高亮内容更新
封装scroll事件
因为后面我还写了回到顶部这个小功能,在这个功能中,只有页面距离顶部一定距离之后才会显示回到顶部的按钮,所以这里也要使用scroll事件,这个项目中还有不少地方要使用scroll事件,所以我干脆提取出来封装一下,免得重复写.
节流函数
export function throttle(func, delay) {
let timer;
return function () {
if (timer) return
timer = setTimeout(() => {
func.call(this, ...arguments);
timer = null
}, delay);
}
}
滚动事封装
import { throttle } from "./throttle"
export const windowScroll = (ref) => {
let resFun
window.addEventListener(
"scroll",
(resFun = throttle(function () {
ref.value =
window.scrollY ||
window.pageYOffset ||
document.documentElement.scrollTop;
}, 500)
)
)
return resFun
}
使用和销毁
onMounted(() => {
resFun = windowScroll(curHeight)
})
onUnmounted(() => {
window.removeEventListener('scroll',resFun)
})
这里要传一个响应式数据,返回的是scroll的回调函数,用于销毁监听事件
获取h标签距离顶部的距离
首先我们要拿到文章内容部分的所有h标签元素.
因为一开始我并不知道到querySelectorAll('h1,h2,h3,h4,h5,h6')这种写法,所以我是在前面给所有h标签包裹一层div的同时,给div都加了同一个类名jump-site,用来获取所有h标签
toc.forEach((item, index) => {
let _toc = `<div class="jump-site" id=${index}>${item}</div>`;
data = data.replace(item, _toc);
});
这样我就可以获取页面中所有h标签距离顶部的位置
const getHtagHeight = () => {
let tag = document.querySelectorAll(".jump-site");
let arr = [];
for (let i = 0; i < tag.length; i++) {
arr.push(tag[i].offsetTop);
}
hTagHeight.value = arr;
};
判断目录高亮内容
const activeScroll = () => {
let arr = hTagHeight.value;//所有h标签距离顶部的距离的数组
if (arr[0] > height.value) return (activeIndex.value = 0);
//height.value 当前页面距离顶部的距离
else if (arr[arr.length - 1] < height.value) {
activeIndex.value = arr.length - 1;
}
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] < height.value && arr[i + 1] > height.value) {
return (activeIndex.value = i);
}
}
};
这里我已经得到了当前页面距离顶部的距离和所有h标签距离顶部的距离
当前页面小于第一个h标签时,高亮第一个目录内容,大于最后一个标签时,高亮最后一个目录内容
当前页面在位于第i个h标签和第i+1之间时,显示第i个标签对应的目录内容
在滚动事件的回调函数中调用该函数,每次滚动都会执行,通过修改activeIndex的值,给目录中的li元素添加active类来改变样式呈现高亮效果
到这里最基本的目录滚动高亮效果就实现了.
但是...真的这么简单吗?
仔细观察这两张图,除了内容不同,还有啥不一样?
答案是滚动条
如果目录的内容过多,超出一定范围,就只能隐藏一部分. 正是因为内容过多,按照前面的写法,滚动高亮效果有一个大bug,这也是最麻烦的部分,下次再说.