注:有缺陷,没有进行scroll事件的防抖、节流处理

某项目需要一下抖音的下拉刷新效果,目前做出来的效果如下:

做的还是很勉强,简而言之:

  1. 手指按住下滑时,需要出现一个旋转的spinner
  2. 随着手指往下滑动,spinner要能跟着旋转
  3. 用户可以取消刷新,取消刷新的动作是再往上滑
  4. 在取消刷新的过程中,spinner需要能跟着倒着旋转回去,在超过一定距离后,即成功取消刷新,spinner消失
  5. 若直接下滑到一定距离,再松开,则开始刷新,此时的spinner开始无限旋转,直到页面刷新完成

spinner怎么做?

也就是一个带缺口的1/4圆怎么做?

参考:mint-ui/mint-spinner

也即:设一个宽高相等的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: 得到startXstartY
  • touchmove: 得到moveXmoveY
  • touchend: 得到endXendY

要手指下滑,spinner跟着旋转,也就是要手指下滑的距离与spinnertransform: 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的时候将,startXstartYmoveXmoveY传入就可以判断此时是否在下滑

用户取消刷新,又倒着上滑的时候

上述的spinner与滑动距离的随动关系,仍可以用,主要是怎么判断用户是在取消刷新?

例如,用户下滑到一定程度,再上滑,但上滑不超过起始点,那么如果一开始用的是startXstartY来判断,此时用户仍会被判断会下滑啊...

解决方法:定义一个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来实现

vuetemplate部分:

<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;

标签: none

添加新评论