小程序篇(3):瀑布流

瀑布流是一種很常見的網頁佈局,視覺表現爲良莠不齊的多欄佈局,是一種時下很流行的佈局形式,最近在寫小程序剛好也碰到了,想了幾種不一樣的實現方法,接下來就來一塊兒看看具體的實現方法(所用的方法中用的例子都是兩欄的佈局)。css

等高的瀑布流

等高的瀑布流顧名思義就是瀑布流裏的單個盒子的高度都是同樣的,這種形式的瀑布流實現起來也比較簡單,由於不涉及到盒子高度的計算,舉個例子:html

<view class="fall">
  <view wx:for="{{list}}" class="fall-item"></view>
</view>
Page({
  data: {
    list: []
  },
  onLoad () {
    let images = []
    for (let i = 0; i < 10; i++) {
      images.push({
        url: 'test'
      })
    }
    this.setData({
      list: images
    })
  }
})
.fall {
  display: flex;
  flex-wrap: wrap;
  background-color: #f7f7f7;
}
.fall-item {
  width: 330rpx;
  height: 330rpx;
  margin-top: 30rpx;
  margin-left: 30rpx;
  background-color: aquamarine;
}

爲了方便,例子中的盒子內容並無使用圖片,而是使用了色塊代替,等高瀑布流的實現能夠直接經過flex佈局實現,如例子所示,直接用flex佈局,容許換行,設置好瀑布流裏中每個盒子的寬高,就能實現簡單的實現兩欄瀑布流佈局前端

不等高瀑布流

不等高瀑布流是更爲常見的形式,不等高瀑布流涉及到列高的計算,因爲每一個盒子的高度不同,所以須要每一列的列高都要記錄、比較,將下一個盒子插入高度矮的一列,接下來就來看看不等高瀑布流的實現方式小程序

已知盒子高度

通常瀑布流裏展現的都是圖片,這種狀況指的是服務端會返給前端要展現的圖片的寬高,這種狀況下相對也比較簡單,由於服務端會返回圖片的寬高,前端只須要計算一下列高,將下一張圖片插入矮的那裏一列就能夠,舉個例子:api

<view class="fall">
  <view wx:for="{{list}}" wx:for-index="idx" wx:for-item="column" class="fall-column">
    <view class="fall-column-item" wx:for="{{column}}" wx:for-index="i" wx:for-item="item" style="height: {{item.showHeight}}rpx"></view>
  </view>
</view>
.fall {
  display: flex;
  background-color: #f7f7f7;
}

.fall-column {
  display: flex;
  flex-direction: column;
  margin-left: 30rpx;
}

.fall-column-item {
  width: 330rpx;
  margin-top: 30rpx;
  background-color: aquamarine;
}
Page({
  data: {
    images: [{
      width: 360,
      height: 540
    }, {
      width: 480,
      height: 540
    }, {
      width: 540,
      height: 720
    }, {
      width: 720,
      height: 960
    }, {
      width: 540,
      height: 960
    }, {
      width: 360,
      height: 720
    }, {
      width: 360,
      height: 960
    }, {
      width: 540,
      height: 540
    }, {
      width: 540,
      height: 1440
    }, {
      width: 960,
      height: 1440
    }],
    heightArr: [],
    list: [],
    col: 2
  },
  onLoad () {
    this.initData(2)
  },
  initData (col) {
    let images = []
    let scale = 2
    // 模擬圖片寬高
    for (let i = 0; i < 10; i++) {
      let image = this.data.images[Math.floor(Math.random() * 10)]
      images.push(image)
    }
    for (let i in images) {
      let height = 165 / images[i].width * images[i].height * scale
      images[i].showHeight = height
      // 第一行的兩個盒子
      if (i < col) {
        this.data.list.push([images[i]])
        this.data.heightArr.push(height)
      } else {
        // 選出高度較矮的一列的索引
        let minHeight = Math.min.apply(null, this.data.heightArr)
        let minHeightIndex = this.data.heightArr.indexOf(minHeight)
        this.data.list[minHeightIndex].push(images[i])
        this.data.heightArr[minHeightIndex] += height
      }
    }
    this.setData({
      list: this.data.list
    })
  },
  onReachBottom () {
    this.initData(0)
  }
})

上例中爲了方便也是用色塊模擬了圖片,在js中模擬了10張圖片的寬高,每次從中隨機取10張圖片,定義了兩列,每次計算一下每列的高度,將圖片插入矮的那一列,而後將記錄用高度數組,將圖片的高度累加,實現起來也很簡單數組

未知盒子高度

未知盒子高度的狀況下,咱們要怎麼作呢?服務器

wx.getImageInfo

第一種辦法就是經過wx.getImageInfo能夠獲取到圖片寬高信息,舉個例子:app

<view class="fall">
  <view class="fall-column" wx:for="{{list}}" wx:for-index="idx" wx:for-item="column" wx:key="{{idx}}">
    <view class="fall-column-item" wx:for="{{column}}" wx:for-index="i" wx:key="{{i}}" wx:for-item="item">
      <image class="fall-column-item-img" src="{{item.cover}}" mode="widthFix"/>
    </view>
  </view>
</view>
.fall {
  display: flex;
  background-color: #f7f7f7;
}

.fall-column {
  display: flex;
  flex-direction: column;
  margin-left: 30rpx;
}

.fall-column-item {
  margin-top: 30rpx;
  line-height: 0;
}

.fall-column-item-img {
  width: 330rpx;
}
import api from '../../api/index'
Page({
  data: {
    list: [],
    heightArr: []
  },
  async onLoad () {
    let {results} = await api.fetchImages()
    let col = 2
    for (let i in results) {
      results[i].cover = results[i].imageUrl
      // 獲取圖片信息
      let info = await this.loadImage(results[i].cover)
      results[i].height = 165 / info.width * info.height
      if (i < col) {
        this.data.list.push([results[i]])
        this.data.heightArr.push(results[i].height)
      } else {
        let minHeight = Math.min.apply(null, this.data.heightArr)
        let minHeightIndex = this.data.heightArr.indexOf(minHeight)
        this.data.list[minHeightIndex].push(results[i])
        this.data.heightArr[minHeightIndex] += results[i].height
      }
    }
    this.setData({
      list: this.data.list
    })
  },
  loadImage (cover) {
    return new Promise(resolve => {
      wx.getImageInfo({
        src: cover,
        success: (res) => {
          resolve(res)
        }
      })
    })
  }
})

當服務端沒有返回圖片的寬高時,能夠直接經過wx.getImageInfo()獲取到圖片的信息,這裏爲了避免打亂服務返回時的圖片順序,特地將這個單獨用Promise封了一層,就是爲了圖片加載完一張再獲取下一張,可是當圖片比較大的時候就會致使加載的時間會很長,會有長時間的白屏:
16e747329f258970.png
這是由於wx.getImageInfo()獲取圖片信息的時候會先將圖片下載下來,而後才能獲取圖片信息,這就致使時間會比較長,可是若是不須要圖片加載順序時能夠考慮直接並行加載,不等上一張圖片加載完就加載下一張,這樣就能更快的展示dom

wx.getImageInfo優化

既然圖片加載獲取信息時間比較長,那考慮是否能夠加上一個默認的圖片,這樣用戶能在第一時間看到有內容展現,圖片信息拿到後再將圖片顯示出來,舉個例子:async

<view class="fall">
  <view class="fall-column" wx:for="{{list}}" wx:for-index="idx" wx:for-item="column" wx:key="{{idx}}">
    <view class="fall-column-item" wx:for="{{column}}" wx:for-index="i" wx:for-item="item" wx:key="{{i}}">
      <image class="fall-column-item-img" src="{{item.cover}}" mode="widthFix"/>
    </view>
  </view>
</view>
.fall {
  display: flex;
  background-color: #f7f7f7;
}

.fall-column {
  display: flex;
  flex-direction: column;
  margin-left: 30rpx;
}

.fall-column-item {
  position: relative;
  margin-top: 30rpx;
  line-height: 0;
  background-color: #ccc;
}

.fall-column-item::after {
  content: '加載中';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: inline-block;
  color: #666;
}

.fall-column-item-img {
  position: relative;
  width: 330rpx;
  z-index: 1;
}
import api from '../../api/index'
Page({
  data: {
    list: [],
    heightArr: []
  },
  async onLoad () {
    let {results} = await api.fetchImages()
    let col = 2
    for (let i = 0; i < col; i++) {
      this.data.list[i] = new Array(results.length / 2)
    }
    this.setData({
      list: this.data.list
    })
    for (let i in results) {
      results[i].cover = results[i].imageUrl
      let info = await this.loadImage(results[i].cover)
      results[i].height = 165 / info.width * info.height
      if (i < col) {
        this.data.list[i][0] = results[i]
        this.data.heightArr.push(results[i].height)
      } else {
        let minHeight = Math.min.apply(null, this.data.heightArr)
        let minHeightIndex = this.data.heightArr.indexOf(minHeight)
        let index = this.data.list[minHeightIndex].filter(Boolean).length
        this.data.list[minHeightIndex][index] = results[i]
        this.data.heightArr[minHeightIndex] += results[i].height
      }
    }
    for (let i = 0; i < col; i++) {
      this.data.list[i] = this.data.list[i].filter(Boolean)
    }
    this.setData({
      list: this.data.list
    })
  },
  loadImage (cover) {
    return new Promise(resolve => {
      wx.getImageInfo({
        src: cover,
        success: (res) => {
          resolve(res)
        }
      })
    })
  }
})

這個例子中就在圖片沒有加載完以前給了一個默認的加載中的顯示,固然這只是一個簡單的例子,只能提供簡單的優化思路,實際中的加載過渡動畫必定會設計得更細膩

雲存儲獲取用戶信息

通常小程序中用到的圖片都是存儲在雲服務器上的,且雲服務器通常都會提供在圖片請求地址上帶參數獲取圖片信息,以阿里云爲例,能夠在圖片連接上拼接?x-oss-process=image/info,就能獲取到圖片信息,舉個例子:

<view class="fall">
  <view class="fall-column" wx:for="{{list}}" wx:for-index="idx" wx:for-item="column" wx:key="{{idx}}">
    <view class="fall-column-item" wx:for="{{column}}" wx:for-index="i" wx:for-item="item" wx:key="{{i}}">
      <image class="fall-column-item-img" src="{{item.cover}}" mode="widthFix"/>
    </view>
  </view>
</view>
.fall {
  display: flex;
  background-color: #f7f7f7;
}

.fall-column {
  display: flex;
  flex-direction: column;
  margin-left: 30rpx;
}

.fall-column-item {
  position: relative;
  margin-top: 30rpx;
  line-height: 0;
  background-color: #ccc;
}

.fall-column-item::after {
  content: '加載中';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: inline-block;
  color: #666;
}

.fall-column-item-img {
  position: relative;
  width: 330rpx;
  z-index: 1;
}
let fetchPicInfo = async (url) => {
  let [err, result] = await to(testFly.get(`${url}?x-oss-process=image/info`))
  if (err) throw err
  return result.data
}
import api from '../../api/index'
Page({
  data: {
    list: [],
    heightArr: []
  },
  async onLoad () {
    let {results} = await api.fetchImages()
    let col = 2
    for (let i = 0; i < col; i++) {
      this.data.list[i] = new Array(results.length / 2)
    }
    this.setData({
      list: this.data.list
    })
    for (let i in results) {
      results[i].cover = results[i].imageUrl
      let info = await api.fetchPicInfo(results[i].cover)
      results[i].height = 165 / info.ImageWidth.value * info.ImageHeight.value
      if (i < col) {
        this.data.list[i][0] = results[i]
        this.data.heightArr.push(results[i].height)
      } else {
        let minHeight = Math.min.apply(null, this.data.heightArr)
        let minHeightIndex = this.data.heightArr.indexOf(minHeight)
        let index = this.data.list[minHeightIndex].filter(Boolean).length
        this.data.list[minHeightIndex][index] = results[i]
        this.data.heightArr[minHeightIndex] += results[i].height
      }
    }
    for (let i = 0; i < col; i++) {
      this.data.list[i] = this.data.list[i].filter(Boolean)
    }
    this.setData({
      list: this.data.list
    })
  }
})

經過這個方法能夠大大減小圖片加載的時間,不須要將圖片下載到本地在獲取圖片信息,而是直接向服務器請求圖片信息,再加上每次請求只會返回圖片基本信息就幾個字段,所以請求時間也很是短,如圖:
16e7490887fea6b7.png
這樣用戶能更快看到圖片顯示,同時也加上了圖片加載時的過渡效果,這樣體驗效果會更好

總結

這篇文章把最近在寫小程序時遇到的瀑布流作了一個比較詳細的總結,不一樣的狀況下選擇不一樣的加載方案,體驗效果最好的固然仍是服務端直接返回圖片信息,這樣能節省不少獲取圖片信息要花的時間,這樣用戶體驗更優,但願能對各位在寫小程序瀑布流能有所幫助。若是有錯誤或不嚴謹的地方,歡迎批評指正,若是喜歡,歡迎點贊

相關文章
相關標籤/搜索