2021-01-16 07:37:33
在阿里云的云市场页面上有一个效果,就是api导航栏当滚动条滚动到其所在位置时,自动吸顶,当滚动到下方所在导航栏指定的介绍时,自动高亮其导航栏。点击时则会滑动至其内容所在位置。具体效果为下图样式。
实现方法
正常情况下我们点击自动定位到其所在位置一般用id锚点的方式,但是这种方式有一个缺陷就是无法实现滚动条缓动效果,而且带url上还会通过hash的方式显示出ID,另外也无法实现滚动到内容所在位置自动高亮导航栏。
那么需要我们自己手动来实现以下,具体实现思路就是增加滚动条监听事件,当滚动到导航栏指定内容区域时,给其导航栏增加高亮样式,点击导航栏时,计算好滚动条的滚动距离,让其滚动过去即可。
代码实现
话不多说,我们直接来实现即可。
我这次采用的是react来写,具体思路都是相同的,无论你用的是vue还是angular 还是使用jq还是原生js,都是一样的。
首先要构建一个导航栏的数据结构,假设导航栏结构是这样的:
代码语言:javascript复制let navInfo = [
{
name:"产品说明",
id:"introduce",
content:"这是产品说明"
},
{
name:"使用指南",
id:"useFun",
content:"巴拉巴拉这是使用指南"
},
{
name:"售后服务",
id:"service",
content:"巴拉巴拉这是售后服务"
},
{
name:"产品参数",
id:"proCanshu",
content:"巴拉巴拉这是产品参数"
}
]
我们假设导航栏有四个导航,我们将这四个导航和内容渲染到页面上:
代码语言:javascript复制 function NavDemo(props){
const nav_content = useRef();//标识nav导航栏渲染内容
const [navList,setNavList] = useState(navInfo);//这里使用自行构建的导航栏
const [fixNav,setFixNav] = useState(false);//用户标识什么时候导航栏吸顶
const [activeNav,setActiveNav] = useState("");//与标识导航栏高亮
return <div>
<div className={"nav_list " (fixNav?"active":"") }>
<ul>
{navList.map(item=>{
return <li key={item.id}>
<a className={activeNav==item.id?"active":""}>{item.name}</a>
</li>
})}
</ul>
</div>
{fixNav && <div className="zhanfIx" />}
<div ref={nav_content}>
{navList.map(item=>{
{/*这里给ID加key字符串后缀是为了防止页面其他地方的ID重复*/}
return <div className="type_group" id={item.id "_key"} key={item.id item.name}>
<div className="type_title">{item.name}</div>
<div>{item.content}</div>
</div>}
</div>
</div>
}
好了,至此我们已经将内容和导航栏渲染好了,并且给内容部分增加ref,便于后续获取其内容,导航栏也增加何时吸顶的标识以及导航栏高亮的标识,另外增加了一个class为zhanfIx的地址,因为当导航栏吸顶时,此处会因为空出位置,下面内容上移,而产生不和谐的效果,我们需要在其吸顶的同时增加一个div来占位,以增加平滑的效果。
下面我们来看一下导航栏吸顶和滑动到指定位置导航栏高亮的逻辑。
代码语言:javascript复制useEffect(()=>{
//增加滚动条监听事件
document.addEventListener('scroll', scrollEventListener)
return ()=>{
//组件注销时去除监听事件
document.removeEventListener('scroll', scrollEventListener)
}
},[]);
let scrollEventListener = ()=>{
//获取导航栏显示内容区域信息
let nav_contentReact = nav_content.current.getBoundingClientRect();
//获取导航栏显示内容区域直接子元素
let groupList = Array.from(nav_content.current.children);
if(nav_contentReact){
groupList.map(item=>{
let itemReact = item.getBoundingClientRect();
if(itemReact.y<=60 && (itemReact.y itemReact.height) >60){
//当该子元素距离顶部小于等于60时,说明此时导航栏应该高亮,
//同时在其高度范围内均应高亮。
setActiveNav(item.id "_key")
}
})
//我们设定导航栏的高度是60px,导航栏占位高度同样是60px
if (nav_contentReact.y <= 60 ) { // 导航-吸顶
setFixNav(true);
} else if(nav_contentReact.y > 60 ){
setFixNav(false);
//当脱离其显示范围时,导航栏无需高亮
setActiveNav("")
}
}
}
方法和注释上面写的很清楚,并不是很复杂,原理就是通过ID找到当前视野内的内容属于哪个导航栏,便让其高亮即可。这样我们就实现了通过滚动条来控制导航栏高亮的效果了,接下了我们要实现的便是点击导航栏自动定位到其所在内容。
首先要做的一件事就是给导航栏增加一个点击事件
代码语言:javascript复制 <a className={activeNav==item.id?"active":""}
onClick={()=>navClick(item.id)} >{item.name}</a>
下面来看具体的实现逻辑
代码语言:javascript复制 //先定义两个变量
/*上一次滚动条距顶部位置,此变量是为了防止底部高度不够时,
无法定位到最下方,结果导致程序无限循环的bug,
通过下面的代码应该可以明白此变量的意义*/
let prevScrollTop = null;
let isToTop = false;//点击锚点时滚动条是向上还是向下
//导航栏点击事件
function navClick(id){
let groupList = Array.from(nav_content.current.children);
let selectItem = null;
//循环遍历,查找当前点击的是哪个导航,通过ID确定内容区域
groupList.map(item=>{
if(item.id==id){
selectItem = item
}
})
//获取所选导航指定内容区域位置信息
let outerItemReact = selectItem.getBoundingClientRect();
//判断导航内容是在可视区域上方还是下方,来决定滚动条是应该向上滚动还是向下滚动
isToTop = outerItemReact.y > 60;
//增加定时循环任务,控制速度逐渐变慢的效果来滚动滚动条。
const createTimer = setInterval(() => {
let itemReact = selectItem.getBoundingClientRect();
const top = document.documentElement.scrollTop || document.body.scrollTop;
//计算滚动速度及方向
let ispeed = itemReact.y < 60 ? -(Math.abs(itemReact.y)/8):(Math.abs(itemReact.y)/8)
//防止速度出现过慢情况,则指定最小速度
if(Math.abs(ispeed)<60/8){
ispeed = ispeed > 0 ? 60/8: -60/8;
}
if ( (isToTop && itemReact.y < 60) || (!isToTop && itemReact.y > 60) || prevScrollTop == 60) {
clearInterval(createTimer);
prevScrollTop = null;
document.documentElement.scrollTop = document.body.scrollTop = top (itemReact.y - 60)
} else {
prevScrollTop = top;
document.documentElement.scrollTop = document.body.scrollTop = top ispeed
}
}, 30)
}
整个功能到此就已经完全实现好了,实现了滚动条滚动时自动高亮导航栏,并超过导航栏位置自动吸顶效果,同时点击导航栏滚动条缓动至锚点位置,实现的最终效果可以看阿里云市场详情页中的效果,比他显示的效果多了滚动条缓动效果。上述的例子我没有贴出css样式,具体实现请大家自己动手写写即可。
如有bug欢迎大家指正。