vue下拉刷新组件的开发及slot的使用

2024-05-25
14885 分钟
...

“下拉刷新”和“上滑加载更多”功能在前端、尤其是移动端项目中非常重要,这里笔者由曾经做过的vue项目中的“blink”功能和各位探讨下【下拉刷新】组件的开发:

JavaScript正式开篇

在前端项目的 components 文件夹下新建 pullRefreshView 文件夹,在其中新建组件 index.vue:(它代表“整个屏幕”,通过slot插入页面其他内容而不是传统的设置遮罩层触发下拉刷新)

首先需要编写下拉刷新组件的 template,这里用到 <slot>,代码如下:

<template>
    <div class="pullRefreshView" @touchmove="touchmove" @touchstart="touchstart" @touchend="touchend">
        <div ref="circleIcon" class="circle-icon">
            <div ref="circleIconInner" class="circle-icon-inner"></div>
        </div>
        <slot></slot>
    </div>
</template>

上面代码中,最外层使用了一个 div 用来包裹,作为事件绑定的容器,同时新建一个圆形 icon 的 div .circleIcon,我们将此 icon 样式设置在屏幕外,达到隐藏的效果,代码如下:

<style>
    .circle-icon{
        position: absolute;
        left: 0.625rem;
        top: -1.875rem;
    }
    .circle-icon-inner{
        width: 1.5625rem;
        height: 1.5625rem;
        background-image: url('圆圈图片地址');
        background-size: cover;
    }
    .circle-rotate{
        animation: xuzhuan .8s linear infinite;
    }
    @keyframes xuzhuan{
        0%{}
        25%{}
        50%{}
        75%{}
        100%{}
    }
</style>

下拉刷新组件的 UI 基本编写完毕,接下来就要绑定事件了,通过上述分析,加上我们之前章节开发图片查看器的原理,我们需要用到移动端 touchstart,touchmove,touchend 事件,可以实现下拉刷新效果。

首先,监听 touchstart 事件:

touchstart(evt){
    this.pullRefresh.dragStart=evt.targetTouches[0].clientY
    this.$refs.circleIcon.style.webkitTransition='none'
},

在 touchstart 事件中,我们主要做的是记录一些初始值,包括手指第一次接触屏幕时的位置,然后将圆形 icon 的动画效果先隐藏。

然后,监听 touchmove 事件:

touchmove(evt){
    if(this.pullRefresh.dragStart===null){
        return
    }
    let target=evt.targetTouches[0]
    // 向上滑为正,向下拉为负
    this.pullRefresh.percentage=(this.pullRefresh.dragStart-target.clientY)/window.screen.height
    let scrollTop=document.documentElement.scrollTop || document.body.scrollTop
    if(scrollTop===0){
        //this.pullRefresh指data中的pullRefresh对象(下方有),而evt即事件event参数
        if(this.pullRefresh.percentage<0 && evt.cancelable){
            evt.preventDefault()
            this.pullRefresh.joinRefreshFlag=true
            let translateY=-this.pullRefresh.percentage*this.pullRefresh.moveCount
            if(Math.abs(this.pullRefresh.percentage)<=this.pullRefresh.dragThreshold){
                let rotate=translateY/30*360
                this.$refs.circleIcon.style.webkitTransform='translate3d(0'+translateY+'px,0) rotate('+rotate+'deg)'
            }
        }else{
            if(this.pullRefresh.joinRefreshFlag===null){
                this.pullRefresh.joinRefreshFlag=false
            }
        }
    }else{
        if(this.pullRefresh.joinRefreshFlag===null){
            this.pullRefresh.joinRefreshFlag=false
        }
    }
},

在 touchmove 事件里,我们主要做的是根据手指移动的量来实时将圆形 icon 移动并旋转,这里有几点确实要说明一下:

  • 我们的下拉刷新触发的时机是在页面处于屏幕顶部并且手指向下拖动,这两个条件,缺一不可,在代码中,我们利用 scrollTop == 0this.pullRefresh.percentage < 0 来判断。
  • 在进入下拉刷新状态时,此时手指不断向下拖动,首先圆形 icon.circleIcon 会向下滚动并旋转,当滚动到临界值时就只原地旋转。
  • 如果手指在向上拖动,圆形 icon.circleIcon 就会向上滚动并旋转。
  • 直到手指离开屏幕前,都不会触发下拉刷新,只是圆形 icon.circleIcon 在不停的上下移动。

监听 touchend 事件:

touchend(evt){
    if(this.pullRefresh.percentage===0){
        return
    }
    if(Math.abs(this.pullRefresh.percentage)>this.pullRefresh.dragThreshold && this.pullRefresh.joinRefreshFlag){
        this.$emit('onRefresh')
        this.$refs.circleIconInner.classList.add('circle-rotate')
        setTimeout(()=>{
            this.$refs.circleIconInner.classList.remove('circle-rotate')
            this.$refs.circleIcon.style.webkitTransition='330ms'
            this.$refs.circleIcon.style.webkitTransform='translate3d(0,0,0) rotate(0deg)'
        },700)
    }else{
        if(this.pullRefresh.joinRefreshFlag){
            this.$refs.circleIcon.style.webkitTransition='330ms'
            this.$refs.circleIcon.style.webkitTransform='translate3d(0,0,0) rotate(0deg)'
        }
    }
    this.pullRefresh.joinRefreshFlag=null
    this.pullRefresh.dragStart=null
    this.pullRefresh.percentage=0
}

在 touchend 事件中,我们主要是做一些动画执行的操作,大家可以看看代码中的注释,这里说明一下:

  1. 此时手指离开屏幕,位移量达到临界值时,并且也有进入下拉刷新的标志位,就表明要触发正在刷新。此时圆形 icon原地旋转,并触发下拉刷新回调方法,延迟 700ms 后向上收起。
  2. 我们在实现圆形 icon 时的旋转和位移动画时,用了两个 div,在 touchmove 时,我们主要对外层的 div 也就是 ref=circleIcon,来实现位移和旋转。
  3. 在 touchend 时,我们主要给内层的 div 也就是 ref=circleIconInner 来加 animation 动画,因为无法给一个 div 同时使用位移旋转和 animation 动画,所以这里一个技巧就是给父元素设置位移和旋转,它的子元素在不设置任何 CSS 动画样式时,是会随着父元素而生效的。

最后,我们看下【data】中都有什么:

data(){
    return{
        pullRefresh:{
            dragStart:null,   //开始抓取标志位
            percentage:0,   //拖动量(百分比)
            dragThreshold:0.3,   //临界值
            moveCount:200,   //位移系数,可以调节圆形图片icon的运动速率
            joinRefreshFlag:null,   //进入刷新状态的标志位(true)
        }
    }
},

补充:slot

<template>中为什么有<slot>

slot有三种形式:

  1. 普通插槽
  2. 具名插槽
  3. 作用域插槽

可能我们一般用具名slot的时候比较多,但是第一种也格外好用——正因为它没有名字,所以引用这个组件的另一个组件中包裹其中的所有内容都归这个slot所有:

假定my-component组件中有如下模板:

<div>
    <h2>我是子组件</h2>
    <slot>只有在没有内容分发的情况下这句话才会出现</slot>
</div>

父组件模板:

<div>
    <h1>这是父组件地盘</h1>
    <my-component>
        <p>这是一些初始内容</p>
        <p>这是更多的内容</p>
    </my-component>
</div>

最后就会被渲染成这样:

<div> 
    <h1>这是父组件地盘</h1>
    <div> 
        <h2>我是子组件</h2>
        <p>这是一些初始内容</p>
        <p>这是更多的内容</p>
    </div> 
</div>

所以这里这样做,就是为了在“父组件”中调用时让“下拉的动画”更自然,但又不会增加一个文件的负担。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
vue2025-10-22
shallowRef 与 ref 的区别、场景与坑位
结论先行: 会 深度 地把你放进去的对象转成响应式(递归代理),因此"对象内部的改动"也会触发视图更新。 只在 顶层 做依赖追踪: 更换 才触发更新 ;若你只是"在原对象上改属性",不会触发,除非手动 。 1\. 快速对比 维度 追踪粒度 深度(递归) 仅顶层 对象内部属性变更 会触发更新 不会 触发(除非 或重新赋值) 性能 有递归 & 依赖跟踪开销 更轻量,适合大对象/第三方实例 适用对象 普通标量、普通对象、数组 第三方实例(图表...
学习面试
vue2025-10-20
Vue 3 中的 setup执行时机与异步逻辑详解
Vue 3 中的 执行时机与异步逻辑详解 本文详细介绍 Vue 3 组合式 API 的核心函数 :它在组件生命周期中的执行时机、异步行为( 的影响)、常见用法和最佳实践。 一、 的执行时机 是 Vue 3 组件实例创建后、渲染前执行的函数。 它相当于 Vue 2 中的 和 的合体。 执行流程: 也就是说: 所有响应式数据( 、 、 )都在这里定义; 所有生命周期钩子( 、 等)都在这里注册; 模板渲染会等待 执行完成(或异步返回的 Pr...
学习面试
vue2025-10-08
vue中ref与reactive
Vue 3 的 与 :区别、实现,以及为什么更推荐 1\. 一句话结论 :适合 原始类型 或 独立、细粒度 的状态单元;类型推断明确、解构安全、追踪粒度更小, 默认首选 。 :适合管理 对象/集合 这类结构化状态;但 解构会丢失响应性 , 默认 非深度 ,需要正确的使用姿势配合。 2\. 用法与语义差异 2.1 基本示例 2.2 模板中的解包(unwrapping) 在 模板 里, 会 自动解包 ,可直接写 ,不必 。 在响应式对象中...
学习面试
vue2025-09-23
Vue3 中computed属性依赖总结
Vue3 属性依赖总结 1. 依赖收集机制 的 getter 内部 读取 到哪些响应式变量 ( / ),就会把这些变量收集为依赖。 当这些依赖发生变化时, 会重新计算。 2. 如果只返回常量 始终是 。 修改 或 不会触发 重新计算,因为 getter 内没有访问它们。 3. 如果访问了响应式变量但仍返回常量 初始化时会打印一次。 当 或 改变时,getter 会重新执行(再次打印),因为它们被收集为依赖。 但 依旧始终是 。 4. 总...
学习面试
vue2025-09-17
template标签为什么不可以使用v-show
前言 写 Vue 时,最容易踩的小坑之一,就是给 套一个 。编辑器不报错,页面却“消失”了。很多人以为这是 bug,其实它只是 Vue 的底层设计在默默提醒: 不是真节点,它只是个“草稿纸”。 一、为什么 拒绝 v show 的本质是切换元素的 CSS: 。浏览器要找到真实 DOM 节点,才能给它加样式。而 在运行时会被完全剥离,不会留下任何标签,Vue 找不到节点,也就无处下刀。 举个反面教材: 打开控制台,你会看到 还是生成了,但...
学习面试
vue2025-09-17
Vue 3中watchEffect自动追踪详解
Vue 3 自动追踪详解 一、是什么 是 Vue 3 Composition API 提供的一个响应式副作用(side effect)函数。 它会 立即执行一次回调函数 ,并且在回调函数中访问到的所有响应式数据(ref、reactive、computed 等)都会被 自动追踪 。 当这些依赖的响应式数据发生变化时,回调会重新执行。 简单理解: 能“自动发现”它用到的响应式变量,不需要手动指定依赖。 二、基本用法 特点: 第一次立即执行...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录