欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

vue移动端下拉刷新和上拉加载的实现代码

程序员文章站 2022-07-22 16:27:01
由于自身的项目比较简单,只有几个h5页面,用来嵌入app中,所有没有引入移动端的ui框架,但是介于能让用户在浏览h5页面时有下拉刷新和上拉加载,有更好的用户体验,自己写组件...

由于自身的项目比较简单,只有几个h5页面,用来嵌入app中,所有没有引入移动端的ui框架,但是介于能让用户在浏览h5页面时有下拉刷新和上拉加载,有更好的用户体验,自己写组件实现。

vue移动端下拉刷新和上拉加载的实现代码

1、下拉刷新dropdownrefresh.vue

<template lang="html">
  <div class="refreshmoudle" @touchstart="touchstart($event)" @touchmove="touchmove($event)" @touchend="touchend($event)" :style="{transform: 'translate3d(0,' + top + 'px, 0)'}">
   <header class="pull-refresh">
    <slot name="pull-refresh">
     <div class="down-tip" v-if="dropdownstate==1">
      <img v-if="dropdownstatetext.downimg" class="down-tip-img" :src="require('../../assets/images/refreshandreload/'+dropdownstatetext.downimg)">
      <span class="down-tip-text">{{dropdownstatetext.downtxt}}</span>
     </div>
     <div class="up-tip" v-if="dropdownstate==2">
      <img v-if="dropdownstatetext.upimg" class="up-tip-img" :src="require('../../assets/images/refreshandreload/'+dropdownstatetext.upimg)">
      <span class="up-tip-text">{{dropdownstatetext.uptxt}}</span>
     </div>
     <div class="refresh-tip" v-if="dropdownstate==3">
      <img v-if="dropdownstatetext.refreshimg" class="refresh-tip-img" :src="require('../../assets/images/refreshandreload/'+dropdownstatetext.refreshimg)">
      <span class="refresh-tip-text">{{dropdownstatetext.refreshtxt}}</span>
     </div>
    </slot>
   </header>
   <slot></slot>
  </div>
</template>
<script>
export default {
 props: {
  onrefresh: {
   type: function,
   required: false
  }
 },
 data () {
  return {
   defaultoffset: 100, // 默认高度, 相应的修改.releshmoudle的margin-top和.down-tip, .up-tip, .refresh-tip的height
   top: 0,
   scrollistotop: 0,
   starty: 0,
   isdropdown: false, // 是否下拉
   isrefreshing: false, // 是否正在刷新
   dropdownstate: 1, // 显示1:下拉刷新, 2:松开刷新, 3:刷新中……
   dropdownstatetext: {
    downtxt: '下拉刷新',
    downimg: '',
    uptxt: '松开刷新',
    upimg: 'release.png',
    refreshtxt: '刷新中...',
    refreshimg: 'refresh.gif'
   }
  }
 },
 created () {
  if (document.queryselector('.down-tip')) {
   // 获取不同手机的物理像素(dpr),以便适配rem
   this.defaultoffset = document.queryselector('.down-tip').clientheight || this.defaultoffset
  }
 },
 methods: {
  touchstart (e) {
   this.starty = e.targettouches[0].pagey
  },
  touchmove (e) {
   this.scrollistotop = document.documentelement.scrolltop || window.pageyoffset || document.body.scrolltop // safari 获取scrolltop用window.pageyoffset
   if (e.targettouches[0].pagey > this.starty) { // 下拉
    this.isdropdown = true
    if (this.scrollistotop === 0 && !this.isrefreshing) {
     // 拉动的距离
     let diff = e.targettouches[0].pagey - this.starty - this.scrollistotop
     this.top = math.pow(diff, 0.8) + (this.dropdownstate === 3 ? this.defaultoffset : 0)
     if (this.top >= this.defaultoffset) {
      this.dropdownstate = 2
      e.preventdefault()
     } else {
      this.dropdownstate = 1
      e.preventdefault()
     }
    }
   } else {
    this.isdropdown = false
    this.dropdownstate = 1
   }
  },
  touchend (e) {
   if (this.isdropdown && !this.isrefreshing) {
    if (this.top >= this.defaultoffset) { // do refresh
     this.refresh()
     this.isrefreshing = true
     console.log(`do refresh`)
    } else { // cancel refresh
     this.isrefreshing = false
     this.isdropdown = false
     this.dropdownstate = 1
     this.top = 0
    }
   }
  },
  refresh () {
   this.dropdownstate = 3
   this.top = this.defaultoffset
   settimeout(() => {
    this.onrefresh(this.refreshdone)
   }, 1200)
  },
  refreshdone () {
   this.isrefreshing = false
   this.isdropdown = false
   this.dropdownstate = 1
   this.top = 0
  }
 }
}
</script>

<!-- add "scoped" attribute to limit css to this component only -->
<style scoped>
.refreshmoudle {
 width: 100%;
 margin-top: -100px;
 -webkit-overflow-scrolling: touch; /* ios5+ */
}
.pull-refresh {
 width: 100%;
 color: #999;
 transition-duration: 200ms;
}
.refreshmoudle .down-tip,
.up-tip,
.refresh-tip {
 display: flex;
 align-items: center;
 justify-content: center;
 height: 100px;
}
.refreshmoudle .down-tip-img,
.up-tip-img,
.refresh-tip-img {
 width: 35px;
 height: 35px;
 margin-right: 5px;
}
</style>
2、上拉加载pullupreload.vue
<template lang="html">
 <div class="loadmoudle" @touchstart="touchstart($event)" @touchmove="touchmove($event)" :style="{transform: 'translate3d(0,' + top + 'px, 0)'}">
  <slot></slot>
  <footer class="load-more">
   <slot name="load-more">
    <div class="moredata-tip" v-if="pullupstate==1">
     <span class="moredata-tip-text">{{pullupstatetext.moredatatxt}}</span>
    </div>
    <div class="loadingmoredata-tip" v-if="pullupstate==2">
     <span class="icon-loading"></span>
     <span class="loadingmoredata-tip-text">{{pullupstatetext.loadingmoredatatxt}}</span>
    </div>
    <div class="nomoredata-tip" v-if="pullupstate==3">
     <span class="connectingline"></span>
     <span class="nomoredata-tip-text">{{pullupstatetext.nomoredatatxt}}</span>
     <span class="connectingline"></span>
    </div>
   </slot>
  </footer>
 </div>
</template>

<script>
export default {
 props: {
  parentpullupstate: {
   default: 0
  },
  oninfiniteload: {
   type: function,
   require: false
  }
 },
 data () {
  return {
   top: 0,
   starty: 0,
   pullupstate: 0, // 1:上拉加载更多, 2:加载中……, 3:我是有底线的
   isloading: false, // 是否正在加载
   pullupstatetext: {
    moredatatxt: '上拉加载更多',
    loadingmoredatatxt: '加载中...',
    nomoredatatxt: '我是有底线的'
   }
  }
 },
 methods: {
  touchstart (e) {
   this.starty = e.targettouches[0].pagey
  },
  touchmove (e) {
   if (e.targettouches[0].pagey < this.starty) { // 上拉
    this.judgescrollbartotheend()
   }
  },

  // 判断滚动条是否到底
  judgescrollbartotheend () {
   let innerheight = document.queryselector('.loadmoudle').clientheight
   // 变量scrolltop是滚动条滚动时,距离顶部的距离
   let scrolltop = document.documentelement.scrolltop || window.pageyoffset || document.body.scrolltop
   // 变量scrollheight是滚动条的总高度
   let scrollheight = document.documentelement.clientheight || document.body.scrollheight
   // 滚动条到底部的条件
   if (scrolltop + scrollheight >= innerheight) {
    if (this.pullupstate !== 3 && !this.isloading) {
     this.pullupstate = 1
     this.infiniteload()
     // settimeout(() => {
     //  this.infiniteload()
     // }, 200)
    }
   }
  },

  infiniteload () {
   this.pullupstate = 2
   this.isloading = true
   settimeout(() => {
    this.oninfiniteload(this.infiniteloaddone)
   }, 800)
  },
  infiniteloaddone () {
   this.pullupstate = 0
   this.isloading = false
  }
 },
 watch: {
  parentpullupstate (curval, oldval) {
   this.pullupstate = curval
  }
 }
}
</script>

<!-- add "scoped" attribute to limit css to this component only -->
<style scoped>
.load-more {
 width: 100%;
 color: #c0c0c0;
 background: #f7f7f7;
}
.moredata-tip,
.loadingmoredata-tip,
.nomoredata-tip {
 display: flex;
 align-items: center;
 justify-content: center;
 height: 150px;
}
.loadmoudle .icon-loading {
 display: inline-flex;
 width: 35px;
 height: 35px;
 background: url(../../assets/images/refreshandreload/loading.png) no-repeat;
 background-size: cover;
 margin-right: 5px;
 animation: rotating 2s linear infinite;
}
@keyframes rotating {
 0% {
  transform: rotate(0deg);
 }
 100% {
  transform: rotate(1turn);
 }
}
.connectingline {
 display: inline-flex;
 width: 150px;
 height: 2px;
 background: #ddd;
 margin-left: 20px;
 margin-right: 20px;
}
</style>
3、对两个组件的使用
<template>
 <section class="container">
  <v-refresh :on-refresh="onrefresh">
  <v-reload :on-infinite-load="oninfiniteload" :parent-pull-up-state="infiniteloaddata.pullupstate">
  <div class="bank_lists">
   <div class="bank_box">
    <div class="bank_list" v-for="item in bank_list" :key="item.id">
     <div class="bank_icon" :style="{ 'background': 'url(' + require('../assets/images/56_56/'+item.iconname) + ') no-repeat', 'background-size': '100%' }" ></div>
     <span class="bank_name">{{item.bankname}}</span>
    </div>
   </div>
  </div>
  <div class="hot_box">
   <div class="hot_header">
    <span class="hot_name">热门推荐</span>
    <div class="more_box">
     <span class="more_text">查看更多</span>
     <span class="more_icon"></span>
    </div>
   </div>
   <div class="hot_centenrt">
    <div class="hot_centent_left">
     <span class="hot_left_name">{{hot_centent_left.name}}</span>
     <span class="hot_left_desc">{{hot_centent_left.desc}}</span>
     <div class="hot_left_img" :style="{ 'background': 'url(' + require('../assets/images/bank/'+hot_centent_left.imgname) + ') no-repeat', 'background-size': '100%' }" ></div>
    </div>
    <div class="hot_centent_right">
     <div class="hot_right_top">
      <div class="hot_right_text_box">
       <span class="hot_right_name">{{hot_c_r_one.name}}</span>
       <span class="hot_right_desc">{{hot_c_r_one.desc}}</span>
      </div>
      <div class="hot_right_img" :style="{ 'background': 'url(' + require('../assets/images/bank/'+hot_c_r_one.imgname) + ') no-repeat', 'background-size': '100%' }" ></div>
     </div>
     <div class="hot_right_bottom">
      <div class="hot_right_text_box2">
       <span class="hot_right_name2">{{hot_c_r_two.name}}</span>
       <span class="hot_right_desc2">{{hot_c_r_two.desc}}</span>
      </div>
      <div class="hot_right_img" :style="{ 'background': 'url(' + require('../assets/images/bank/'+hot_c_r_two.imgname) + ') no-repeat', 'background-size': '100%' }" ></div>
     </div>
    </div>
   </div>
  </div>
  <div class="card_state">
   <div class="card_progress border-right">
    <div class="progress_icon"></div>
    <div class="card_text">
     <span class="card_state_name">{{card_progress.name}}</span>
     <span class="card_desc">{{card_progress.desc}}</span>
    </div>
   </div>
   <div class="card_activation">
    <div class="activation_icon"></div>
    <div class="card_text">
     <span class="card_state_name">{{card_activation.name}}</span>
     <span class="card_desc">{{card_activation.desc}}</span>
    </div>
   </div>
  </div>
  <div class="card_order">
   <div class="border_bottom card_content_bottom">
    <div class="hot_header">
     <span class="hot_name">热卡排行</span>
    </div>
   </div>
   <div slot="load-more">
   <li class="card_list" v-for="(item,index) in infiniteloaddata.pulluplist" :key="item.id">
    <div class="card_content" :class="infiniteloaddata.pulluplist.length - 1 != index? 'card_content_bottom':''">
     <div class="card_img" :style="{ 'background': 'url(' + require('../assets/images/bank/'+item.imgname) + ') no-repeat', 'background-size': '100%' }" ></div>
     <div class="card_list_text">
      <p class="card_name">{{item.cardname}}</p>
      <p class="card_title">{{item.cardtitle}}</p>
      <div class="card_words_lists">
       <div class="card_words bor_rad_20">
        <p class="card_word">{{item.cardwordone}}</p>
       </div>
       <div v-if="item.cardwordtwo" class="card_words card_words_two bor_rad_20">
        <p class="card_word">{{item.cardwordtwo}}</p>
       </div>
      </div>
     </div>
    </div>
   </li>
   </div>
  </div>
  </v-reload>
  </v-refresh>
 </section>
</template>

<script>
import dropdownrefresh from './common/dropdownrefresh'
import pullupreload from './common/pullupreload'
export default {
 data () {
  return {
   bank_list: [
    {
     iconname: 'zhaoshang.png',
     bankname: '招商银行'
    },
    {
     iconname: 'minsheng.png',
     bankname: '民生银行'
    },
    {
     iconname: 'pingancar.png',
     bankname: '平安联名'
    },
    {
     iconname: 'xingye.png',
     bankname: '兴业银行'
    },
    {
     iconname: 'shanghai.png',
     bankname: '上海银行'
    },
    {
     iconname: 'jiaotong.png',
     bankname: '交通银行'
    },
    {
     iconname: 'guangda.png',
     bankname: '光大银行'
    },
    {
     iconname: 'more.png',
     bankname: '全部银行'
    }
   ],
   hot_centent_left: {
    bankname: '交通银行',
    name: '交行y-power黑卡',
    desc: '额度100%取现',
    imgname: 'jiaohangy-power.png'
   },
   hot_c_r_one: {
    bankname: '招商银行',
    name: '招行young卡',
    desc: '生日月双倍积分',
    imgname: 'zhaohangyoung.png'
   },
   hot_c_r_two: {
    bankname: '光大银行',
    name: '光大淘票票公仔联名卡',
    desc: '电影达人必备',
    imgname: 'guangdalianming.png'
   },
   card_progress: {
    name: '办卡进度',
    desc: '让等待随处可见'
   },
   card_activation: {
    name: '办卡激活',
    desc: '让等待随处可见'
   },
   card_list: [
    {
     bankname: '平安联名',
     imgname: 'pinganqiche.png',
     cardname: '平安银行信用卡',
     cardtitle: '平安银行汽车之家联名单币卡',
     cardwordone: '首年免年费',
     cardwordtwo: '加油88折'
    },
    {
     bankname: '上海银行',
     imgname: 'shanghaitaobao.png',
     cardname: '上海银行信用卡',
     cardtitle: '淘宝金卡',
     cardwordone: '积分抵现',
     cardwordtwo: '首刷有礼'
    },
    {
     bankname: '华夏银行',
     imgname: 'huaxiaiqiyi.png',
     cardname: '华夏银行信用卡',
     cardtitle: '华夏爱奇艺悦看卡',
     cardwordone: '送爱奇艺会员',
     cardwordtwo: '商城8折'
    },
    {
     bankname: '浦发银行',
     imgname: 'pufajianyue.png',
     cardname: '浦发银行信用卡',
     cardtitle: '浦发银行简约白金卡',
     cardwordone: '团购立减',
     cardwordtwo: '酒店优惠 免年费'
    },
    {
     bankname: '中信银行',
     imgname: 'zhongxinbaijin.png',
     cardname: '中信银行信用卡',
     cardtitle: '中信银行i白金信用卡',
     cardwordone: '首刷有礼',
     cardwordtwo: '双倍积分'
    }
   ],

   // 上拉加载的设置
   infiniteloaddata: {
    initialshownum: 3, // 初始显示多少条
    everyloadingnum: 3, // 每次加载的个数
    pullupstate: 0, // 子组件的pullupstate状态
    pulluplist: [], // 上拉加载更多数据的数组
    showpulluplistlength: this.initialshownum // 上拉加载后所展示的个数
   }
  }
 },
 mounted () {
  this.getstartpullupstate()
  this.getpullupdefdata()
 },
 methods: {
  // 获取上拉加载的初始数据
  getpullupdefdata () {
   this.infiniteloaddata.pulluplist = []
   for (let i = 0; i < this.infiniteloaddata.initialshownum; i++) {
    this.infiniteloaddata.pulluplist.push(this.card_list[i])
   }
  },

  getstartpullupstate () {
   if (this.card_list.length === this.infiniteloaddata.initialshownum) {
    // 修改子组件的pullupstate状态
    this.infiniteloaddata.pullupstate = 3
   } else {
    this.infiniteloaddata.pullupstate = 0
   }
  },

  // 上拉一次加载更多的数据
  getpullupmoredata () {
   this.showpulluplistlength = this.infiniteloaddata.pulluplist.length
   if (this.infiniteloaddata.pulluplist.length + this.infiniteloaddata.everyloadingnum > this.card_list.length) {
    for (let i = 0; i < this.card_list.length - this.showpulluplistlength; i++) {
     this.infiniteloaddata.pulluplist.push(this.card_list[i + this.showpulluplistlength])
    }
   } else {
    for (let i = 0; i < this.infiniteloaddata.everyloadingnum; i++) {
     this.infiniteloaddata.pulluplist.push(this.card_list[i + this.showpulluplistlength])
    }
   }
   if (this.card_list.length === this.infiniteloaddata.pulluplist.length) {
    this.infiniteloaddata.pullupstate = 3
   } else {
    this.infiniteloaddata.pullupstate = 0
   }
  },

  // 下拉刷新
  onrefresh (done) {
   // 如果下拉刷新和上拉加载同时使用,下拉时初始化上拉的数据
   this.getstartpullupstate()
   this.getpullupdefdata()
   done() // call done
  },

  // 上拉加载
  oninfiniteload (done) {
   if (this.infiniteloaddata.pullupstate === 0) {
    this.getpullupmoredata()
   }
   done()
  }
 },
 components: {
  'v-refresh': dropdownrefresh,
  'v-reload': pullupreload
 }
}
</script>

<!-- add "scoped" attribute to limit css to this component only -->
<style scoped>
@import "../assets/css/not2rem.css";
.container {
 display: flex;
 flex-direction: column;
 width: 750px;
 height: 1334px;
 background-color: #f7f7f7;
}

.bank_lists {
 width: 100%;
 height: 320px;
 margin-top: 0px;
 background-color: #fff;
}

.bank_box {
 display: flex;
 flex-wrap: wrap;
 padding: 2px 7px 42px 7px;
}

.bank_list {
 width: 100px;
 height: 98px;
 margin: 40px 42px 0 42px;
}

.bank_icon {
 width: 56px;
 height: 56px;
 margin: 0 22px 18px;
}

.bank_name {
 display: inline-flex;
 width: 110px;
 height: 24px;
 line-height: 24px;
 font-size: 24px;
 color: #333;
}

.hot_box {
 width: 100%;
 height: 420px;
 margin-top: 10px;
 background: #fff;
}

.hot_header {
 display: flex;
 justify-content: space-between;
 align-items: center;
 width: 674px;
 height: 80px;
 margin: 0 30px 0 46px;
}

.hot_name {
 display: inline-flex;
 height: 28px;
 line-height: 28px;
 font-size: 28px;
 color: #333;
}

.more_text {
 display: inline-flex;
 height: 24px;
 line-height: 24px;
 font-size: 24px;
 color: #999;
}

.more_icon {
 display: inline-flex;
 margin-left: 20px;
 width: 11px;
 height: 20px;
 background: url("../assets/images/icon/more.png") no-repeat;
 background-size: 100%;
}

.hot_centenrt {
 display: flex;
 flex-direction: row;
 width: 710px;
 height: 320px;
 margin: 0 20px 20px 20px;
}

.hot_centent_left {
 flex-direction: column;
 width: 350px;
 height: 320px;
 background: #f7f7f7;
}

.hot_left_name {
 display: inline-flex;
 width: 282px;
 height: 24px;
 margin: 50px 34px 0 34px;
 font-size: 24px;
 line-height: 24px;
 color: #333;
}

.hot_left_desc {
 display: inline-flex;
 width: 282px;
 height: 20px;
 margin: 12px 34px 0 34px;
 font-size: 20px;
 line-height: 20px;
 color: #999;
}

.hot_left_img {
 width: 220px;
 height: 142px;
 margin-left: 34px;
 margin-top: 34px;
}

.hot_centent_right {
 flex-direction: column;
 width: 350px;
 height: 320px;
 margin-left: 10px;
}

.hot_right_top {
 display: flex;
 flex-direction: row;
 width: 100%;
 height: 156px;
 background: #f7f7f7;
}

.hot_right_text_box {
 display: flex;
 flex-direction: column;
 width: 180px;
 height: 58px;
 margin: 49px 20px 0 20px;
}

.hot_right_name {
 display: inline-flex;
 width: 100%;
 height: 24px;
 line-height: 24px;
 font-size: 24px;
 color: #333;
}

.hot_right_desc {
 display: inline-flex;
 margin-top: 10px;
 width: 100%;
 height: 24px;
 line-height: 24px;
 font-size: 24px;
 color: #999;
}

.hot_right_img {
 width: 110px;
 height: 70px;
 margin-top: 43px;
}

.hot_right_bottom {
 display: flex;
 flex-wrap: wrap;
 width: 100%;
 height: 156px;
 margin-top: 8px;
 background: #f7f7f7;
}

.hot_right_text_box2 {
 display: flex;
 flex-direction: column;
 width: 180px;
 margin: 31px 20px 0 20px;
}

.hot_right_name2 {
 display: inline-flex;
 width: 100%;
 height: 58px;
 line-height: 30px;
 font-size: 24px;
 color: #333;
}

.hot_right_desc2 {
 display: inline-flex;
 margin-top: 12px;
 width: 100%;
 height: 24px;
 line-height: 24px;
 font-size: 24px;
 color: #999;
}

.card_state {
 display: flex;
 flex-direction: row;
 width: 100%;
 height: 128px;
 margin-top: 10px;
 background-color: #fff;
}
.card_progress {
 display: inline-flex;
 width: 327px;
 height: 88px;
 margin: 20px 0 20px 48px;
}
.progress_icon {
 width: 48px;
 height: 48px;
 margin: 20px 0;
 background: url("../assets/images/icon/search.png") no-repeat;
 background-size: 100%;
}
.activation_icon {
 width: 48px;
 height: 48px;
 margin: 20px 0;
 background: url("../assets/images/icon/activation.png") no-repeat;
 background-size: 100%;
}
.card_text {
 width: 228px;
 height: 66px;
 margin: 11px 20px 11px 30px;
}
.card_state_name {
 display: inline-flex;
 width: 100%;
 height: 28px;
 line-height: 28px;
 font-size: 28px;
 color: #333;
}
.card_desc {
 display: inline-flex;
 width: 100%;
 height: 22px;
 line-height: 22px;
 font-size: 22px;
 margin-top: 16px;
 color: #999;
}
.card_activation {
 display: inline-flex;
 width: 326px;
 height: 88px;
 margin: 20px 0 20px 48px;
}

.card_order {
 width: 100%;
 height: auto;
 margin-top: 10px;
 background-color: #fff;
}
.border_bottom {
 width: 100%;
 height: 80px;
}
.card_list {
 width: 100%;
 height: 228px;
 list-style-type: none;
}
.card_content {
 display: flex;
 flex-direction: row;
 width: 700px;
 height: 228px;
 margin-left: 50px;
}
.card_img {
 width: 186px;
 height: 120px;
 margin: 54px 0 54px 20px;
}
.card_list_text {
 flex-direction: column;
 width: 386px;
 height: 124px;
 margin: 52px 34px 52px 74px;
}
.card_name {
 width: 100%;
 height: 28px;
 line-height: 28px;
 font-size: 28px;
 color: #333;
}
.card_title {
 width: 100%;
 height: 24px;
 margin-top: 20px;
 line-height: 24px;
 font-size: 24px;
 color: #666;
}
.card_words_lists {
 display: flex;
 flex-direction: row;
}
.card_words {
 height: 36px;
 margin-top: 16px;
 background-color: #e8ca88;
}
.card_word {
 height: 20px;
 padding: 8px 18px;
 line-height: 20px;
 font-size: 20px;
 color: #4b4b4b;
}
.card_words_two {
 margin-left: 20px;
}
</style>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。