1. 程式人生 > 程式設計 >vue實現瀑布流元件滑動載入更多

vue實現瀑布流元件滑動載入更多

建議先看vue瀑布流元件上拉載入更多再來食用本文,如果直接想看原始碼文末就是~

文末新增元件優化,之所以沒有刪優化前的程式碼是想讓以後自己還能看到走過的路。

上一篇講到在專案中使用上拉載入更多元件,但是由於實際專案開發中由於需求變更或者說在webview中上拉載入有些機型在上拉時候會把webview也一起上拉導致上拉載入不靈敏等問題,我們有時候也會換成滑動到底部自動載入的功能。

既然都是載入更多,很多程式碼思想勢必相似,主要區別在於上拉和滑動到底部這個操作上,所以,我們需要注意:

1、上拉載入是point指標touch觸控事件,現在因為是滑動載入,需要新增scroll事件去監聽然後執行相應回撥

2、上拉載入主要計算觸控滾動距離,滑動載入主要計算container底部和視窗上邊緣的距離

事件繫結改成:

mounted() {
 ···
 this.dom.addEventListener('scroll',this.scroll,false)
 ···
 },beforeDestroy() {
 ···
 this.dom.removeEventListener('scroll',

事件回撥改為:

 /**
 * 滾動鉤子
 */
 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 {
 /**
  * 總頁數(由服務端返回)
  * @type {number}
  */
 count: 0,/**
  * 是否正在拖拽中
  * @type {boolean}
  */
 dragging: false,/**
  * 已載入次數
  * @type {number}
  */
 times: 0,/**
  * 已開始記載
  * @type {boolean}
  */
 started: false,/**
  * 正在載入中
  * @type {boolean}
  */
 loading: false,dom: null,}
 },props: {
 /**
 * 初始化後自動開始載入資料
 */
 autoload: {
 type: Boolean,default: true,},/**
 * 離元件最近的可滾動父級元素(用於監聽事件及獲取滾動條位置)
 */
 container: {
 // Selector or Element
 default: () => (global),/**
 * Axios請求引數配置物件
 * {@link https://github.com/mzabriskie/axios#request-config}
 */
 options: {
 type: Object,default: null,/**
 * 起始頁碼
 */
 page: {
 type: Number,default: 1,/**
 * 每頁載入資料條數
 */
 rows: {
 type: Number,default: 10,/**
 * 資料載入請求地址
 */
 url: {
 type: String,default: '',/**
 * 距離底部多遠載入
 */
 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',false)
 if (this.autoload && !this.loading) {
 this.load()
 }
 },// eslint-disable-next-line
 beforeDestroy() {
 if (this.dom) {
 this.dom.removeEventListener('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()
  }
 }
 },/**
 * 載入一組資料的方法
 */
 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')
 })
 },/**
 * 重置載入相關變數
 */
 reset() {
 this.count = 0
 this.times = 0
 this.started = false
 this.loading = false
 },/**
 *重新開始載入
 */
 restart() {
 this.reset()
 this.load()
 },}
</script>

———————-我是分割線——————–

2017-09-18 元件優化

我們在寫元件時候,通常會大致先分為兩種,業務元件和通用元件,業務元件通和業務邏輯相關,一般作為一個業務模組的區域性元件, 比如列表中的列表項元件;通用元件適用面廣,不會和業務有牽扯,比如彈出框元件。

所以我們開始封裝一個元件的時候,就要劃分業務邏輯,做什麼,不做什麼,從外部接收什麼,向外部提供什麼,這個邊界應該非常清楚

但是之前的封裝的loadmore元件不太符合這一點,可能是專案一開始比較關注功能的實現,將其當成的一個業務元件撰寫,現在有一點需要優化:

之前我們傳入了各種請求相關的引數,包括url在元件內部完成載入和頁碼控制等一系列操作,顯然這不太符合元件功能職責單一化的原則, 其實元件內部並不關心載入到第幾頁或者是需要請求什麼後端介面,而只要父元件告訴自己是否還可以載入就可以了, 至於載入請求列表,子元件通知父元件去載入就OK。

最終我們得到一個和業務完全分離的通用元件,程式碼如下:

<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),data() {
 return {
 dom: null,// 外部容器dom
 }
 },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()
 }
 }
 },/**
 * 載入一組資料的方法
 */
 load() {
 this.$emit('load')
 },beforeDestroy() {
 if (this.dom) {
 this.dom.removeEventListener('scroll',}
</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>

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。