JS瀑布流的实现
实现思路:
1.首先根据视口的宽度除以元素宽度加间距的宽度,向下取整,获取一行所占列数
2.获取所有子元素数据,循环遍历,当循环索引小于子元素索引,说明处于第一行,否则位于后几行
3.第一行设置绝对定位,设置上、左偏移量,上偏移量0,左偏移量根据索引和元素宽度进行设置,并将元素的offsetHeight存入元素列高度的数组
4.其余行,将后续元素依次放到列高度最小的位置。获取到元素列高度数组中最小的索引,根据列高度和列偏移量分别设置当前元素的上、左偏移量,将当前列高度更新
5.当视口宽度改变时,重新设置列,排列元素位置
6.当容器底部的位置小于或等于视口高度加上滚动距离,说明触底,加载更多的数据
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>瀑布流示例</title>
<style>
* {
margin: 0;
padding: 0;
position: relative;
}
img {
display: block;
width: 250px;
}
.item {
box-shadow: 2px 2px 2px #999;
position: absolute;
}
</style>
<script>
window.onload = function () {
var box = document.getElementById('box');
var items = box.children;
var gap = 10;
var img_width = 250;
function waterfall() {
var pageWidth = document.documentElement.clientWidth || document.body.clientWidth;
// 获取列数
var column = Math.floor(pageWidth / (img_width + gap));
var arr = [];
for (var i = 0; i < items.length; i++) {
if (i < column) {
// 第一行执行
items[i].style.top = 0;
items[i].style.left = (img_width + gap) * i + 'px';
arr.push(items[i].offsetHeight);
} else {
// 其余行执行
var minHeight = arr[0];
var index = 0;
for (var j = 0; j < arr.length; j++) {
if (minHeight > arr[j]) {
minHeight = arr[j];
index = j;
}
}
items[i].style.top = arr[index] + gap + 'px';
items[i].style.left = items[index].offsetLeft + 'px';
arr[index] = arr[index] + items[i].offsetHeight + gap;
}
}
var boxBottom = box.getBoundingClientRect().bottom;
// 触底加载数据
if (boxBottom <= document.documentElement.clientHeight + window.pageYOffset) {
for (var k = 0; k < 5; k++) {
var newItem = document.createElement('div');
newItem.className = 'item';
var newImg = document.createElement('img');
newImg.src = `img/${items.length + k + 1}.jpg`;
newItem.appendChild(newImg);
box.appendChild(newItem);
}
waterfall();
}
}
waterfall();
// 窗口尺寸变化执行重新绘制
window.onresize = function () {
waterfall();
};
// 滚动事件监听
window.addEventListener('scroll', function () {
waterfall();
});
};
</script>
</head>
<body>
<div class="container" id="box">
<div class="item"><img src="img/1.jpg" /></div>
<div class="item"><img src="img/2.jpg" /></div>
<div class="item"><img src="img/3.jpg" /></div>
</div>
</body>
</html>
WaterFllow组件实现
无限滚动
在onReachEnd时给LazyForEach数据源增加新数据,并将footer做成正在加载新数据的样式(使用LoadingProgress组件)。
build() {
Column({ space: 2 }) {
WaterFlow({ footer: this.itemFoot.bind(this) }) {
LazyForEach(this.datasource, (item: number) => {
FlowItem() {
Column() {
Text("N" + item).fontSize(12).height('16')
Image('res/waterFlowTest (' + item % 5 + ').jpg')
.objectFit(ImageFit.Fill)
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
.height(this.itemHeightArray[item % 100])
.backgroundColor(this.colors[item % 5])
}, (item: string) => item)
}
// 触底加载数据
.onReachEnd(() => {
console.info("onReachEnd")
setTimeout(() => {
for (let i = 0; i < 100; i++) {
this.datasource.addLastItem()
}
}, 1000)
})
.columnsTemplate("1fr 1fr")
.columnsGap(10)
.rowsGap(5)
.backgroundColor(0xFAEEE0)
.width('100%')
.height('80%')
}
}
// 在数据尾部增加一个元素
public addLastItem(): void {
this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length)
this.notifyDataAdd(this.dataArray.length - 1)
}
提前新增数据
想要流畅的进行无限滑动,还需要调整下增加新数据的时机。比如可以在LazyForEach还剩若干个数据就迭代到结束的情况下提前增加一些新数据。
build() {
Column({ space: 2 }) {
WaterFlow() {
LazyForEach(this.datasource, (item: number) => {
FlowItem() {
Column() {
Text("N" + item).fontSize(12).height('16')
Image('res/waterFlowTest (' + item % 5 + ').jpg')
.objectFit(ImageFit.Fill)
.width('100%')
.layoutWeight(1)
}
}
.onAppear(() => {
// 即将触底时提前增加数据
if (item + 20 == this.datasource.totalCount()) {
for (let i = 0; i < 100; i++) {
this.datasource.addLastItem()
}
}
})
.width('100%')
.height(this.itemHeightArray[item % 100])
.backgroundColor(this.colors[item % 5])
}, (item: string) => item)
}
.columnsTemplate("1fr 1fr")
.columnsGap(10)
.rowsGap(5)
.backgroundColor(0xFAEEE0)
.width('100%')
.height('80%')
}
}
固定宽高比
当瀑布流内容较多时,由于避免了组件整体的测算过程,性能会带来一定的提升。
// 计算FlowItem高度
getSize() {
let ret = Math.floor(Math.random() * this.maxSize)
return (ret > this.minSize ? ret : this.minSize)
}
// 设置FlowItem的高度数组
setItemSizeArray() {
for (let i = 0; i < 100; i++) {
this.itemHeightArray.push(this.getSize())
}
}
build() {
Column({ space: 2 }) {
WaterFlow() {
LazyForEach(this.datasource, (item: number) => {
FlowItem() {
...
}
.width('100%')
// 从数组中取得数值,设置FlowItem的高度
.height(this.itemHeightArray[item % 100])
}, (item: string) => item)
}
...
}
}
写在最后
●如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我两个小忙:
●点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
●关注小编,同时可以期待后续文章ing ,不定期分享原创知识。