vue實現(xiàn)瀑布流組件滑動加載更多
建議先看vue瀑布流組件上拉加載更多再來食用本文,如果直接想看源碼文末就是~
文末新增組件優(yōu)化,之所以沒有刪優(yōu)化前的代碼是想讓以后自己還能看到走過的路。
上一篇講到在項目中使用上拉加載更多組件,但是由于實際項目開發(fā)中由于需求變更或者說在webview中上拉加載有些機(jī)型在上拉時候會把webview也一起上拉導(dǎo)致上拉加載不靈敏等問題,我們有時候也會換成滑動到底部自動加載的功能。
既然都是加載更多,很多代碼思想勢必相似,主要區(qū)別在于上拉和滑動到底部這個操作上,所以,我們需要注意:
1、上拉加載是point指針touch觸摸事件,現(xiàn)在因為是滑動加載,需要添加scroll事件去監(jiān)聽然后執(zhí)行相應(yīng)回調(diào)
2、上拉加載主要計算觸摸滾動距離,滑動加載主要計算container底部和視窗上邊緣的距離
事件綁定改成:
mounted() {
···
this.dom.addEventListener('scroll', this.scroll, false)
···
},
beforeDestroy() {
···
this.dom.removeEventListener('scroll', this.scroll, false)
···
},
事件回調(diào)改為:
/**
* 滾動鉤子
*/
scroll() {
const viewHeight = global.innerHeight
let parentNode
if (this.container !== global) {
parentNode = this.$el
} else {
parentNode = this.$el.parentNode
}
if (parentNode) {
// 獲取Vue實例使用的根 DOM 元素相對于視口的位置
const rect = parentNode.getBoundingClientRect()
// this.distance 離底部多少距離開始加載
// 如果此元素底邊距離視口頂部的距離小于視口高度加上distance之和,就加載下一頁
if ((rect.bottom <= viewHeight + this.distance) && this.loadable && !this.loading) {
this.load()
}
}
},
源碼如下:
<template>
<div class="loadmore" ref="loadmore">
<div class="loadmore__body">
<slot></slot>
</div>
<div class="loadmore__footer">
<span v-if="loading">
<i class="tc-loading"></i>
<span>正在加載</span>
</span>
<span v-else-if="loadable">加載更多</span>
<span v-else>沒有更多了</span>
</div>
</div>
</template>
<script type="text/babel">
import axios from 'axios'
const CancelToken = axios.CancelToken
export default {
data() {
return {
/**
* 總頁數(shù)(由服務(wù)端返回)
* @type {number}
*/
count: 0,
/**
* 是否正在拖拽中
* @type {boolean}
*/
dragging: false,
/**
* 已加載次數(shù)
* @type {number}
*/
times: 0,
/**
* 已開始記載
* @type {boolean}
*/
started: false,
/**
* 正在加載中
* @type {boolean}
*/
loading: false,
dom: null,
}
},
props: {
/**
* 初始化后自動開始加載數(shù)據(jù)
*/
autoload: {
type: Boolean,
default: true,
},
/**
* 離組件最近的可滾動父級元素(用于監(jiān)聽事件及獲取滾動條位置)
*/
container: {
// Selector or Element
default: () => (global),
},
/**
* Axios請求參數(shù)配置對象
* {@link https://github.com/mzabriskie/axios#request-config}
*/
options: {
type: Object,
default: null,
},
/**
* 起始頁碼
*/
page: {
type: Number,
default: 1,
},
/**
* 每頁加載數(shù)據(jù)條數(shù)
*/
rows: {
type: Number,
default: 10,
},
/**
* 數(shù)據(jù)加載請求地址
*/
url: {
type: String,
default: '',
},
/**
* 距離底部多遠(yuǎn)加載
*/
distance: {
type: Number,
default: 200,
},
},
computed: {
/**
* 是否可以加載
* @returns {boolean} 是與否
*/
loadable() {
return !this.started || (this.page + this.times) <= this.count
},
},
mounted() {
if (this.container !== global) {
this.dom = document.querySelector(this.container)
} else {
this.dom = this.container
}
if (!this.dom) {
return
}
this.dom.addEventListener('scroll', this.scroll, false)
if (this.autoload && !this.loading) {
this.load()
}
},
// eslint-disable-next-line
beforeDestroy() {
if (this.dom) {
this.dom.removeEventListener('scroll', this.scroll, false)
}
},
methods: {
/**
* 滾動鉤子
*/
scroll() {
const viewHeight = global.innerHeight
let parentNode
if (this.container !== global) {
parentNode = this.$el
} else {
parentNode = this.$el.parentNode
}
if (parentNode) {
const rect = parentNode.getBoundingClientRect()
if ((rect.bottom <= viewHeight + this.distance) && this.loadable && !this.loading) {
this.load()
}
}
},
/**
* 加載一組數(shù)據(jù)的方法
*/
load() {
if (this.loading) {
return
}
this.started = true
this.loading = true
const params = {
currentPage: this.page + this.times,
pageSize: this.rows,
}
const options = Object.assign({}, this.options, {
url: this.url,
cancelToken: new CancelToken((cancel) => {
this.cancel = cancel
}),
})
if (String(options.method).toUpperCase() === 'POST') {
options.data = Object.assign({}, options.data, params)
} else {
options.params = Object.assign({}, options.params, params)
}
this.$axios.request(options).then((res) => {
const data = res.result
this.times += 1
this.loading = false
this.count = data.pageCount
this.$emit('success', data.list)
this.$emit('complete')
}).catch((e) => {
this.loading = false
this.$emit('error', e)
this.$emit('complete')
})
},
/**
* 重置加載相關(guān)變量
*/
reset() {
this.count = 0
this.times = 0
this.started = false
this.loading = false
},
/**
*重新開始加載
*/
restart() {
this.reset()
this.load()
},
},
}
</script>
———————-我是分割線——————–
2017-09-18 組件優(yōu)化
我們在寫組件時候,通常會大致先分為兩種,業(yè)務(wù)組件和通用組件,業(yè)務(wù)組件通和業(yè)務(wù)邏輯相關(guān),一般作為一個業(yè)務(wù)模塊的局部組件, 比如列表中的列表項組件;通用組件適用面廣,不會和業(yè)務(wù)有牽扯,比如彈出框組件。
所以我們開始封裝一個組件的時候,就要劃分業(yè)務(wù)邏輯,做什么,不做什么,從外部接收什么,向外部提供什么,這個邊界應(yīng)該非常清楚
但是之前的封裝的loadmore組件不太符合這一點,可能是項目一開始比較關(guān)注功能的實現(xiàn),將其當(dāng)成的一個業(yè)務(wù)組件撰寫,現(xiàn)在有一點需要優(yōu)化:
之前我們傳入了各種請求相關(guān)的參數(shù),包括url在組件內(nèi)部完成加載和頁碼控制等一系列操作,顯然這不太符合組件功能職責(zé)單一化的原則, 其實組件內(nèi)部并不關(guān)心加載到第幾頁或者是需要請求什么后端接口,而只要父組件告訴自己是否還可以加載就可以了, 至于加載請求列表,子組件通知父組件去加載就OK。
最終我們得到一個和業(yè)務(wù)完全分離的通用組件,代碼如下:
<template>
<div class="loadmore" ref="loadmore">
<div class="loadmore__body">
<slot></slot>
</div>
<div class="loadmore__footer">
<span v-if="loading">
<i class="tc-loading"></i>
<span>正在加載</span>
</span>
<span v-else-if="loading">正在加載...</span>
<span v-else>沒有更多了</span>
</div>
</div>
</template>
<script>
export default {
props: {
/**
* 是否加載
*/
loading: {
type: Boolean,
default: false,
},
/**
* 滾動外部容器, 選擇器字符串
*/
container: {
default: () => (global),
},
/**
* 距離底部多遠(yuǎn)加載
*/
distance: {
type: Number,
default: 200,
},
},
data() {
return {
dom: null, // 外部容器dom
}
},
mounted() {
if (this.container !== global) {
this.dom = document.querySelector(this.container)
} else {
this.dom = this.container
}
if (!this.dom) {
return
}
this.dom.addEventListener('scroll', this.scroll, false)
if (this.autoload && !this.loading) {
this.load()
}
},
methods: {
/**
* 滾動鉤子
*/
scroll() {
if (!this.loading) {
return
}
const viewHeight = global.innerHeight
let parentNode
if (this.container !== global) {
parentNode = this.$el
} else {
parentNode = this.$el.parentNode
}
if (parentNode) {
const rect = parentNode.getBoundingClientRect()
if ((rect.bottom <= viewHeight + this.distance)) {
this.load()
}
}
},
/**
* 加載一組數(shù)據(jù)的方法
*/
load() {
this.$emit('load')
},
},
beforeDestroy() {
if (this.dom) {
this.dom.removeEventListener('scroll', this.scroll, false)
}
},
}
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
.loadmore {
user-select: none;
&__footer {
color: #e4e4e4;
padding: 20px;
text-align: center;
}
.tc-loading {
~ span {
vertical-align: middle;
}
}
}
</style>
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue3.2自定義彈窗組件結(jié)合函數(shù)式調(diào)用示例詳解
這篇文章主要為大家介紹了vue3.2自定義彈窗組件結(jié)合函數(shù)式調(diào)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
vue?contextmenujs鼠標(biāo)右鍵菜單高度不夠顯示不全的問題及解決方法
這篇文章主要介紹了使用vue-contextmenujs鼠標(biāo)右鍵菜單時,當(dāng)高度不夠時顯示不全的問題,大家需要注意本文給提供的解決方案雖然能夠解決現(xiàn)有問題,但是如果組件升級了,想要使用最新升級后的組件,還要再次修改代碼,需要的朋友可以參考下2022-07-07
vue報錯Cannot?read?properties?of?undefined?(...)類型的解決辦法
這篇文章主要給大家介紹了關(guān)于vue報錯Cannot?read?properties?of?undefined?(...)類型的解決辦法,文中通過代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-04-04
ElementUI修改實現(xiàn)更好用圖片上傳預(yù)覽組件
這篇文章主要為大家介紹了ElementUI修改實現(xiàn)更好用圖片上傳預(yù)覽組件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09

