实现思路
首先有一个支持滑动的view,可以基于better-scroll实现 当点击每一个item时,根据点击的item的位置,调用scroll方法将其滚动到视图中间
位置计算主要由以下几种情况
代码可参考下面完整代码中的 _adjust方法代码注释
进一步丰富支持如美团外卖点餐效果,实现横向bar和竖向滚动联动效果,则只要每次点击或者滑动时更新当前current,是横向和竖向用同一个current,即可
完整代码如下
<template>
<div
class="wrap"
id="wrap">
<div class="wrapper1" ref="wrapper1" >
<div class="content1" ref="items" >
<div class="item"
:class="{'item_active': current === todo}"
@click="clickHandler(todo)" v-for="(todo) in labels" :key="todo">{{todo}}</div>
</div>
</div>
<cube-scroll-nav-bar :current="current" :labels="labels" @change="changeHandler" >
<template v-slot:default="slotProps">
{{ slotProps.txt }}
</template>
</cube-scroll-nav-bar>
<cube-scroll-nav-bar
direction="vertical"
:current="current"
:labels="labels"
:txts="txts"
@change="changeHandler">
<i slot-scope="props">{{props.txt}}</i>
</cube-scroll-nav-bar>
</div>
</template>
<script>
import BetterScroll from 'better-scroll'
export default {
data() {
return {
current: '快车',
labels: [
'快车',
'小巴',
'专车',
'顺风车',
'代驾',
'公交',
'自驾租车',
'豪华车',
'二手车',
'出租车'
],
txts: [
'1快车',
'2小巴',
'3专车',
'4顺风车',
'5代驾',
'6公交',
'7自驾租车',
'8豪华车',
'9二手车',
'10出租车'
],
bs: {}
}
},
methods: {
changeHandler(cur) {
this.current = cur
},
clickHandler(cur) {
console.log(cur)
this.current = cur
this._adjust()
},
_adjust() {
this.$nextTick(() => {
const targetProp = 'clientWidth'
const current = this.current
// 滚动容器总宽度为viewportSize 450
const viewportSize = this.$refs.wrapper1[targetProp]
const itemsEle = this.$refs.items
// 滚动条总宽度为scrollerSize 100 * 10
const scrollerSize = itemsEle[targetProp]
// 最大可滚动的宽度为minTranslate 450 - 100 * 10
// 一般scrollerSize 都大于viewportSize 这样才能滚动 所以一般为负数,所以,英文名称用的min)
// 当一般scrollerSize < 都大于viewportSize表示 不需要滚动,则minTranslate直接为零就好了
const minTranslate = Math.min(0, viewportSize - scrollerSize)
// 视图中间距离左侧宽度(滚动容器总宽度的一半)为viewportSize 450/2
const middleTranslate = viewportSize / 2
const items = itemsEle.children
let size = 0
this.labels.every((label, index) => {
if (label === current) {
size += (items[index][targetProp] / 2)
return false
}
// 每一个红色方框宽100
size += items[index][targetProp]
return true
})
// size为点击的红框的左侧宽度的和+当前红框宽度的一半
// 当点击的红框的位置在 视图中间左侧的时候 translate < 0 ,反之大于0
let translate = middleTranslate - size
console.log('size', size)
console.log('minTranslate', minTranslate)
console.log('translate前', translate)
// Math.min(0, translate) 表示点击位置在视图中间左侧的时候取零,表示不需要异动
// Math.max(minTranslate, Math.min(0, translate)),v如果需要滚动,最大滚动距离不会超过minTranslate
translate = Math.max(minTranslate, Math.min(0, translate))
console.log('translate最终', translate)
console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
this.bs.scrollTo( translate , 0 )
})
}
},
mounted() {
let bs = new BetterScroll(this.$refs.wrapper1, {
scrollX:true,
startX:0
})
this.bs = bs
console.log(bs)
setTimeout(()=> {
// bs.scrollTo(-200,0)
}, 1000)
},
}
</script>
<style>
html,
body {
height: 100%;
}
.wrap {
height: 600px;
overflow: scroll;
}
.bg {
background: #ccc;
height: 300px;
}
</style>
<style lang="stylus" scoped>
.wrapper1 {
width: 450px;
overflow: scroll;
.content1-wraper {
overflow: hidden;
min-height: 0px;
}
.content1 {
white-space: nowrap;
display: inline-block;
}
.item {
box-sizing: border-box;
display:inline-block;
width: 102px;
border: 1px solid red;
height: 102px;
}
.item_active {
color: blue;
}
}
div::-webkit-scrollbar {
width: 0 !important;
height: 0 !important;
}
div{
-ms-overflow-style: none;
overflow: -moz-scrollbars-none;
}
.layer-container{
padding: 1.5625rem;
margin-top: 10px;
background-color: red;
.text{
margin: 0 0 0.9375rem;
font-family: PingFangSC-Medium;
font-size: 1rem;
color: #4B4B4D;
line-height: 1.3125rem;
}
.item-wrapper{
padding: 1.5625rem 0.9375rem;
background: rgba(216, 216, 216, 0.25);
border-radius: 0.4375rem;
.item{
margin-top: 1.6875rem;
display: flex;
justify-content: space-between;
&:first-child {
margin-top: 0;
}
.left{
display: flex;
font-family: PingFangSC-Medium, sans-serif;
font-size: 16px;
color: #323233;
text-align: justify;
line-height: 22px;
.time{
margin-right: 10px;
}
}
.right{
position: relative;
padding-right: 13px;
font-family: PingFangSC-Medium, sans-serif;
font-size: 16px;
line-height: 22px;
color: #46648C;
box-sizing: border-box;
&::after{
position: absolute;
content: '';
display: block;
right: 0;
top: 50%;
width: 7px;
height: 7px;
border-top: 2px solid #46648C;
border-right: 2px solid #46648C;
transform: translateY(-50%) rotate(45deg);
line-height: 22px;
}
}
}
}
}
</style>
<svg
t="1604497443000"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2837"
:class="{
icon: true,
rotate: popoverShow,
}"
><path d="M234.88884163 377.47806845l279.65492248 279.65492248 278.8075161-278.8075161z" p-id="2838" data-spm-anchor-id="a313x.7781069.0.i0" class="selected"></path></svg>