仿抖音下拉刷新的动画
注:有缺陷,没有进行scroll事件的防抖、节流处理
某项目需要一下抖音的下拉刷新效果,目前做出来的效果如下:
做的还是很勉强,简而言之:
- 手指按住下滑时,需要出现一个旋转的
spinner
- 随着手指往下滑动,
spinner
要能跟着旋转 - 用户可以取消刷新,取消刷新的动作是再往上滑
- 在取消刷新的过程中,
spinner
需要能跟着倒着旋转回去,在超过一定距离后,即成功取消刷新,spinner
消失 - 若直接下滑到一定距离,再松开,则开始刷新,此时的
spinner
开始无限旋转,直到页面刷新完成
spinner怎么做?
也就是一个带缺口的1/4圆怎么做?
也即:设一个宽高相等的div
,再设 border-radius: 50%;
,这样就把div变成了一个圆
再设border: 4px solid transparent;
,这样就形成了一个圆环,但是这个圆环还是透明的,还看不出效果
1/4圆环的最重要一步就来了:
border-top-color: #f5f5f5;
border-left-color: #f5f5f5;
border-bottom-color: #f5f5f5;
大家可以看出这个缺口是哪一边吗? --对的,就是右边,不过还隐藏了一点就是div默认的background
的就是background: transparent;
,所以此时显示的圆环就是一个4px
宽的背景色为#f5f5f5
的1/4圆环
旋转的spinner怎么做?
一个属性搞定:transform: rotate(360deg);
,此时表示将元素旋转360度
ransform 属性向元素应用 2D 或 3D 转换。该属性允许我们对元素进行旋转、缩放、移动或倾斜
手指下滑,spinner要能跟着旋转
也是这次的核心:
主要思路: touchstart
事件得到用户滑动的开始坐标,touchmove
中去判断用户滑动的的位移,touchend
事件判断用户滑动结束时的坐标,有了这3对值,就可以进行很多操作了
touchstart
: 得到startX
、startY
touchmove
: 得到moveX
、moveY
touchend
: 得到endX
、endY
要手指下滑,spinner
跟着旋转,也就是要手指下滑的距离与spinner
的transform: rotate(xxxdeg);
成比例关系,手指下滑的距离就是moveY-startY
可以用diff
表示
// 使得转动的角度,随着diff的距离变动
spinner.style.transform = 'rotate('+diff*1.2+'deg)';
这样基本的思路就确定了,但还有一个隐藏的地方,怎么确定用户是在下滑?
参考:模仿抖音的全屏滚动效果
GetSlideAngle(dx, dy) {
// atan2() 方法可返回从 x 轴到点 (x,y) 之间的角度
return Math.atan2(dy, dx) * 180 / Math.PI;
},
GetSlideDirection(startX, startY, endX, endY) {
let dy = startY - endY;
let dx = endX - startX;
let result = 0;
if (Math.abs(dx) < 50 && Math.abs(dy) < 50) {
return result;
}
let angle = this.GetSlideAngle(dx, dy);
if (angle >= -45 && angle < 45) {
result = 4;
} else if (angle >= 45 && angle < 135) {
result = 1; //向上
} else if (angle >= -135 && angle < -45) {
result = 2; // 向下
} else if (
(angle >= 135 && angle <= 180) ||
(angle >= -180 && angle < -135)
) {
result = 3;
}
return result;
}
之后在touchmove
的时候将,startX
、startY
、moveX
、moveY
传入就可以判断此时是否在下滑
用户取消刷新,又倒着上滑的时候
上述的spinner
与滑动距离的随动关系,仍可以用,主要是怎么判断用户是在取消刷新?
例如,用户下滑到一定程度,再上滑,但上滑不超过起始点,那么如果一开始用的是startX
和startY
来判断,此时用户仍会被判断会下滑啊...
解决方法:定义一个maxDown
变量来存储用户下滑的最大距离,下滑过程中,touchmove
事件会被不断的触发,于是diff
的值就会不断被计算出来
// 存储用户下滑的最大距离
if(this.maxDown<diff) {this.maxDown = diff}
// 但用户上滑到一定距离的时候
if(this.maxDown-diff) > this.maxDown*2/3) {
// 下滑标志置为false
this.moveDownFlag = false;
// 隐藏spinner
this.spinner = false;
}
直接下滑到一定距离,再松开
此时用户触发刷新操作,spinner
开始无限旋转,直到页面刷新完成
这里判断用户正常下滑比较容易,只要在touchend
事件时,用户是在下滑而且之前的moveDownFlag
是为true
,这样就可以了
但怎么让spinner无限旋转,而且要充当页面刷新的loading效果呢?
我采用的是vue
,所以可以用动态class
来实现
vue
的template
部分:
<div class="main__indicator" ref="spinner" :class="{'active': isLoading}" v-show="spinner"></div>
为了性能,这里采用css动画
,使用animation
+keyframes
的组合,CSS @keyframes Rule
sass
:
@keyframes kebab-spinner-rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
&.active {
animation:kebab-spinner-rotate 0.8s infinite linear;
}
js:
if(direction === 2 && this.moveDownFlag) {
this.isLoading = true;
// 发送ajax更新,此时充当loading效果
// ajax code ...
if(response.data.code === 200) {
this.isLoading = false;
}
}
但有一个缺陷就是,当spinner
无限旋转时与之前的旋转角度有点衔接补上。。。
隐藏彩蛋,固定/绝对定位的水平居中问题
需要注意的是,spinner
是固定定位的,而从一开始的预览图中可以看出,它是水平居中的,怎么实现固定定位的水平居中呢?
我的方法是参考的网上的,具体出处有点不清楚了...
left: 50%;
height: 30px;
width: 30px;
margin-left: -15px;