vue使用better-scroll制作左右菜单联动以及vant框架导致滚动失效的解决办法
vue使用better-scroll制作左右菜单联动
better-scroll是一个纯js的插件,官方推荐配合vue这种纯前端开发使用,在vue中使用需要注意一些地方,比如初始化的时机,html的布局,事件的触发机制
安装
我现在都是用yarn 方便一些,未来的安装包管理器
yarn add better-scroll
yarn不写--save默认就是save,如果要--save-dev 如下写法
yarn add better-scroll --dev
安装完毕后可以在main.js文件中作为全局调用,也可以在对于的组件内调用,我是在组件中的,所以在组件的script元素中import
<script>
import BScroll from "better-scroll";
</script>
BScroll
将作为这个js插件的变量名使用。
引入完毕后,我们先了解下html的结构,我们可以先去看下官方文档的原理示意图:
html结构
可以看到,一个父元素,这父元素限制高度,并且overflow: hidden
,里面的子元素则由内容撑开。
所以一个基本结构如下:
<div class="container" ref="container">
<ul>
<li>子元素内容</li>
<li>子元素内容</li>
<li>子元素内容</li>
<li>子元素内容</li>
<li>子元素内容</li>
<li>子元素内容</li>
<li>很多个</li>
</ul>
</div>
container容器为父元素,ul为子元素,li则是内容撑开,我们可以限制container容器的高度,利用插件来滑动ul元素。
如果说你的布局很复杂,建议就是先用一个div布好位置,然后把容器丢进去,高度100%。
初始化的时机
结构搭好后我们就需要对这个插件功能进行初始化。
在vue的created生命周期里:
created(){
this.$nextTick(() => {
//这里放初始化函数,我还没写,后面补充
});
}
这里我们用到了一个$nextTick
;原因是因为我们的li元素,很有可能是for循环遍历出来的,如果直接初始化,容易造成for循环渲染还没有完毕,我就初始化完了,此时的高度是错误的,使用就会出现问题,所以要在$nextTick
的回调里进行初始化,此时元素都已经渲染好了的。
如果我们的元素是一个弹窗,那么在created初始化就不正确,因为此时的元素可能没有创建出来,或者是display:none
;这个时候初始化就会报错,因为获取不到dom元素或者元素高度计算不出来。
这种情况:
我推荐就是监听元素用于显示隐藏的变量,比如这个变量名为:show
,show默认是false,元素就没有,为true就弹窗显示,我们就监听这个变量,当他为true的时候,我们再进行初始化。
但是这个弹窗用户可能打开了又关闭了,弹窗这个东西是需要复用的,所以只监听为true是片面的,我们要创建一个变量用于判断是否已经初始化了,在data里面创建一个变量名为initScroll:false
watch: {
show(val) {
if (val && !this.initScroll) {
this.$nextTick(() => {
//这里放初始化函数,我还没写,后面补充
});
}
}
}
初始化函数
在methods中创建一个初始化函数
methods:{
initScroll() {
new BScroll(this.$refs.container, {
click: true
});
}
}
将容器dom元素丢入BScroll得第一个参数,第二个参数是一个对象option,click为true表示开启子元素点击事件,否则默认是阻止的。
此时子元素就可以滑动了,但是还没有达到我们的要求:两个菜单联动
菜单联动
两个菜单联动他其实是有要求的,不是什么东西随便一搭他就能成。
首先我们需要两个菜单进行对应,比如左边的菜单有:一,二,三,四,五;5个菜单选项,那么右边的菜单,第一个li对应一,第二个li对应二,以此类推。
<div class="tab-nav" ref="tabNav">
<ul>
<li>一</li>
<li>二</li>
<li>三</li>
<li>四</li>
<li>五</li>
</ul>
</div>
<div class="tab-content" ref="tabContent">
<ul>
<li ref="sli">
<p>小标题</p>
<div>内容</div>
</li>
<li ref="sli">
<p>小标题</p>
<div>内容</div>
</li>
<li ref="sli">
<p>小标题</p>
<div>内容</div>
</li>
<li ref="sli">
<p>小标题</p>
<div>内容</div>
</li>
<li ref="sli">
<p>小标题</p>
<div>内容</div>
</li>
</ul>
</div>
左右两个菜单,我要求:
- 当我点击左边的菜单,右边的菜单要自动滚动到对应的li
- 当我右边的菜单滚动的时候,左边的菜单要即使反馈,比如我右边滚动到第三个li,左边菜单三要高亮显示
因为要判断滚动的位置,所以需要计算出每个li的高度值,将每个li的高度保存在数组,然后在通过对比数组的下标调出高度值进行判断,从而判断是是哪个li,从而可以让左边的菜单高亮。
data(){
return {
scrollY: 0, //实时滚动高度
heightList: [], //右边li元素高度数组
leftScroll:null,
rightScroll:null,
initScroll:false
}
},
methods:{
initScrollFn(){
//左边
this.leftScroll = new BScroll(this.$refs.tabNav, {
click: true
});
//右边
this.rightScroll = new BScroll(this.refs.tabContent,{
probeType: 3,
click: true
});
//监听右边scroll事件
this.rightScroll.on("scroll",(pos)=>{
//pos包含x,y轴两个属性,我们要取正值
this.scrollY = Math.abs(Math.round(pos.y));
});
},
getHeight(){ //获取高度
const lis = this.$refs.sli;
//高度从0开始
let sh = 0;
this.heightList.push(sh);
//遍历
lis.forEach((item,i)=>{
height += item.clientHeight;
this.heightList.push(height);
});
}
},
watch:{
show(val){
if(val && this.initScroll){
this.$nextTick(() => {
this.initScrollFn();
this.getHeight();
});
}
}
}
点击左边菜单右边滚动对应位置
此时初始化就完毕了,下面处理点击左边,右边进行自动滚动到对应位置
创建一个方法:
data(){
return {
activeTabNav:0 //左边菜单点击选中的元素下标
}
},
methods : {
selectTabNav(index){
//获取到左边菜单下标对应的右边li元素
const lis = this.$refs.sli;
const el = lis[index];
//更新选中的下标
this.activeTabNav = index;
//运行右边菜单的跳转方法,传入li元素和执行时间
this.rightScroll.scrollToElement(el, 300);
}
}
html绑定
<div class="tab-nav" ref="tabNav">
<ul>
<li v-for="(item,i) in 6" :key="i" @click="selectTabNav(i)" :class="{'active': activeTabNav === i}">{{i}}</li>
</ul>
</div>
点击事件绑定,clss判断,如果activeTabNav选中的下标和遍历的i相同,就显示active的class
右边菜单滚动左边菜单自动高亮
创建一个计算属性,用于实时计算滚动到哪里
computed:{
currentIndex(){
const index = this.heightList.findIndex((item, index) => {
return (
this.scrollY >= this.heightList[index] &&
this.scrollY < this.heightList[index + 1]
);
});
return index > 0 ? index : 0;
}
}
currentIndex判断,当前heightList高度数组里面,大于或者等于当前滚动的高度,但又小于下一个高度的数组下标。
左边菜单绑定
<li v-for="(item,i) in 6" :key="i" @click="selectTabNav(i)" :class="{'active': activeTabNav === i || currentIndex === i}">{{i}}</li>
这样写虽然能用,但是会有一个问题,就是左边的activeTabNav和currentIndex会有冲突,因为currentInde是即时的,activeTabNav固定的,可能我已经滚动到菜单二,菜单一被activeTabNav影响而高亮显示。
为此我们要对这个两个业务逻辑分开,点击的时候不触发即时计算
data(){
return {
tabNavClick: false //用于判断是否在点击滚动
}
},
methods:{
initScrollFn(){
//左边
this.leftScroll = new BScroll(this.$refs.tabNav, {
click: true
});
//右边
this.rightScroll = new BScroll(this.refs.tabContent,{
probeType: 3,
click: true
});
//监听右边scroll事件
this.rightScroll.on("scroll",(pos)=>{
//pos包含x,y轴两个属性,我们要取正值
this.scrollY = Math.abs(Math.round(pos.y));
});
//监听右边滚动结束事件
this.rightScroll.on("scrollEnd", pos => {
//如果是点击滚动,滚动结束后恢复tabNavClick为false
if (this.tabNavClick) {
this.tabNavClick = false;
}
});
}
},
computed:{
currentIndex(){
if(!this.tabNavClick){
const index = this.heightList.findIndex((item, index) => {
return (
this.scrollY >= this.heightList[index] &&
this.scrollY < this.heightList[index + 1]
);
const active = index > 0 ? index : 0;
if (this.activeTabNav !== active) {
this.activeTabNav = active;
}
return active;
});
}
}
}
即时计算的时候,如果是点击触发的scrollY变化,不做处理。
这样,左右菜单联动就完成了。
vant框架popup组件导致滚动失效
我使用该框架的时候遇到一个问题,就是组件在浏览器pc模式,是可以滚动的,但是手机端不行,滚动无反应,想来想去我只想到一个地方。
better-scroll是在dom上的滚动事件,我记得vant在弹出popup组件的时候默认是有一个防止滑动的属性的,于是我尝试关闭了这个功能,问题解决。
<van-popup :lock-scroll="false"></van-popup>
由于关闭了防止滚动,事件是不会阻止了,但是body不会默认添加clss="van-overflow-hidden"
了,这就导致滚动穿透,解决这个问题简单点,我就是在监听这个弹窗的时候,手动添加class了,利用原生js方法。
watch: {
countryPopup(val) {
if (val && !this.initScr) {
this.$nextTick(() => {
this.initScroll();
this.getHeight();
});
}
//防止滚动穿透
if (val) {
document.body.className = "van-overflow-hidden";
} else {
document.body.className = "";
}
}
}
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
全部评论 1
前端小学徒
Google Chrome Windows 10