vue移动端下拉刷新和上拉加载的实现代码
程序员文章站
2023-11-23 21:00:46
由于自身的项目比较简单,只有几个h5页面,用来嵌入app中,所有没有引入移动端的ui框架,但是介于能让用户在浏览h5页面时有下拉刷新和上拉加载,有更好的用户体验,自己写组件...
由于自身的项目比较简单,只有几个h5页面,用来嵌入app中,所有没有引入移动端的ui框架,但是介于能让用户在浏览h5页面时有下拉刷新和上拉加载,有更好的用户体验,自己写组件实现。
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>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。