遇到超长页面,给用户最好的体验就是在侧边栏加上一些标题的锚点。最近在写项目时就遇到过这种情况,思索在滚动页面时锚点也应该高亮,又根据自己的遇到的实际情况提出了三种锚点追踪方法。
- 顶点法;指滚动条移动的像素超过某一元素,就对该元素对应锚点高亮
- 最近法:指距离滚动条最近的标题锚点进行高亮
- 比例法:将所有元素的高度对总高度进行求比,根据滚动条滚动的比例对锚点高亮
顶点法
我们首先要思考一个问题,滚动条本身是有长度的,所以滚动条能滚动的长度必然小于整个页面的高度。所以当滚动条滚动到最底端时,我们也要对最后一个元素的锚点进行高亮。代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>顶点法</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<style>
.item-div{
height: 500px;
text-align: center;
}
h1{
line-height: 500px;
}
</style>
</head>
<body id="body">
<div class="container">
<div style="position: fixed;left: 0;">
<div class="list-group">
<a href="#item1" class="list-group-item active">
item1
</a>
<a href="#item2" class="list-group-item">item2</a>
<a href="#item3" class="list-group-item">item3</a>
<a href="#item4" class="list-group-item">item4</a>
<a href="#item5" class="list-group-item">item5</a>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" ></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
<script>
//组装测试的元素
var itemModel = ['#FCD97D','#84C9EF','#DD7E81','#246B69','#C7C9E0']
for (let i = 0; i < itemModel.length; i++) {
var index= i+1;
$('.container').append('<div class="item-div" style="background: '+itemModel[i]+'" id="item'+index+'"><h1>item'+index+'</h1></div>')
}
//滚动的监听方法
$(document).scroll(function (){
//当前滚动的距离
var current = document.documentElement.scrollTop;
//页面的总高度
var offsetHeight = document.documentElement.offsetHeight
//页面的可视高度
var clientHeight = document.documentElement.clientHeight
//offsetHeight - clientHeight得出的就是滚动条最大滚动的长度。
//跟当前滚动距离相比较,相等则直接高亮最后的锚点
if (current == offsetHeight - clientHeight){
$('a').removeClass('active');
$('a:last').addClass('active');
}else{
//对超过的最后一个元素锚点进行高亮
//注意要给予一个默认的高亮节点,也就是第一个
var divs = $('.item-div');
var index = 0;
for (let i = 0; i < divs.length; i++) {
if (current >= $(divs[i]).offset().top){
index = i;
}
}
$('a').removeClass('active');
$('a:eq('+index+')').addClass('active');
}
})
</script>
</html>
效果图:
顶点法的缺陷是什么?发现没有,在滚动到上一个元素的所有内容都消失后,锚点却还停留在上一个。只是因为滚动的位置没有达到后一个元素的顶点。这非常不科学,正确的做法是在上一个元素的大部分内容消失后我们就跳跃到下一个节点,这就是最近法
最近法
具体的做法就是比较元素的顶点和当前滚动距离的差值,并取绝对值。高亮最小绝对值的元素对应锚点。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>最近法</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<style>
.item-div{
height: 500px;
text-align: center;
}
h1{
line-height: 500px;
}
</style>
</head>
<body id="body">
<div class="container">
<div style="position: fixed;left: 0;">
<div class="list-group">
<a href="#item1" class="list-group-item active">
item1
</a>
<a href="#item2" class="list-group-item">item2</a>
<a href="#item3" class="list-group-item">item3</a>
<a href="#item4" class="list-group-item">item4</a>
<a href="#item5" class="list-group-item">item5</a>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" ></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
<script>
//组装测试的元素
var itemModel = ['#FCD97D','#84C9EF','#DD7E81','#246B69','#C7C9E0']
for (let i = 0; i < itemModel.length; i++) {
var index= i+1;
$('.container').append('<div class="item-div" style="background: '+itemModel[i]+'" id="item'+index+'"><h1>item'+index+'</h1></div>')
}
//滚动的监听方法
$(document).scroll(function (){
//当前滚动的距离
var current = document.documentElement.scrollTop;
//页面的总高度
var offsetHeight = document.documentElement.offsetHeight
//页面的可视高度
var clientHeight = document.documentElement.clientHeight
//offsetHeight - clientHeight得出的就是滚动条最大滚动的长度。
//跟当前滚动距离相比较,相等则直接高亮最后的锚点
if (current == offsetHeight - clientHeight){
$('a').removeClass('active');
$('a:last').addClass('active');
}else{
//运算出绝对值,并进行比较,最终找到最小的那个值
var divs = $('.item-div');
var index = 0;
var min = offsetHeight;
for (let i = 0; i < divs.length; i++) {
var diff = Math.abs($(divs[i]).offset().top - current);
if (min >= diff){
min = diff;
index = i;
}
}
$('a').removeClass('active');
$('a:eq('+index+')').addClass('active');
}
})
</script>
</html>
效果图:
比例法
以上做的实现都是建立在元素内容的高度比较高,具体的标准是一个可见页的高度最多显示2个元素。当超过2个元素会发生什么,将 .item-div
的height
改成350
,h1
的line-height
改成350
。你会发现item4
锚点是永远无法高亮的。在页面底端的可视高度内超过两个元素就会触发这个BUG,包括就近法。
如图,元素高度改为350px
的顶点法:
如何解决此问题,可以观察到主要问题的是滚动条的滚动距离并不等于页面的高度,有些元素的我们是触碰不到的。我们可以把滚动条滚动视为一个比例,将这个比例映射到总页面的高度,这样的话每一个元素都能被触碰。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>比例法</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<style>
.item-div{
height: 350px;
text-align: center;
}
h1{
line-height: 350px;
}
</style>
</head>
<body id="body">
<div class="container">
<div style="position: fixed;left: 0;">
<div class="list-group">
<a href="#item1" class="list-group-item active">
item1
</a>
<a href="#item2" class="list-group-item">item2</a>
<a href="#item3" class="list-group-item">item3</a>
<a href="#item4" class="list-group-item">item4</a>
<a href="#item5" class="list-group-item">item5</a>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" ></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
<script>
//组装测试的元素
var itemModel = ['#FCD97D','#84C9EF','#DD7E81','#246B69','#C7C9E0']
for (let i = 0; i < itemModel.length; i++) {
var index= i+1;
$('.container').append('<div class="item-div" style="background: '+itemModel[i]+'" id="item'+index+'"><h1>item'+index+'</h1></div>')
}
//滚动的监听方法
$(document).scroll(function (){
//当前滚动的距离
var current = document.documentElement.scrollTop;
//页面的总高度
var offsetHeight = document.documentElement.offsetHeight
//页面的可视高度
var clientHeight = document.documentElement.clientHeight
//滚动条可活动的总高度
var activeHeight = offsetHeight - clientHeight;
//当没有滚动条时的高亮第一个
if (activeHeight == 0){
$('a').removeClass('active');
$('a:first').addClass('active');
}else{
//经过转换后的滚动条距离
var transformTop = current / activeHeight * offsetHeight;
//对超过的最后一个元素锚点进行高亮
//注意要给予一个默认的高亮节点,也就是第一个
var divs = $('.item-div');
var index = 0;
for (let i = 0; i < divs.length; i++) {
if (transformTop >= $(divs[i]).offset().top){
index = i;
}
}
$('a').removeClass('active');
$('a:eq('+index+')').addClass('active');
}
})
</script>
</html>
效果图:
还没完,当你点击锚点时会发现跳转后锚点的定位并不准确。这是因为锚点跳转的是元素的顶点,所以我们要对锚点跳转做一个特殊的处理,使滚动的距离正确。
具体的做法是在点击事件里进行计算,通过将元素的顶点距离和页面的总高度求比,再用这个比值乘于滚动条可以活动的长度,就是最终跳转的位置。又因为不是跳转到顶点,有时候元素的位置会非常的靠底部,可以加上元素本身的高度使其尽量靠前,但是这个值不能超过元素的顶点。修改后的代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>比例法</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<style>
.item-div{
height: 350px;
text-align: center;
}
h1{
line-height: 350px;
}
</style>
</head>
<body id="body">
<div class="container">
<div style="position: fixed;left: 0;">
<div class="list-group">
<a ref="item1" class="list-group-item active">
item1
</a>
<a ref="item2" class="list-group-item">item2</a>
<a ref="item3" class="list-group-item">item3</a>
<a ref="item4" class="list-group-item">item4</a>
<a ref="item5" class="list-group-item">item5</a>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" ></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
<script>
//组装测试的元素
var itemModel = ['#FCD97D','#84C9EF','#DD7E81','#246B69','#C7C9E0']
for (let i = 0; i < itemModel.length; i++) {
var index= i+1;
$('.container').append('<div class="item-div" style="background: '+itemModel[i]+'" id="item'+index+'"><h1>item'+index+'</h1></div>')
}
//滚动的监听方法
$(document).scroll(function (){
//当前滚动的距离
var current = document.documentElement.scrollTop;
//页面的总高度
var offsetHeight = document.documentElement.offsetHeight
//页面的可视高度
var clientHeight = document.documentElement.clientHeight
//滚动条可活动的总高度
var activeHeight = offsetHeight - clientHeight;
//当没有滚动条时的高亮第一个
if (activeHeight == 0){
$('a').removeClass('active');
$('a:first').addClass('active');
}else{
//经过转换后的滚动条距离
var transformTop = current / activeHeight * offsetHeight;
//对超过的最后一个元素锚点进行高亮
//注意要给予一个默认的高亮节点,也就是第一个
var divs = $('.item-div');
var index = 0;
for (let i = 0; i < divs.length; i++) {
if (transformTop >= $(divs[i]).offset().top){
index = i;
}
}
$('a').removeClass('active');
$('a:eq('+index+')').addClass('active');
}
})
/**
* 点击锚点跳转,有一点要注意的是要让跳转的元素尽量靠前
*/
$('.list-group-item').click(function (){
//页面的总高度
var offsetHeight = document.documentElement.offsetHeight
//页面的可视高度
var clientHeight = document.documentElement.clientHeight
//滚动条可活动的总高度
var activeHeight = offsetHeight - clientHeight;
//获取元素
var offsetEle =$('#'+$(this).attr('ref'));
//元素所在的高度
var itemTop = offsetEle.offset().top
//计算元素对应滚动条的y值,加上元素本身的长度保证元素尽量靠前
var y = (itemTop+offsetEle.height())/offsetHeight*activeHeight
//最终滚动的位置如果大于元素的顶点时使其相等,这一步是为了防止过长的元素直接跳转到尾部
if (y > itemTop)y = itemTop;
window.scroll(document.documentElement.offsetWidth,Math.floor(y))
})
</script>
</html>
总结
顶点法和就近法相差不大,就近法更符合我们阅读的逻辑,一般情况这两种方法完全够用。比例法运用于元素的高度差距过大的情形,在页面可视高度包括两个以上的元素可以使用。