您的位置:

小程序瀑布流实现全指南

一、什么是小程序瀑布流?

瀑布流是一种非常常见的网页布局,在不同的屏幕尺寸下都有很好的适应性。简单来说,瀑布流就是将不同大小的元素按照一定的规则排列,形成一列一列的布局。而小程序瀑布流则是将瀑布流这种布局方式带入到小程序中,实现动态的渲染效果。

在小程序中实现瀑布流主要通过实现scroll-view的onReachBottom事件监听,动态地将元素渲染到页面上。

二、小程序瀑布流的基本原理

在小程序中实现瀑布流的基本原理是使用scroll-view组件,监听onReachBottom事件,对滚动到底部的页面进行数据渲染。

具体实现步骤如下:

Step 1:创建scroll-view组件,设置scroll-y属性,使页面可以纵向滚动。

Step 2:定义一个瀑布流布局的容器,在该容器中创建多个子元素,每个子元素代表一张图片。

Step 3:在onLoad事件中,请求数据,并渲染页面。同时定义一个空数组,用于存储已经渲染的图片信息。

Step 4:在onReachBottom事件中,判断是否还有数据,若有,则加载数据并渲染页面,并更新已经渲染的图片信息数组。

在实现上述步骤后,就可以实现小程序瀑布流的基本功能。

三、小程序瀑布流的实现细节

1、计算每一列的高度

在实现瀑布流布局时,需要计算每一列的高度,并将当前图片渲染到高度最短的那一列。

具体实现方法是定义一个数组,用来存储每一列的高度。当渲染一张图片时,遍历这个数组,找到高度最短的那一列,将当前图片渲染到该列,并更新该列的高度。

// 定义列数
const colNum = 2;
// 定义每列的宽度
const colWidth = wx.getSystemInfoSync().windowWidth / colNum;

// 定义每一列的高度
let colHeightArr = new Array(colNum).fill(0);

// 获取图片信息
const imgWidth = e.detail.width;
const imgHeight = e.detail.height;

// 计算图片的高度
const height = imgHeight / imgWidth * colWidth;

// 找到高度最短的那一列
const minColHeight = Math.min(...colHeightArr);
const minColIndex = colHeightArr.indexOf(minColHeight);

// 渲染图片
ctx.drawImage(imgPath, minColIndex * colWidth, minColHeight, colWidth, height);

// 更新高度数组
colHeightArr[minColIndex] += height;

2、防抖动

瀑布流布局需要在滚动到底部时加载数据并进行渲染,但是当用户快速滑动时,可能会同时触发多个onReachBottom事件,从而导致重复加载数据。

为了解决这个问题,可以使用防抖函数对onReachBottom事件进行处理,使得在短时间内多次触发事件时,只处理最后一次事件。

function debounce(fn, wait) {
  let timerId = null;
  return function() {
    if (timerId !== null) {
      clearTimeout(timerId);
    }
    timerId = setTimeout(fn, wait);
  };
}

Page({
  onReachBottom: debounce(function() {
    // 加载数据并渲染页面
  }, 300)
})

3、图片预加载

为了提高用户体验,当滚动到底部时,可以提前加载下一页的图片。

具体实现方法是在滚动到底部时,同时将下一页的图片进行预加载。当用户滑动到下一页时,该页的图片已经被预加载完毕,无需等待加载时间。

// 定义一个变量,用于记录当前已经预加载到第几页
let currentPage = 1;

Page({
  onReachBottom: function() {
    // 加载当前页数据并渲染页面
    // 同时预加载下一页数据
    currentPage++;
    loadNextPage(currentPage);
  }
})

function loadNextPage(pageNum) {
  // 请求下一页数据并预加载
}

四、实现代码示例

1、WXML代码

<scroll-view scroll-y="true" bindscrolltolower="onReachBottom">
  <view class="container">
    <block wx:for="{{imgList}}" wx:key="index">
      <view class="img-wrap">
        <image class="img" mode="aspectFill" 
          src="{{item.url}}" 
          bindload="onImgLoad" 
          data-img="{{item}}">
        </image>
      </view>
    </block>
  </view>
</scroll-view>

2、JS代码

Page({
  data: {
    imgList: [], // 图片列表
    screenWidth: 0 // 屏幕宽度
  },
  onLoad: function() {
    // 获取屏幕宽度
    const screenWidth = wx.getSystemInfoSync().windowWidth;
    this.setData({
      screenWidth: screenWidth
    });

    // 请求第一页数据并渲染页面
    const imgData = require('../../imgs/data.js');
    this.setData({
      imgList: imgData.data
    });
    this.renderImg(imgData.data);
  },
  onReachBottom: function() {
    // 加载下一页数据并渲染页面
    const imgData = require('../../imgs/data.js');
    this.setData({
      imgList: this.data.imgList.concat(imgData.data)
    });
    this.renderImg(imgData.data);
  },
  onImgLoad: function(e) {
    // 图片加载完成后渲染图片
    const imgData = e.currentTarget.dataset.img;
    const imgWidth = imgData.width;
    const imgHeight = imgData.height;
    const ratio = imgWidth / this.data.screenWidth;
    const height = imgHeight / ratio;
    const index = e.currentTarget.dataset.index;
    const ctx = wx.createCanvasContext('canvas-' + index, this);
    ctx.drawImage('../' + imgData.url, 0, 0, this.data.screenWidth, height);
    ctx.draw();
  },
  renderImg: function(imgList) {
    // 渲染图片
    const len = imgList.length;
    for (let i = 0; i < len; i++) {
      const imgPath = '../' + imgList[i].url;
      const imgWidth = imgList[i].width;
      const imgHeight = imgList[i].height;
      const ratio = imgWidth / this.data.screenWidth;
      const height = imgHeight / ratio;
      const index = this.data.imgList.indexOf(imgList[i]);
      const ctx = wx.createCanvasContext('canvas-' + index, this);
      ctx.drawImage(imgPath, 0, 0, this.data.screenWidth, height);
      ctx.draw();
    }
  }
})

3、CSS代码

.container {
  display: flex;
  flex-wrap: wrap;
}

.img-wrap {
  width: 50%;
  padding: 5rpx;
  box-sizing: border-box;
  display: flex;
  justify-content: center;
}

.img {
  width: 100%;
}