移动端吸顶导航组件的实现
前言
吸顶导航是营销会场类最常用的组件之一, 现在的会场页面是越来越长,如果从第一屏手动滑到最后一屏,还是一个挺累的操作,所以吸顶导航还是很有必要存在的,组件很常见,但是开源的不多,而且大多是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 30
| 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 32
|
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 45
|
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上,欢迎使用和提问题,如果您对本文有什么问题也可以在底下留言讨论。