导航栏滚动吸顶并自动高亮和点击跳转锚点

2021-01-20 10:06:01 浏览数 (1)

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欢迎大家指正。

0 人点赞