关于此分类

关于初识 vue3分类主要是记录一些通过 vue3 进行的实践与学习记录。

此文主要记录封装基于BetterScroll 2.0插件封装的 scroll-view 组件。适用于移动端的弹性滚动及下拉刷新、上拉加载等场景。

弹性滚动

下拉加载

上拉刷新

滚动前后及滚动中事件

滚动条(待添加)

本文会随着作者日常使用进行补充及内容修正

简单的实现过程

实现之前需要先明确一下需求:

  1. 弹性滚动
  2. 下拉刷新、上拉加载
  3. 自定义传入内容
  4. 事件派发

弹性滚动

实现弹性滚动只需要按照官方示例初始化即可实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div class="wrapper" ref="wrapper">
<div class="content">
<div v-for="i in 100">{{ i }}</div>
</div>
</div>
</template>
<script setup lang="ts">
import BScroll from '@better-scroll/core'
import { onMounted } from 'vue'
let bscroll: BScroll
const wrapper = ref(null)
onMounted(() => {
bscroll = new BScroll(wrapper.value, {
mouseWheel: true
})
})
</script>

只需要在 mounted 阶段进行初始化创建对象即可。

下拉刷新、上拉加载

由于我安装的是@better-scroll/core,并没有安装‘全量包’ ,因此实现此功能同样需要安装其他两个插件:@better-scroll/pull-up@better-scroll/pull-down

安装后在初始化时开启选项即可。

1
2
3
4
5
6
7
8
9
10
import BScroll from '@better-scroll/core'
import Pullup from '@better-scroll/pull-up'
import Pulldown from '@better-scroll/pull-down'
BScroll.use(Pullup)
BScroll.use(Pulldown)
bscroll = new BScroll(wrapper.value, {
mouseWheel: true,
pullDownRefresh: true,
pullUpLoad: true
})

对于其触发事件的监听需要用实例对象去监听:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import BScroll from '@better-scroll/core'
import Pullup from '@better-scroll/pull-up'
import Pulldown from '@better-scroll/pull-down'
BScroll.use(Pullup)
BScroll.use(Pulldown)
bscroll = new BScroll(document.querySelector('.wrapper') as any, {
mouseWheel: true,
pullDownRefresh: true,
pullUpLoad: true
})
bscroll.on('pullingUp', () => {
console.log('触发了上拉')
})
bscroll.on('pullingUp', () => {
console.log('触发了下拉')
})

自定义传入内容

自定义传入内容这里,由于我并不想传入一个数据列表,然后去渲染元素。因此通过watch监听 props 传入的数据这个方法就不好用了;监听slots里的变化通过一番尝试,最终也是失败,因此最终选择了使用官方提供的插件:@better-scroll/observe-dom@better-scroll/observe-image进行自动更新实例对象。安装完成后只需要在初始化前use此插件即可。

1
2
3
import BScroll from '@better-scroll/core'
BScroll.use(ObserveDOM)
BScroll.use(ObserveImage)

事件派发

需要派发的事件大致为:滚动事件和上拉下拉事件

  1. 滚动事件

    此类事件只需要正常 emit 即可。

  2. 上拉下拉事件

    上拉下拉刷新需要调用对应的结束事件才可以,因此将其封装为组件时就会遇到一个问题即需要等传入函数执行完毕后在调用结束事件。

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import { defineProps, PropType, defineEmits } from 'vue'
const emit = defineEmits(['ckick', 'beforeScroll', 'afterScroll', 'scroll'])
const emit = defineEmits(['ckick', 'beforeScroll', 'afterScroll', 'scroll'])
const props = defineProps({
/**
* 是否派发滚动事件
*/
listenScroll: {
type: Boolean,
default: false
},
/**
* 是否派发滚动到底部的事件,用于上拉加载
*/
pullup: {
type: Function,
default: null
},
/**
* 是否派发顶部下拉的事件,用于下拉刷新
*/
pulldown: {
type: Function,
default: null
},
/**
* 是否派发列表滚动开始的事件
*/
beforeScroll: {
type: Boolean,
default: false
},
/**
* 是否派发列表滚动开始的事件
*/
afterScroll: {
type: Boolean,
default: false
}
})
/**
* 如果开启了滚动前事件派发
*/
if (props.beforeScroll) {
bscroll.on('beforeScrollStart', () => {
emit('beforeScroll')
})
}

/**
* 如果开启了滚动(滚动中)事件派发
*/
if (props.listenScroll) {
bscroll.on('scroll', (position: { x: number; y: number }) => {
emit('scroll', position)
})
}

/**
* 如果开启了滚动结束事件派发
*/
if (props.beforeScroll) {
bscroll.on('scrollEnd', () => {
emit('afterScroll')
})
}

if (props.pullup !== null) {
bscroll.on('pullingUp', () => {
try {
props.pullup().then(() => {
bscroll.finishPullUp()
})
} catch (e) {
// 传入非 Promise 函数
bscroll.finishPullUp()
}
})
}

if (props.pulldown !== null) {
bscroll.on('pullingDown', () => {
try {
props.pulldown().then(() => {
bscroll.finishPullDown()
})
} catch (e) {
// 传入非 Promise 函数
bscroll.finishPullDown()
}
})
}

完整代码

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<template>
<div :class="{ wrapper: true, x: props.scrollX }" ref="wrapper">
<div class="content">
<slot></slot>
</div>
</div>
</template>

<script setup lang="ts">
import { onMounted, defineProps, PropType, defineEmits } from 'vue'

import BScroll from '@better-scroll/core'
import ObserveDOM from '@better-scroll/observe-dom'
import ObserveImage from '@better-scroll/observe-image'
import Pullup from '@better-scroll/pull-up'
import Pulldown from '@better-scroll/pull-down'
let bscroll: BScroll
const wrapper = ref(null)
BScroll.use(ObserveDOM)
BScroll.use(ObserveImage)
BScroll.use(Pullup)
BScroll.use(Pulldown)
const emit = defineEmits(['ckick', 'beforeScroll', 'afterScroll', 'scroll'])
const props = defineProps({
/**
* 1 滚动的时候会派发scroll事件,会截流。
* 2 滚动的时候实时派发scroll事件,不会截流。
* 3 除了实时派发scroll事件,在swipe的情况下仍然能实时派发scroll事件
*/
probeType: {
type: Number as PropType<1 | 2 | 3>,
required: false,
default: 1
},

/**
* 是否开启横向滚动,默认纵向滚动。
* 开启横向滚动需要将传入元素设置为横向例如:display:inline-block
*/
scrollX: {
type: Boolean,
required: false,
default: false
},

/**
* 点击列表是否派发click事件
*/
click: {
type: Boolean,
default: true
},
/**
* 是否派发滚动事件
*/
listenScroll: {
type: Boolean,
default: false
},
/**
* 是否派发滚动到底部的事件,用于上拉加载
*/
pullup: {
type: Function,
default: null
},
/**
* 是否派发顶部下拉的事件,用于下拉刷新
*/
pulldown: {
type: Function,
default: null
},
/**
* 是否派发列表滚动开始的事件
*/
beforeScroll: {
type: Boolean,
default: false
},
/**
* 是否派发列表滚动开始的事件
*/
afterScroll: {
type: Boolean,
default: false
}
})

onMounted(() => {
bscroll = new BScroll(wrapper.value, {
scrollX: props.scrollX,
probeType: props.probeType,
click: props.click,
observeDOM: true,
observeImage: true,
mouseWheel: true,
pullDownRefresh: true,
pullUpLoad: true
})
/**
* 如果开启了滚动前事件派发
*/
if (props.beforeScroll) {
bscroll.on('beforeScrollStart', () => {
emit('beforeScroll')
})
}

/**
* 如果开启了滚动(滚动中)事件派发
*/
if (props.listenScroll) {
bscroll.on('scroll', (position: { x: number; y: number }) => {
emit('scroll', position)
})
}

/**
* 如果开启了滚动结束事件派发
*/
if (props.beforeScroll) {
bscroll.on('scrollEnd', () => {
emit('afterScroll')
})
}

if (props.pullup !== null) {
bscroll.on('pullingUp', () => {
try {
props.pullup().then(() => {
bscroll.finishPullUp()
})
} catch (e) {
// 传入非 Promise 函数
bscroll.finishPullUp()
}
})
}

if (props.pulldown !== null) {
bscroll.on('pullingDown', () => {
try {
props.pulldown().then(() => {
bscroll.finishPullDown()
})
} catch (e) {
// 传入非 Promise 函数
bscroll.finishPullDown()
}
})
}
})
</script>

<style scoped lang="scss">
@import './index.scss';
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
.wrapper {
overflow: hidden;
.content {
display: block;
}
// 开启横向滚动
&.x {
white-space: nowrap;
.content {
display: inline-block;
}
}
}

外部使用时需要传入样式指定宽高

1
2
3
<scroll-view style="height: 300px; width: 300px">
<li v-for="i in count" :key="i">当前第{{ i }}个元素</li>
</scroll-view>