前言
浏览别人的网站,看到瀑布流的这种效果很好看,于是就想着自己实现一个,刚开始去写的时候发现其实还是有点难度的,搜索参考了一下别人的理解加自己的改造,自己也写了一个响应式的瀑布流
分析
瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。
- 1、首先瀑布流所有的图片应该保持宽度一致,高度是由内容决定。
左浮动的话,我们可以看到第
6
个盒子直接就在第4
个盒子旁边停下了,因为第4
个高度最高,挡住了它左浮动的去路。第6
个盒子是第2
行的最后一个,所以第7
个盒子只能在第3
行排列了。当排到第12
个盒子的时候,盒子会以第11
个盒子的位置为基础左浮动(这就是第12
个盒子为什么没有‘跳到’第9
个盒子下面的原因),碰到第8
个盒子后又被挡住了。
通过定位的方式是我们实现瀑布流的最基本的原理,只要我们动态的设置它的
top
值、left
值,就能让它排列。
-
2、定位后确定浏览器显示区域内,一行能放多少列图片盒子。
- 获取页面的宽度
- 获取图片盒子的宽度
- 显示的列数 = 页面宽度/图片盒子宽度
计算出来列数,然后根据列数再反过来平分剩下的宽度,然后再原有的宽度基础上加上剩下的平分宽度就是盒子的总宽度,这样就平分了屏幕的宽度,做到自适应
- 3、为了美观我们也可以加上一个空隙
- 显示的列数 = 页面宽度/(图片盒子宽度+间隙);
图片之间也可也增加一个右边距,这样图片和图片之间就有间隙了,增加间隙计算相对定位的时候别忘记计算进去
- 4、计算完列数和宽度之后,就开始填充数据了
- 每一列当成一个数组,用来存储每一列当前的高度之和,
- 然后循环遍历服务端请求来的数据,push到展示的数据里面
- 计算每一次push进去的图片当前的top值和left值
全部代码
<template>
<div class="v-waterfall-content">
<div
class="v-waterfall-item"
v-for="(item, index) in waterfallList"
:key="index"
:style="{
top: item.top + 'px',
left: item.left + 'px',
width: waterfallImgWidth + 'px',
height: item.height + 'px',
}"
>
<img :src="item.src" alt="" />
</div>
</div>
</template>
<script>
import { debaunce } from '../utils'
import imgArr from '../api/imgArr.js'
export default {
name: "v-waterfall",
data() {
return {
waterfallImgWidth: 250,//每一列的宽度
waterfallImgCol: 0,//多少列
waterfallImgRight: 10,//右边距
waterfallImgBottom: 10,//下边距
imgArr,//原始数据
waterfallList: [],//存放计算好的数据
waterfallDeviationHeight: [],//存放瀑布流各个列的高度
}
},
mounted() {
this.waterFall()
// 窗口尺寸变化事件
window.addEventListener("resize", debaunce(() => {
this.waterfallImgWidth = 250
this.waterfallList = []
this.waterFall()
}, 1000)),
// 窗口滚动事件
window.addEventListener("scroll", debaunce(() => {
this.onScroll()
}, 1000))
},
methods: {
//动态计算多少列,每列的宽度
waterFall() {
let pageWidth = this.getClient().width - 10;//可以减去一个滚动条的宽度
this.waterfallImgCol = parseInt(pageWidth / (this.waterfallImgWidth + this.waterfallImgRight));
this.waterfallImgWidth = parseInt(pageWidth % (this.waterfallImgWidth + this.waterfallImgRight) / this.waterfallImgCol) + this.waterfallImgWidth
//初始化偏移高度数组
this.waterfallDeviationHeight = new Array(this.waterfallImgCol);
for (let i = 0; i < this.waterfallDeviationHeight.length; i++) {
this.waterfallDeviationHeight[i] = 0;
}
this.imgPreloading()
},
//图片预加载
async imgPreloading() {
for (let i = 0; i < this.imgArr.length; i++) {
await new Promise((resolve) => {
const aImg = new Image();
aImg.src = this.imgArr[i]
aImg.onload = () => {
const imgData = {}
//根据设定的列宽度求出图片的高度
imgData.height = this.waterfallImgWidth / aImg.width * aImg.height
imgData.src = this.imgArr[i]
this.waterfallList.push(imgData)
//调用图片位置计算方法
this.rankImg(imgData);
resolve()
}
})
}
},
//计算图片偏移量
rankImg(imgData) {
let { waterfallImgWidth, waterfallImgRight, waterfallImgBottom, waterfallDeviationHeight } = this;
//找出当前最短列的索引
let minIndex = this.waterfallDeviationHeight.indexOf(Math.min.apply(null, this.waterfallDeviationHeight))
//获取最短列底部高度,既下一张图片的顶部高度
imgData.top = waterfallDeviationHeight[minIndex];
//计算左侧偏移,最短列索引*(右边距+列宽度)
imgData.left = minIndex * (waterfallImgRight + waterfallImgWidth);
//改变当前列高度
waterfallDeviationHeight[minIndex] += imgData.height + waterfallImgBottom;
},
// clientWidth 处理兼容性
getClient() {
return {
width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
}
},
// 触底事件
onScroll() {
// 滚动高度(document.documentElement.scrollTop)
// 可视区域/屏幕高度(document.documentElement.clientHeight)
// 页面高度(document.documentElement.scrollHeight)
//If(滚动高度 + 可视区域 >= 页面高度){ do something函数}
// 是否滚动到底部
const IS_BOTTOM = document.documentElement.scrollTop || document.body.scrollTop + document.documentElement.clientHeight >= document.documentElement.scrollHeight - 100
if (IS_BOTTOM) {
console.log('到底了')
this.imgArr = imgArr.slice(Math.round(Math.random() * 10), Math.round(Math.random() * 10) + 20)
this.imgPreloading()
}
}
},
destroyed() {
// 离开页面取消监听
window.removeEventListener('scroll', this.onScroll, false)
window.removeEventListener('resize', this.waterFall, false)
}
}
</script>
<style >
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.v-waterfall-content {
position: relative;
}
.v-waterfall-item {
position: absolute;
}
.v-waterfall-item img {
width: 100%;
}
</style>