一、什么是小程序瀑布流?
瀑布流是一种非常常见的网页布局,在不同的屏幕尺寸下都有很好的适应性。简单来说,瀑布流就是将不同大小的元素按照一定的规则排列,形成一列一列的布局。而小程序瀑布流则是将瀑布流这种布局方式带入到小程序中,实现动态的渲染效果。
在小程序中实现瀑布流主要通过实现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%;
}