上一篇,我们实现了h5 实现向左平滑,出现按钮操作,封装组件,模拟购物车左滑删除,现在我们将要实现一个下拉刷新的组件,模拟原生下拉刷新的操作。这对h5手势的要求也是蛮高的,接下来,我们就一步一步的实现下拉刷新的操作。
需求背景
在原生app中嵌入h5页面,有些页面需要下拉刷新操作,一般比较简单的方法就是使用其他框架封装的下拉刷新,比如说mint-ui框架中的loadmore组件就可以实现下拉刷新操作,但是样式上可能就和整个app的风格不一致了。既然都是封装好的组件,那为什么我们不能自己封装一个组件,并且样式风格由我们自己来定了。
基础知识
touchstart事件:事件对象event,包含手指触摸的位置
touchmove事件:事件对象event,包含手指滑动的位置
touchend事件:事件对象event,包含手指离开屏幕的位置
封装组件
1. 首先我们先考虑下拉刷新的html结构
<div class="pull-refresh" ref="scrollBox">
<!-- 下拉刷新文字,下拉状态 -->
<div class="pull-refreshing-text"></div>
<!-- 页面内容 -->
<slot/>
</div>
- 下拉刷新的文字有三种状态,下拉刷新=>释放刷新=>加载中,下面代码中刷新距离是自己定义的。
下拉即可刷新:页面下拉距离 < 刷新的距离
释放即可刷新:页面下拉距离 >= 刷新的距离
加载中:页面下拉距离 >= 刷新距离 && 手指释放
2. css样式
- 首先,下拉刷新的文字部分,一开始隐藏的,下拉出现
- 其次,slot部分的样式,我们可以忽略
.pull-refresh {
height: calc(~'100vh - 50px');
.pull-refreshing-text {
margin-top: -50px;
line-height: 50px;
height: 50rem;
font-size:14px;
color: #5a5a5a;
text-align: center;
}
}
- 最后下拉刷新的三种状态,我们定义一个下拉状态的变化moveState,0:初始状态(下拉即可刷新),1:释放即可刷新,2:加载中。所以我们的css样式和html结构可以根据moveState来变化
<!-- 下拉刷新文字,下拉状态 -->
<div class="pull-refreshing-text">
<!-- moveState为0和1的时候 下拉即可刷新,释放即可刷新 -->
<div v-if="moveState < 2">
<!-- 下拉刷新 箭头图片 moveState从0变成1的时候箭头图片旋转180度,这个由css实现-->
<img class="pull_icon" src="箭头图片" :class="{'pull_icon-rotate': moveState === 1}" alt>
{{ moveState === 0 ? '下拉即可刷新...' : '释放即可刷新...' }}
</div>
<!-- 加载中 moveState=2 -->
<div v-else>
<!-- loading状态的图片+文字 -->
<i class="pull_loading"></i> 加载中...
</div>
</div>
html结构增加下拉三种状态
.pull-refreshing-text {
margin-top: -50px;
line-height: 50px;
height: 50px;
font-size: 14px;
color: #5a5a5a;
text-align: center;
// 下拉即可刷新
.pull_icon {
height: 30px;
display: inline-block;
margin-right: 10px;
transition: transform 0.28s; // 箭头旋转动画时间
vertical-align: middle;
// 释放即可刷新
&.pull_icon-rotate {
transform: rotate(-180deg); // 箭头旋转180度
}
}
// 加载中
.pull_loading {
display: inline-block;
width: 20px;
height: 20px;
background-image: url('./loading.gif'); // loading动画
background-size: 20px 20px;
vertical-align: middle;
}
}
3. 绑定触摸滑动事件
- 绑定事件
在mounted钩子函数中绑定事件
let el = this.$refs.scrollBox
el.addEventListener('touchstart', e => {
this.touchStart(e)
})
el.addEventListener('touchmove', e => {
this.touchMove(e)
})
el.addEventListener('touchend', e => {
this.touchEnd(e)
})
- 触摸处理函数
在处理函数之前,我们首先要考虑好这个组件需要哪些全局变量,首先是移动的距离moveDistance,其次是触摸开始的起始y轴位置startY(为了在touchmove触发时候,计算y轴滑动距离),动画持续时间duration(手指离开屏幕之后,页面要回归到原始位置,这个动画的持续时间),下拉的三种状态moveState(0:下拉即可刷新,1:释放即可刷新,2:加载中)
在methods中处理相对应的绑定事件
touchStart(e) {
// 如果正在加载 则阻住触发
if (this.moveState === 2) return
// 触摸开始时,动画时间,移动距离归0
this.distance = 0
this.moveDistance = 0
// 获取初始y轴位置
this.startY = e.targetTouches[0].clientY
},
touchMove(e) {
// 如果正在加载 则阻止触发
if (this.moveState === 2) return
// 首先计算当前页面是否有滚动条,有滚动条,那么触摸滑动就是页面简单的上下滚动
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
if (scrollTop > 0) return
let move = e.targetTouches[0].clientY - this.startY
// 判断手指滑动的距离,只有为正数才代表用户下拉了。
if (move > 0) {
// 阻止默认事件
e.preventDefault()
// 增加滑动阻力的感觉
this.moveDistance = Math.pow(move, 0.8)
if (this.moveDistance > 50) {
// 如果滑动距离大于50 ,moveState状态更新为释放即可刷新
if (this.moveState === 1) return
this.moveState = 1
} else {
// 否则 恢复原样
if (this.moveState === 0) return
this.moveState = 0
}
}
}
touchEnd(e) {
// 如果正在加载 则阻住触发
if (this.moveState === 2) return
// 只要手指拿开,加上结束时的动画
this.duration = 300
if (this.moveDistance > 50) {
// 手指松开之后,就开始变成加载中的状态了
this.moveState = 2
this.moveDistance = 50
// 在家中,开始刷新页面数据,刷新之后有一个回调函数
this.$emit('refresh', () => {
// 这里就是成功后的回调了,如果该函数被调用,那就以为着加载数据完成,所以状态要回到0,当然需要在父组件调用。
this.moveState = 0
this.moveDistance = 0
})
} else {
// 否则 回到初始状态
this.moveDistance = 0
}
}
4. 下拉动画
使用vue的钩子函数computed计算样式
computed: {
style() {
return {
transition: `${this.duration}ms`,
transform: `translate3d(0,${this.moveDistance}px, 0)`
}
}
}
html加上style,形成动画
<div class="pull-refresh" ref="scrollBox" :style="style">
</div>
至此,我们完成了基本的下拉刷新的功能组件,但是我们还可以进一步优化这个组件,比如说页面有个手势触发之后,下拉的手势就需要禁止掉,这个时候我们需要考虑到。阻止事件的触发stop属性由父组件传进来,根据这个stop值,我们控制事件的触发与否
更改代码如下
el.addEventListener('touchstart', e => {
// 根据stop值阻止事件触发
!this.stop && this.touchStart(e)
})
el.addEventListener('touchmove', e => {
// 根据stop值阻止事件触发
!this.stop && this.touchMove(e)
})
el.addEventListener('touchend', e => {
// 根据stop值阻止事件触发
!this.stop && this.touchEnd(e)
})
完整的下拉刷新的组件就全部完成了,下面就是怎么引用这个组件了
使用组件
import pullRefresh from '/pullRefresh.vue'
components: {pullRefresh}
// html
<pull-refresh @refresh="refresh">
...
</pull-refresh>
// 方法
refresh() {
// 刷新页面,重新请求接口
}
网友评论