前言
吸顶导航是营销会场类最常用的组件之一, 现在的会场页面是越来越长,如果从第一屏手动滑到最后一屏,还是一个挺累的操作,所以吸顶导航还是很有必要存在的,组件很常见,但是开源的不多,而且大多是PC版,几乎都不能满足业务的需求,所以决定自己写一个。
先看下组件效果 demo
功能拆解
梳理下组件需要实现的功能
- 到达首层吸顶和最后一层取消吸顶
- 当前楼层高亮显示
- 选中导航居中显示
- 默认显示或滑到首层才显示
- 滑动过程中控制隐藏显示
- 展开显示更多
功能实现
下面我会介绍下其中几个功能的实现方法,全部源码有兴趣的话可以点击这里
导航选中居中
1. 如何居中
首先我们可以先考虑怎么居左,我们知道每一项距离左边的宽度是m
,那居左就是-m
,居中就是再减中线的位置,中线的位置如果是M
,那就是M-m
。
2. 处理边界的情况
通过M-m
,我们再来处理到达边界的问题,主要两种情况
1.当M-m>0的时候,则已经到达最左边
2.当M-m >于可滚动的距离(滚动条长度-可视长度),就是到达最右边
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
/*以下代码为了方便理解,略有删减*/
/* * 导航切换 */ watch(){ translateX(value){ //滚动条位置修改 this.scrollView.scrollLeft = Math.abs(value) } }, methods:{ center(index){ //当前选中项 const activeItem = this.$refs.navitem[index] //选中项距离左边的距离和宽度 const {offsetLeft,offsetWidth} = activeItem //导航条可见的宽度 const touchWidth = this.stickyNav.offsetWidth //可滚动宽度 = 整个滚动宽度 - 导航条可见的宽度 const scrollWidth = this.scrollView.scrollWidth - touchWidth //导航条中点 const half = (touchWidth - offsetWidth) / 2 //需要滚动的长度 let scrollLeft = half - offsetLeft //到达最左边 scrollLeft > 0 && (scrollLeft = 0); //到达最右边 scrollLeft < -scrollWidth && (scrollLeft = -scrollWidth) this.translateX = scrollLeft } }
|
导航缓动
实现了导航居中后我们再给他加一个缓动的效果,上面已经通过监听滚动的值去修改滚动条scrollLeft改变位置,由于watch可以监听值的变化,我们可以取到初始值和结束值,所以我们只需给数字变化添加一个缓动的过程,这里使用了一个插件tweenjs来实现这个功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
import tween from '@tweenjs/tween.js" watch:{ translateX(star, end) { this.tween(star,end) } } methods:{ tween(start,end){ new TWEEN.Tween({ number: start }) .to({ number: end }, 100) .onUpdate(tween => { //改变滚动位置 this.scrollView.scrollLeft = -tween.number;
}) .start(); function animate(){ if (TWEEN.update()) { requestAnimationFrame(animate) } } animate() } }
|
滚动过程中的隐藏和显示
实现这个功能我们需要知道用户当前的操作是上划还是下划,同样借助于vue中的watch功能,我们监听当前屏幕滚动的距离scrollTop
,可以得到一个当前值和过去值,将两个值对比,当前值大于过去值的时候,则表示用户手指是向上滑(屏幕往下滚动)的,反之向下,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
/*以下代码为了方便理解,略有删减*/
data(){ //控制导航是否显示隐藏的变量 scrollHide:false, //需要设置一个定时器,当用户一段时间没操作的时候,显示导航条 scrollTimer:false }, watch:{ scrollTop(newValue, oldValue){ const delay = 2000 //向下滚动 if(newValue > oldvalue){ //改变属性,控制隐藏显示 this.scrollHide = true //清除定时器 clearTimeout(this.scrollTimer) this.scrollTimer = null //向上 }else{ this.scrollHide = fasle } if(!this.scrollTimer){ this.scrollTimer = setTimeout(()=>{ this.scrollHide = fasle }) } } }
|
向下滚动隐藏的功能是实现了,但还有一个问题,就是当点击导航栏的时候页面也是向下滚的,这时候还会触发上面的函数,这个时候体验效果有点奇怪,所以还需要优化下,当用户的操作是点击屏幕的时候不去执行隐藏导航的功能
改进版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
/*以下代码为了方便理解,略有删减*/
data(){ //控制导航是否显示隐藏的变量 scrollHide:false, //需要设置一个定时器,当用户一段时间没操作的时候,显示导航条 scrollTimer:false, //是否点击事件 isClickScroll: false }, methods:{ //点击时触发 change(index) { this.isClickScroll = true; } }, watch:{ scrollTop(newValue, oldValue){ if(this.isClickScroll){ setTimeout(() => { this.isClickScroll = false }, 10); } if (this.isClickScroll) return; const delay = 2000 //向下滚动 if(newValue > oldvalue){ //改变属性,控制隐藏显示 this.scrollHide = true; //清除定时器 clearTimeout(this.scrollTimer); this.scrollTimer = null; //向上 }else{ this.scrollHide = fasle; } if(!this.scrollTimer){ this.scrollTimer = setTimeout(()=>{ this.scrollHide = fasle; this.isClickScroll = false; }) } } }
|
遇到的一些问题
京东APP沉浸式兼容问题
沉浸式效果:
沉浸式就是去掉了首屏标题栏的一种沉浸式体验,,如果开启了沉浸式,那么首屏标题栏是一个透明的状态,整个页面的高度就会上移,然后当你往下滑动的时候标题栏会出现,这时候导航栏如果吸顶,那么就会被标题栏给挡住看不到了,解决方法就是需要增加导航栏距离顶部的高度,而且是动态修改的,因为在APP中获取标题栏的高度是一个异步的操作,原先组件中并没考虑需要动态修改高度的情况,所以需要点小修改,先看下一开始是怎么初始化组件的:
1 2 3 4 5 6 7 8
|
<StickyNav :options="options"/>
options:{ disabled:false, stickyTop:0, //距离顶部 zIndex:1000, ... }
|
我们是通过stickyTop属性来控制导航栏距离顶部的距离,但是如果异步去修改这个对象的值是没有任何变化的,因为vue中是无法检测到对象的修改,
1.通过watch的deep属性,设置为true可以监听options对象的修改,再重新复制到新对象
1 2 3 4 5 6 7 8
|
watch{ options:{ handler(value){ assign(this.stickyOptions,value) }, deep:true } }
|
2.或者把stickyTop单独作为一个prop属性传给组件,这样可以实时变化
低端机兼容性问题
兼容性问题通常出现在一些很低端的手机上,比如android4.0,ios8、不过如果做到以下3点基本也没什么问题
1.ES6兼容
通常我们webpack上已经配置了babel转换,但其实只是对语法的编译,比如你可以使用箭头函数等
如果你使用了Promise、Object.assign、includes等全局方法其实都不能被转换的,最简单的方法可以全局引入polyfill
1 2
|
npm install babel-polyfill --save import 'babel-polyfill'
|
或者你的项目中只是用了一两个方法,引入整个polyfill太浪费,也可以使用一些第三方库,如 lodash/includes
2.CSS自动 -webkit- 前缀
还有就是样式不生效的问题,一般我们现在都是在webpack工程中配置autoprefixer
去自动加前缀,不过要注意修改下package.json下的browserslist
1 2 3 4
|
"browserslist": [ "Android >= 4.4", "iOS 8" ]
|
3.尽量不要使用flex布局
flex布局有某些很老的机型还是支持不是很好,用inline-block
来代替
结束
本文到这里就结束啦,组件vue-stivky-nav已经开源到npm上,欢迎使用和提问题,如果您对本文有什么问题也可以在底下留言讨论。
上次更新:2020-07-21 16:19:59
来源:oschina
链接:https://my.oschina.net/u/4381645/blog/4416539