vue移动端下拉刷新、上拉加载
生活随笔
收集整理的這篇文章主要介紹了
vue移动端下拉刷新、上拉加载
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
由于自身的項目比較簡單,只有幾個H5頁面,用來嵌入app中,所有沒有引入移動端的UI框架,但是介于能讓用戶在瀏覽H5頁面時有下拉刷新和上拉加載,有更好的用戶體驗,自己寫組件實現。
1、下拉刷新DropDownRefresh.vue
<template lang="html"><div class="refresh-moudle" @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="dropDownInfo.downImg" class="down-img" :src="require('../../assets/images/refreshAndReload/'+dropDownInfo.downImg)"><span class="down-text">{{dropDownInfo.downText}}</span></div><div class="up-tip" v-if="dropDownState==2"><img v-if="dropDownInfo.upImg" class="up-img" :src="require('../../assets/images/refreshAndReload/'+dropDownInfo.upImg)"><span class="up-text">{{dropDownInfo.upText}}</span></div><div class="refresh-tip" v-if="dropDownState==3"><img v-if="dropDownInfo.refreshImg" class="refresh-img" :src="require('../../assets/images/loading/'+dropDownInfo.refreshImg)"><span class="refresh-text">{{dropDownInfo.refreshText}}</span></div></slot></header><slot></slot></div> </template> <script> export default {props: {onRefresh: {type: Function,required: false}},data () {return {defaultOffset: 50, // 默認高度, 相應的修改.releshMoudle的margin-top和.down-tip, .up-tip, .refresh-tip的heighttop: 0,scrollIsToTop: 0,startY: 0,isDropDown: false, // 是否下拉isRefreshing: false, // 是否正在刷新dropDownState: 1, // 顯示1:下拉可以刷新, 2:松開立即刷新, 3:正在刷新數據中...dropDownInfo: {downText: '下拉可以刷新',downImg: 'arrow.png',upText: '松開立即刷新',upImg: 'arrow.png',refreshText: '正在刷新數據...',refreshImg: 'loading.png'}}},created () {if (document.querySelector('.down-tip')) {// 獲取不同手機的物理像素(dpr),以便適配remthis.defaultOffset = document.querySelector('.down-tip').clientHeight || this.defaultOffset}},methods: {/*** 觸摸開始,手指點擊屏幕時* @param {object} e Touch 對象包含的屬性*/touchStart (e) {this.startY = e.targetTouches[0].pageY},/*** 接觸點改變,滑動時* @param {object} e Touch 對象包含的屬性*/touchMove (e) {this.scrollIsToTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop // safari 獲取scrollTop用window.pageYOffsetif (e.targetTouches[0].pageY > this.startY) {// 下拉this.isDropDown = trueif (this.scrollIsToTop === 0 && !this.isRefreshing) {// 拉動的距離let diff = e.targetTouches[0].pageY - this.startY - this.scrollIsToTopthis.top = Math.pow(diff, 0.8) + (this.dropDownState === 3 ? this.defaultOffset : 0)if (this.top >= this.defaultOffset) {this.dropDownState = 2e.preventDefault()} else {this.dropDownState = 1// 去掉會導致ios無法刷新e.preventDefault()}}} else {this.isDropDown = falsethis.dropDownState = 1}},/*** 觸摸結束,手指離開屏幕時* @param {object} e Touch 對象包含的屬性*/touchEnd (e) {if (this.isDropDown && !this.isRefreshing) {if (this.top >= this.defaultOffset) {// do refreshthis.refresh()this.isRefreshing = true} else {// cancel refreshthis.isRefreshing = falsethis.isDropDown = falsethis.dropDownState = 1this.top = 0}}},/*** 刷新*/refresh () {this.dropDownState = 3this.top = this.defaultOffset// 這是全是靜態數據,延時1200毫秒,給用戶一個刷新的感覺,如果是接口數據的話,直接調接口即可setTimeout(() => {this.onRefresh(this.refreshDone)}, 1200)},/*** 刷新完成*/refreshDone () {this.isRefreshing = falsethis.isDropDown = falsethis.dropDownState = 1this.top = 0}} } </script><!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .refresh-moudle {width: 100%;margin-top: -100px;-webkit-overflow-scrolling: touch; /* ios5+ */ }.pull-refresh {width: 100%;color: #999;transition-duration: 200ms;font-size: 24px; }.refresh-moudle .down-tip, .up-tip, .refresh-tip {display: flex;align-items: center;justify-content: center;height: 100px; }.down-img {width: 35px;height: 35px;margin-right: 15px;transform: rotate(0deg);animation: anticlockwise 0.8s ease; }@keyframes anticlockwise {0% {transform: rotate(-180deg);}100% {transform: rotate(0deg);} }.up-img {width: 35px;height: 35px;margin-right: 15px;transform: rotate(180deg);animation: clockwise 0.8s ease; }@keyframes clockwise {0% {transform: rotate(0deg);}100% {transform: rotate(-180deg);} }.refresh-img {width: 35px;height: 35px;margin-right: 15px;animation: rotating 1.5s linear infinite; }@keyframes rotating {0% {transform: rotate(0deg);}100% {transform: rotate(1turn);} } </style>2、上拉加載PullUpReload.vue
<template lang="html"><div class="load-moudle" @touchstart="touchStart($event)" @touchmove="touchMove($event)" @touchend="touchend($event)"><slot></slot><footer class="load-more"><slot name="load-more"><div class="more-tip" v-if="pullUpState==1"><span class="more-text">{{pullUpInfo.moreText}}</span></div><div class="loading-tip" v-if="pullUpState==2"><span class="loading-icon"></span><span class="loading-text">{{pullUpInfo.loadingText}}</span></div><div class="no-more-tip" v-if="pullUpState==3"><span class="connecting-line"></span><span class="no-more-text">{{pullUpInfo.noMoreText}}</span><span class="connecting-line"></span></div></slot></footer></div> </template><script> export default {props: {parentPullUpState: {default: 0},onInfiniteLoad: {type: Function,require: false}},data () {return {top: 0,pullUpState: 0, // 1:上拉加載更多, 2:加載中……, 3:我是有底線的isLoading: false, // 是否正在加載pullUpInfo: {moreText: '上拉加載更多',loadingText: '數據加載中...',noMoreText: '我是有底線的'},startX: 0,startY: 0,endX: 0,endY: 0}},methods: {/*** 觸摸開始,手指點擊屏幕時* @param {object} e Touch 對象包含的屬性*/touchStart (e) {this.startX = e.touches[0].pageXthis.startY = e.touches[0].pageY},/*** 接觸點改變,滑動時* @param {object} e Touch 對象包含的屬性*/touchMove (e) {this.endX = e.changedTouches[0].pageXthis.endY = e.changedTouches[0].pageYlet direction = this.getSlideDirection(this.startX, this.startY, this.endX, this.endY)switch (direction) {case 0:// console.log('沒滑動')breakcase 1:// console.log('向上')this.scrollToTheEnd()breakcase 2:// console.log('向下')breakcase 3:// console.log('向左')breakcase 4:// console.log('向右')breakdefault:}},/*** 觸摸結束,手指離開屏幕時* @param {object} e Touch 對象包含的屬性*/touchend (e) {this.isLoading = false},/*** 判斷滾動條是否到底*/scrollToTheEnd () {let innerHeight = document.querySelector('.load-moudle').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.infiniteLoad()}// console.log('距頂部' + scrollTop + '滾動條總高度' + scrollHeight + '內容高度' + innerHeight)}},/*** 上拉加載數據*/infiniteLoad () {if (this.pullUpState !== 0) {this.pullUpState = 2this.isLoading = truethis.onInfiniteLoad(this.infiniteLoadDone)}},/*** 加載數據完成*/infiniteLoadDone () {this.pullUpState = 1},/*** 返回角度*/getSlideAngle (dx, dy) {return Math.atan2(dy, dx) * 180 / Math.PI},/*** 根據起點和終點返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑動* @param {number} startX X軸開始位置* @param {number} startY X軸結束位置* @param {number} endX Y軸開始位置* @param {number} endY Y軸結束位置*/getSlideDirection (startX, startY, endX, endY) {let dy = startY - endYlet dx = endX - startXlet result = 0// 如果滑動距離太短if (Math.abs(dx) < 2 && Math.abs(dy) < 2) {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}},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: #fafafa;font-size: 24px; }.more-tip, .loading-tip, .no-more-tip {display: flex;align-items: center;justify-content: center;height: 150px; }.load-moudle .loading-icon {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);} }.connecting-line {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-box"><div class="bank-list" v-for="item in bankList" :key="item.id"><divclass="bank-icon":style="{ 'background': 'url(' + require('../../assets/images/bankIcon/'+item.iconName) + ') no-repeat', 'background-size': '100%' }"></div><span class="bank-name">{{item.bankName}}</span></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-left"><span class="left-name">{{hotLeft.name}}</span><span class="left-desc">{{hotLeft.desc}}</span><divclass="left-img":style="{ 'background': 'url(' + require('../../assets/images/bank/'+hotLeft.imgName) + ') no-repeat', 'background-size': '100%' }"></div></div><div class="hot-right"><div class="right-top"><div class="text-box"><span class="right-name">{{centenrtOne.name}}</span><span class="right-desc">{{centenrtOne.desc}}</span></div><divclass="right-img":style="{ 'background': 'url(' + require('../../assets/images/bank/'+centenrtOne.imgName) + ') no-repeat', 'background-size': '100%' }"></div></div><div class="hot-right-bottom"><div class="text-box2"><span class="right-name2">{{centenrtTwo.name}}</span><span class="right-desc2">{{centenrtTwo.desc}}</span></div><divclass="right-img":style="{ 'background': 'url(' + require('../../assets/images/bank/'+centenrtTwo.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-name">辦卡進度</span><span class="card-desc">讓等待隨處可見</span></div></div><div class="card-activation"><div class="activation-icon"></div><div class="card-text"><span class="card-name">辦卡激活</span><span class="card-desc">讓等待隨處可見</span></div></div></div><div class="card-order"><div class="border-bottom card-bottom"><div class="hot-header"><span class="hot-name">熱卡排行</span></div></div><div slot="load-more"><liclass="card-list"v-for="(item,index) in infiniteLoadData.pullUpList":key="item.id"><divclass="card-content":class="infiniteLoadData.pullUpList.length - 1 != index? 'card-bottom':''"><divclass="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="words-lists"><div class="card-words"><p class="card-word">{{item.cardWordOne}}</p></div><div v-if="item.cardWordTwo" class="card-words words-two"><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 {bankList: [{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: '全部銀行'}],hotLeft: {bankName: '交通銀行',name: '交行Y-POWER黑卡',desc: '額度100%取現',imgName: 'jiaohangY-POWER.png'},centenrtOne: {bankName: '招商銀行',name: '招行YOUNG卡',desc: '生日月雙倍積分',imgName: 'zhaohangYOUNG.png'},centenrtTwo: {bankName: '光大銀行',name: '光大淘票票公仔聯名卡',desc: '電影達人必備',imgName: 'guangdalianming.png'},cardList: [{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: 2, // 子組件的pullUpState狀態pullUpList: [], // 上拉加載更多數據的數組showPullUpListLength: this.initialShowNum // 上拉加載后所展示的個數}}},mounted () {this.getStartPullUpState()this.getPullUpDefData()},methods: {/*** 獲取上拉加載的初始數據*/getPullUpDefData () {this.infiniteLoadData.pullUpList = []if (this.cardList.length < this.infiniteLoadData.initialShowNum) {for (let i = 0; i < this.cardList.length; i++) {this.infiniteLoadData.pullUpList.push(this.cardList[i])}} else {for (let i = 0; i < this.infiniteLoadData.initialShowNum; i++) {this.infiniteLoadData.pullUpList.push(this.cardList[i])}}this.getStartPullUpState()},/*** 獲取上拉加載的pullUpState狀態*/getStartPullUpState () {if (this.infiniteLoadData.pullUpList.length) {if (this.cardList.length <= this.infiniteLoadData.initialShowNum) {// 修改子組件的pullUpState狀態this.infiniteLoadData.pullUpState = 3} else {this.infiniteLoadData.pullUpState = 1}} else {this.infiniteLoadData.pullUpState = 0}},/*** 上拉一次加載更多的數據*/getPullUpMoreData () {this.showPullUpListLength = this.infiniteLoadData.pullUpList.lengthif (this.infiniteLoadData.pullUpList.length + this.infiniteLoadData.everyLoadingNum > this.cardList.length) {for (let i = 0; i < this.cardList.length - this.showPullUpListLength; i++) {this.infiniteLoadData.pullUpList.push(this.cardList[i + this.showPullUpListLength])}} else {for (let i = 0; i < this.infiniteLoadData.everyLoadingNum; i++) {this.infiniteLoadData.pullUpList.push(this.cardList[i + this.showPullUpListLength])}}if (this.cardList.length === this.infiniteLoadData.pullUpList.length) {this.infiniteLoadData.pullUpState = 3} else {this.infiniteLoadData.pullUpState = 1}},/*** 下拉刷新*/onRefresh (done) {// 如果下拉刷新和上拉加載同時使用,下拉時初始化上拉的數據this.getStartPullUpState()this.getPullUpDefData()done() // call done},/*** 上拉加載*/onInfiniteLoad (done) {if (this.infiniteLoadData.pullUpState === 1) {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-box {display: flex;flex-wrap: wrap;padding: 2px 7px 42px 7px;background-color: #fff; }.bank-list {display: flex;flex-direction: column;justify-content: space-between;width: 100px;height: 98px;margin: 40px 42px 0 42px; }.bank-icon {width: 56px;height: 56px;margin: 0 22px 18px; }.bank-name {display: flex;align-items: center;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: flex;align-items: center;height: 28px;font-size: 28px;color: #333; }.more-text {display: flex;align-items: center;font-size: 24px;color: #999; }.more-box {display: flex;align-items: center; }.more-icon {margin-left: 20px;width: 11px;height: 20px;background: url("../../assets/images/bank/more.png") no-repeat;background-size: 100%; }.hot-centenrt {display: flex;align-items: center;width: 710px;height: 320px;margin: 0 20px 20px 20px; }.hot-left {display: flex;flex-direction: column;width: 350px;height: 320px;background: #f7f7f7; }.left-name {display: flex;align-items: center;width: 282px;height: 24px;margin: 50px 34px 0 34px;font-size: 24px;line-height: 24px;color: #333; }.left-desc {display: flex;align-items: center;width: 282px;height: 20px;margin: 12px 34px 0 34px;font-size: 20px;line-height: 20px;color: #999; }.left-img {width: 220px;height: 142px;margin-left: 34px;margin-top: 34px; }.hot-right {display: flex;flex-direction: column;width: 350px;height: 320px;margin-left: 10px; }.right-top {display: flex;flex-direction: row;width: 100%;height: 156px;background: #f7f7f7; }.text-box {display: flex;flex-direction: column;width: 180px;height: 58px;margin: 49px 20px 0 20px; }.right-name {display: flex;align-items: center;width: 100%;height: 24px;line-height: 24px;font-size: 24px;color: #333; }.right-desc {display: flex;align-items: center;margin-top: 10px;width: 100%;height: 24px;line-height: 24px;font-size: 24px;color: #999; }.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; }.text-box2 {display: flex;flex-direction: column;width: 180px;margin: 31px 20px 0 20px; }.right-name2 {display: flex;align-items: center;width: 100%;height: 58px;line-height: 30px;font-size: 24px;color: #333; }.right-desc2 {display: flex;align-items: center;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: flex;align-items: center;width: 327px;height: 88px;margin: 20px 0 20px 48px; } .progress-icon {width: 48px;height: 48px;margin: 20px 0;background: url("../../assets/images/bank/search.png") no-repeat;background-size: 100%; } .activation-icon {width: 48px;height: 48px;margin: 20px 0;background: url("../../assets/images/bank/activation.png") no-repeat;background-size: 100%; } .card-text {display: flex;flex-direction: column;align-items: center;width: 228px;height: 66px;margin: 11px 20px 11px 30px; } .card-name {display: flex;align-items: center;width: 100%;height: 28px;line-height: 28px;font-size: 28px;color: #333; } .card-desc {display: flex;align-items: center;width: 100%;height: 22px;line-height: 22px;font-size: 22px;margin-top: 16px;color: #999; } .card-activation {display: flex;align-items: center;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 {display: flex;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; } .words-lists {display: flex;flex-direction: row; } .card-words {height: 36px;margin-top: 16px;border-radius: 20px;background-color: #e8ca88; } .card-word {height: 20px;padding: 8px 18px;line-height: 20px;font-size: 20px;color: #4b4b4b; } .words-two {margin-left: 20px; } </style>這里只是展示了一下效果,使用的全是靜態數據,如果要用接口數據的話,this.getPullUpMoreData()方法直接換成接口數據的方法,并將判斷的邏輯移到接口方法里,當然根據實際情況,分頁呀啥的做做適當的修改。
總結
以上是生活随笔為你收集整理的vue移动端下拉刷新、上拉加载的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不负此生
- 下一篇: LeetCode 1344. 时钟指针的