vue實(shí)現(xiàn)瀑布流組件滑動(dòng)加載更多
建議先看vue瀑布流組件上拉加載更多再來(lái)食用本文,如果直接想看源碼文末就是~
文末新增組件優(yōu)化,之所以沒(méi)有刪優(yōu)化前的代碼是想讓以后自己還能看到走過(guò)的路。
上一篇講到在項(xiàng)目中使用上拉加載更多組件,但是由于實(shí)際項(xiàng)目開(kāi)發(fā)中由于需求變更或者說(shuō)在webview中上拉加載有些機(jī)型在上拉時(shí)候會(huì)把webview也一起上拉導(dǎo)致上拉加載不靈敏等問(wèn)題,我們有時(shí)候也會(huì)換成滑動(dòng)到底部自動(dòng)加載的功能。
既然都是加載更多,很多代碼思想勢(shì)必相似,主要區(qū)別在于上拉和滑動(dòng)到底部這個(gè)操作上,所以,我們需要注意:
1、上拉加載是point指針touch觸摸事件,現(xiàn)在因?yàn)槭腔瑒?dòng)加載,需要添加scroll事件去監(jiān)聽(tīng)然后執(zhí)行相應(yīng)回調(diào)
2、上拉加載主要計(jì)算觸摸滾動(dòng)距離,滑動(dòng)加載主要計(jì)算container底部和視窗上邊緣的距離
事件綁定改成:
mounted() { ··· this.dom.addEventListener('scroll', this.scroll, false) ··· }, beforeDestroy() { ··· this.dom.removeEventListener('scroll', this.scroll, false) ··· },
事件回調(diào)改為:
/** * 滾動(dòng)鉤子 */ scroll() { const viewHeight = global.innerHeight let parentNode if (this.container !== global) { parentNode = this.$el } else { parentNode = this.$el.parentNode } if (parentNode) { // 獲取Vue實(shí)例使用的根 DOM 元素相對(duì)于視口的位置 const rect = parentNode.getBoundingClientRect() // this.distance 離底部多少距離開(kāi)始加載 // 如果此元素底邊距離視口頂部的距離小于視口高度加上distance之和,就加載下一頁(yè) 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>沒(méi)有更多了</span> </div> </div> </template> <script type="text/babel"> import axios from 'axios' const CancelToken = axios.CancelToken export default { data() { return { /** * 總頁(yè)數(shù)(由服務(wù)端返回) * @type {number} */ count: 0, /** * 是否正在拖拽中 * @type {boolean} */ dragging: false, /** * 已加載次數(shù) * @type {number} */ times: 0, /** * 已開(kāi)始記載 * @type {boolean} */ started: false, /** * 正在加載中 * @type {boolean} */ loading: false, dom: null, } }, props: { /** * 初始化后自動(dòng)開(kāi)始加載數(shù)據(jù) */ autoload: { type: Boolean, default: true, }, /** * 離組件最近的可滾動(dòng)父級(jí)元素(用于監(jiān)聽(tīng)事件及獲取滾動(dòng)條位置) */ container: { // Selector or Element default: () => (global), }, /** * Axios請(qǐng)求參數(shù)配置對(duì)象 * {@link https://github.com/mzabriskie/axios#request-config} */ options: { type: Object, default: null, }, /** * 起始頁(yè)碼 */ page: { type: Number, default: 1, }, /** * 每頁(yè)加載數(shù)據(jù)條數(shù) */ rows: { type: Number, default: 10, }, /** * 數(shù)據(jù)加載請(qǐng)求地址 */ 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: { /** * 滾動(dòng)鉤子 */ 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 }, /** *重新開(kāi)始加載 */ restart() { this.reset() this.load() }, }, } </script>
———————-我是分割線——————–
2017-09-18 組件優(yōu)化
我們?cè)趯?xiě)組件時(shí)候,通常會(huì)大致先分為兩種,業(yè)務(wù)組件和通用組件,業(yè)務(wù)組件通和業(yè)務(wù)邏輯相關(guān),一般作為一個(gè)業(yè)務(wù)模塊的局部組件, 比如列表中的列表項(xiàng)組件;通用組件適用面廣,不會(huì)和業(yè)務(wù)有牽扯,比如彈出框組件。
所以我們開(kāi)始封裝一個(gè)組件的時(shí)候,就要?jiǎng)澐謽I(yè)務(wù)邏輯,做什么,不做什么,從外部接收什么,向外部提供什么,這個(gè)邊界應(yīng)該非常清楚
但是之前的封裝的loadmore組件不太符合這一點(diǎn),可能是項(xiàng)目一開(kāi)始比較關(guān)注功能的實(shí)現(xiàn),將其當(dāng)成的一個(gè)業(yè)務(wù)組件撰寫(xiě),現(xiàn)在有一點(diǎn)需要優(yōu)化:
之前我們傳入了各種請(qǐng)求相關(guān)的參數(shù),包括url在組件內(nèi)部完成加載和頁(yè)碼控制等一系列操作,顯然這不太符合組件功能職責(zé)單一化的原則, 其實(shí)組件內(nèi)部并不關(guān)心加載到第幾頁(yè)或者是需要請(qǐng)求什么后端接口,而只要父組件告訴自己是否還可以加載就可以了, 至于加載請(qǐng)求列表,子組件通知父組件去加載就OK。
最終我們得到一個(gè)和業(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>沒(méi)有更多了</span> </div> </div> </template> <script> export default { props: { /** * 是否加載 */ loading: { type: Boolean, default: false, }, /** * 滾動(dòng)外部容器, 選擇器字符串 */ 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: { /** * 滾動(dòng)鉤子 */ 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>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue3.2自定義彈窗組件結(jié)合函數(shù)式調(diào)用示例詳解
這篇文章主要為大家介紹了vue3.2自定義彈窗組件結(jié)合函數(shù)式調(diào)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Vue使用自定義指令實(shí)現(xiàn)頁(yè)面底部加水印
本文主要實(shí)現(xiàn)給項(xiàng)目的整個(gè)背景加上自定義水印,可以改變水印的文案和字體顏色等,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06element step組件在另一側(cè)加時(shí)間軸顯示
本文主要介紹了element step組件在另一側(cè)加時(shí)間軸顯示,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06vue?contextmenujs鼠標(biāo)右鍵菜單高度不夠顯示不全的問(wèn)題及解決方法
這篇文章主要介紹了使用vue-contextmenujs鼠標(biāo)右鍵菜單時(shí),當(dāng)高度不夠時(shí)顯示不全的問(wèn)題,大家需要注意本文給提供的解決方案雖然能夠解決現(xiàn)有問(wèn)題,但是如果組件升級(jí)了,想要使用最新升級(jí)后的組件,還要再次修改代碼,需要的朋友可以參考下2022-07-07vue報(bào)錯(cuò)Cannot?read?properties?of?undefined?(...)類(lèi)型的解決辦法
這篇文章主要給大家介紹了關(guān)于vue報(bào)錯(cuò)Cannot?read?properties?of?undefined?(...)類(lèi)型的解決辦法,文中通過(guò)代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-04-04ElementUI修改實(shí)現(xiàn)更好用圖片上傳預(yù)覽組件
這篇文章主要為大家介紹了ElementUI修改實(shí)現(xiàn)更好用圖片上傳預(yù)覽組件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09vue項(xiàng)目配置eslint保存自動(dòng)格式化問(wèn)題
這篇文章主要介紹了vue項(xiàng)目配置eslint保存自動(dòng)格式化問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09