在移动端手机滑动浏览列表是没有滚动条的,那么如何实现没有滚动条又能够滚动呢?
这时候会用到一个常用的插件:better-scroll,利用它来实现移动端的滚动。其原理是利用translate来实现滚动,看下调试:
这个div是需要滚动的列表的父元素,transition-timing-function是为了让translate这个过渡效果更像原生scroll。
使用这个组件你需要有特定的结构,借一张图:
wrpper有固定高度content区超出隐藏;
使用时首先引入better-scroll插件:
import Better from 'better-scroll
传入dom节点对象:
new Better(wrpper)
wrpper节点对象可以是自己找到的对象,赋值给一个变量并传入,也可以传入节点的class名或者id它会自动调用querySelector方法去找节点,还可以通过$refs来查找。
给节点设置ref
传入:
new Better(this.$refs.list)
这样改wrpper内部的content区就可以实现滚动了;
然后来看看这种效果:
左右两个列表,左边点击右边跳到相应位置,右边滚动左边相应位置也高亮显示;这就是典型的列表联动。
我们将它分解一下,先实现左联右:
用到better-scroll中的 scrollToElement(el,time,offsetX,offsetY,easing) 很简单的方法拿来即用
1、在需要跳转到的区块上添加ref属性用于标识
1 <div v-for="(it,index) in goods" :key="index" ref="good">
2 <p class="title">
3 {{it.name}}
4 </p>
5 <ul>
6 <li v-for="(item,index) in it.foods" :key="index" class="list " >
7 <div class="list_item flex" :class="index===it.foods.length-1?'last':''">
8 <p @click="getDetails(item)"><img :src="item.icon" alt=""></p>
9 <div>
10 <p class="name">{{item.name}}</p>
11 <p class="des">{{item.description}}</p>
12 <p class="sale">月售{{item.sellCount}}份<span>好评率{{item.rating}}%</span></p>
13 <p class="price">¥{{item.price}}</p>
14 <div class="add"><add :food="item"></add></div>
15 </div>
16 </div>
17 </li>
18 </ul>
19 </div>
2、定义方法跳转到相应位置
1methods: {
2 change (index) {
3 this.actli = index
4 this.rgt.scrollToElement(this.$refs.good[index], 100, 0, 0)
5 },
6}
3、左列表点击调用
1 <aside ref="l_list">
2 <ul>
3 <li ref="l_item" :class="{'act':index === actli}"
4 @click="change(index)" v-for="(item,index) in goods" :key="index">
5 <p>
6 {{item.name}}
7 </p>
8 </li>
9 </ul>
10 </aside>
记住左侧滚动栏的better-scroll对象要开启点击事件
1 this.left = new Better(this.$refs.l_list, {
2 click: true //开启点击事件
3 })
然后来实现:右联左
右边在滚动时我们需要知道它滚动了多少距离,滚动到了哪里,(右边属于列表套列表,大列表是分类,小列表是具体商品)
滚动的距离可以通过better-scroll对象的scroll事件的回调函数的参数取得;
1 this.rgt = new Better(this.$refs.r_list, {
2 click: true,
3 probeType: 3//实时分发scroll事件
4 })
5 this.rgt.on('scroll', (res) => {
6 console.log(res)
7 this.scrollY = Math.abs(res.y)
8 })
probeType属性具体可参考官网文档,在此不再累述
可以看到参数res是一个对象,对象内包含x,y,即滚动的x轴和y轴的距离。
知道了滚动了多少距离,还要知道滚动到了哪里,即大列表的index值;我们可以拿content滚动的距离和每个小列表距离顶部的距离作比较;
设content滚动距离为x,小列表a距离顶部的距离为La,小列表b距离顶部的距离为Lb(小列表a是小列表b的前一项),那么当La<x<Lb时,我们可以确定content滚动到了小列表a的区域,那么此时大列
表index值为小列表a的索引值;
初始化一个数组用于存储小列表距离顶部的距离:
1 data () {
2 return {
3 actli: 0,
4 arr: [0],
5 }
6 },
将距离存储到arr中:
1 mounted () {
2 this.$refs.good.forEach((el, index) => {
3 this.arr.push(el.offsetHeight + this.arr[index])
4 })
5}
比较并更新index:
1 this.rgt.on('scroll', (res) => {
2 if (this.flag) {
3 console.log(res)
4 this.scrollY = Math.abs(res.y)
5 for (let i = 0; i < this.arr.length; i++) {
6 if (this.scrollY > this.arr[i] && this.scrollY < this.arr[i + 1]) {
7 this.actli = i
8 }
9 }
10 }
11 })
考虑到数据的异步请求,以及左侧列表增加动态性更完整的代码如下:
1mounted () {
2 setTimeout(() => {
3 /* eslint-disable no-new */
4 this.left = new Better(this.$refs.l_list, {
5 click: true //开启点击事件(默认关闭)
6 })
7 this.rgt = new Better(this.$refs.r_list, {
8 click: true,
9 probeType: 3 //scroll事件实时分发
10 })
11 this.$refs.good.forEach((el, index) => {//计算每个列表相对于顶部的距离,存到数组arr中
12 this.arr.push(el.offsetHeight + this.arr[index])
13 })
14 this.rgt.on('scroll', (res) => {//监听滚动事件
15 if (this.flag) {
16 this.scrollY = Math.abs(res.y)
17 for (let i = 0; i < this.arr.length; i++) {
18 if (this.scrollY > this.arr[i] && this.scrollY < this.arr[i + 1]) {
19 this.actli = i
20 if (i === this.$refs.l_item.length - 2) {//当滚动到倒数第2个位置时左侧列表向上滚动一个距离
21 this.left.scrollToElement(this.$refs.l_item[1], 100, 0, 0)
22 }
23 if (i === 2) {//当滚动到倒数第3个位置时左侧列表向上下滚动一个距离
24 this.left.scrollToElement(this.$refs.l_item[0], 100, 0, 0)
25 }
26 }
27 }
28 }
29 })
30 })
31 }
附goods组件全部代码:
template:
1<template>
2 <div v-if="goods">
3 <aside ref="l_list">
4 <ul>
5 <li ref="l_item" :class="{'act':index === actli}"
6@click="change(index)" v-for="(item,index) in goods" :key="index">
7 <p>
8 {{item.name}}
9 </p>
10 </li>
11 </ul>
12 </aside>
13 <section class="r_list" ref="r_list">
14 <div >
15 <div v-for="(it,index) in goods" :key="index" ref="good">
16 <p class="title">
17 {{it.name}}
18 </p>
19 <ul>
20 <li v-for="(item,index) in it.foods" :key="index" class="list " >
21 <div class="list_item flex" :class="index===it.foods.length-1?'last':''">
22 <p @click="getDetails(item)"><img :src="item.icon" alt=""></p>
23 <div>
24 <p class="name">{{item.name}}</p>
25 <p class="des">{{item.description}}</p>
26 <p class="sale">月售{{item.sellCount}}份<span>好评率{{item.rating}}%</span></p>
27 <p class="price">¥{{item.price}}</p>
28 <div class="add"><add :food="item"></add></div>
29 </div>
30 </div>
31 </li>
32 </ul>
33 </div>
34 </div>
35 </section>
36 </div>
37</template>
js:
1<script>
2import Better from 'better-scroll'
3import add from '../addNum/addnum'
4export default {
5 name: 'goods',
6 data () {
7 return {
8 actli: 0,
9 scrollY: 0,
10 arr: [0],
11 flag: true,
12 obj: null,
13 show: false
14 }
15 },
16 components: {add},
17 computed: {
18 goods () {
19 return this.$store.state.msg.goods
20 }
21 },
22 watch: {
23 show () {
24 /* eslint-disable no-new */
25 if (this.show) {
26 setTimeout(() => {
27 new Better(this.$refs.detail, {
28 click: true
29 })
30 })
31 }
32 }
33 },
34 methods: {
35 change (index) {
36 this.flag = false
37 this.actli = index
38 this.rgt.scrollToElement(this.$refs.good[index], 100, 0, 0)
39 setTimeout(() => {
40 this.flag = true
41 }, 100)
42 },
43 getDetails (it) {
44 this.show = !this.show
45 this.obj = it
46 }
47 },
48 mounted () {
49 setTimeout(() => {
50 /* eslint-disable no-new */
51 this.left = new Better(this.$refs.l_list, {
52 click: true
53 })
54 this.rgt = new Better(this.$refs.r_list, {
55 click: true,
56 probeType: 3
57 })
58 this.$refs.good.forEach((el, index) => {
59 this.arr.push(el.offsetHeight + this.arr[index])
60 })
61 this.rgt.on('scroll', (res) => {
62 if (this.flag) {
63 console.log(res)
64 this.scrollY = Math.abs(res.y)
65 for (let i = 0; i < this.arr.length; i++) {
66 if (this.scrollY > this.arr[i] && this.scrollY < this.arr[i + 1]) {
67 this.actli = i
68 if (i === this.$refs.l_item.length - 2) {
69 this.left.scrollToElement(this.$refs.l_item[1], 100, 0, 0)
70 }
71 if (i === 2) {
72 this.left.scrollToElement(this.$refs.l_item[0], 100, 0, 0)
73 }
74 }
75 }
76 }
77 })
78 })
79 }
80}
81</script>
82
css:
1<style scoped lang="less">
2aside{
3 position: fixed;
4 left: 0;
5 top: 3.48rem;
6 bottom: 1.04rem;
7 overflow: hidden;
8 ul{
9 li{
10 height: 1.08rem;
11 width: 1.6rem;
12 display: table;
13 background-color: #f3f5f7;
14 padding: 0 0.24rem;
15 box-sizing: border-box;
16 p{
17 display: table-cell;
18 font:200 0.24rem/0.28rem '';
19 vertical-align: middle;
20 border-bottom: 1px solid rgba(7,17,27,.1);
21 }
22 }
23 .act{
24 background-color: deepskyblue;
25 }
26 }
27}
28.r_list{
29 position: fixed;
30 left: 1.6rem;
31 right: 0;
32 top: 3.48rem;
33 bottom: 1.04rem;
34 overflow: hidden;
35 .title{
36 border-left: 3px solid #d9dde1;
37 height: 0.52rem;
38 font: 0.24rem/0.52rem '';
39 color: rgb(147,153,159);
40 background-color:#f3f5f7;
41 padding-left: 0.28rem;
42 }
43 .flex{
44 display: flex;
45 justify-content: center;
46 align-items: center;
47 }
48 .list{
49 padding: 0.36rem 0.36rem 0 0.36rem;
50 img{
51 width: 1.28rem;
52 border-radius: 0.04rem;
53 }
54 .list_item{
55 justify-content: flex-start;
56 border-bottom: 1px solid rgba(7,17,27,0.1);
57 padding-bottom: 0.36rem;
58 position: relative;
59 &>div{
60 margin-left: 0.2rem;
61 .name{
62 font: 0.28rem/0.28rem '';
63 color: rgb(7,17,27);
64 margin-bottom: 0.16rem;
65 }
66 .des,.sale{
67 font: 0.2rem/0.2rem '';
68 color: rgb(147,153,159);
69 margin-bottom: 0.16rem;
70 }
71 .sale{
72 margin-bottom: 0;
73 span{
74 margin-left: 0.24rem;
75 }
76 }
77 .price{
78 font:700 0.2rem/0.48rem '';
79 color: red;
80 }
81 .add{
82 position: absolute;
83 right: 0;
84 bottom: 0.28rem;
85 }
86 }
87 }
88 .last{
89 border-bottom: none;
90 }
91 }
92 }
93 .detail{
94 background-color: white;
95 position: fixed;
96 z-index: 299;
97 left: 0;
98 right: 0;
99 top:0;
100 bottom: 1.04rem;
101 overflow: hidden;
102 .bg{
103 img{
104 height: 7.5rem;
105 }
106 }
107 .tit{
108 &>div{
109 &:nth-of-type(1){
110 padding: 0.36rem;
111 p{
112 &:nth-of-type(1){
113 font: 700 0.28rem/0.28rem '';
114 margin-bottom: 0.16rem;
115 }
116 &:nth-of-type(2){
117 font: 0.2rem '';
118 color: rgb(147,153,159);
119 margin-bottom: 0.36rem;
120 }
121 &:nth-of-type(3){
122 font:700 0.28rem/0.48rem '';
123 color: rgb(240,20,20);
124 }
125 }
126 }
127 &:nth-of-type(2){
128 float: right;
129 margin-right: 0.36rem;
130 margin-top: -0.9rem;
131 font: 0.2rem/0.48rem '';
132 color: white;
133 background-color: rgb(0,160,220);
134 border-radius: 0.24rem;
135 width: 1.48rem;
136 height: 0.48rem;
137 text-align: center;
138 }
139 }
140 }
141 .b_line{
142 height: 0.36rem;
143 background-color: #f3f5f7;
144 border-bottom: 1px solid rgba(0,0,0,0.1);
145 border-top: 1px solid rgba(0,0,0,0.1);
146 }
147 .intr{
148 padding: 0.36rem;
149 p{
150 &:nth-of-type(1){
151 font: 0.28rem '';
152 margin-bottom: 0.12rem;
153 }
154 &:nth-of-type(2){
155 font:200 0.24rem/0.48rem '';
156 color: rgba(77,85,93);
157 min-height: 1rem;
158 }
159 }
160 }
161 .flex{
162 display: flex;
163 justify-content: flex-start;
164 align-items: center;
165 }
166 .esit{
167 &>div{
168 &:nth-of-type(1){
169 padding: 0.36rem 0.36rem 0 0.36rem;
170 &>p{
171 font: 0.28rem '';
172 &:nth-of-type(1){
173 margin-bottom: 0.36rem;
174 }
175 }
176 .count {
177 padding:0 0 0.36rem 0;
178 border-bottom:1px solid rgba(7, 17, 27, 0.1) ;
179 p {
180 text-align: center;
181 font: 0.24rem/0.48rem '';
182 padding: 0.12rem;
183 border-radius: 0.02rem;
184 margin-right: 0.12rem;
185 &:nth-of-type(1) {
186 background-color: #00A0DC;
187 }
188 &:nth-of-type(2) {
189 background-color: #CCECF8;
190 }
191 &:nth-of-type(3) {
192 background-color: #E9EBEC;
193 }
194 }
195 .choosen{
196 transform: scale(1.1);
197 }
198 }
199 }
200 &:nth-of-type(2){
201 font-size: 0;
202 padding: 0.24rem 0.36rem;
203 border-bottom:1px solid rgba(7, 17, 27, 0.1) ;
204 i{
205 color: rgb(147,153,159);
206 font-size: 0.48rem;
207 margin-right: 0.08rem;
208 }
209 span{
210 font: 0.24rem '';
211 color: rgb(147,153,159);
212 }
213 }
214 }
215 &>ul{
216 li{
217 padding: 0 0.36rem;
218 &>div{
219 padding: 0.32rem 0;
220 border-bottom:1px solid rgba(7, 17, 27, 0.1) ;
221 &>div{
222 span{
223 font: 0.2rem/0.24rem '';
224 color: rgb(147,153,159);
225 &:nth-of-type(2){
226 margin-left: 55%;
227 margin-right: 0.12rem;
228 }
229 }
230 em{
231 font-size: 0;
232 img {
233 width: 0.48rem;
234 border-radius: 50%;
235 }
236 }
237 }
238 p{
239 &:nth-of-type(1){
240 font: 0.24rem/0.32rem '';
241 }
242 }
243 }
244 }
245 }
246 }
247 }