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

由于自身的项目比较简单,只有几个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>

以上是 vue移动端下拉刷新和上拉加载的实现代码 的全部内容, 来源链接: utcz.com/z/352043.html

回到顶部