theme: juejin
瀑布流效果实现过程总结
起因
23年的1月12日,本该沉浸在放年假的愉悦氛围中,没想到偏偏来了个紧急的需求,要求年前完成~ 我被分配需要完成瀑布流部分 😒
实现过程(异常艰辛)
由于之前没有写过这类的需求,拿到需求后,异常懵逼 😳,这可怎么办呢~头疼
- 寻找思路(千万不要闭门造车)  ̄ω ̄= 不管了,先找找思路吧,逛一逛掘金、百度,发现有不少关于瀑布流效果实现的,但都是用的jquery库,而且实现的效果跟想要的效果 相差的有点大~(基本都是只教了拿到数据怎么渲染实现瀑布流,没有具体的怎么在滚动到底部时获取数据,预渲染等等) 但,好在思路有了 😄
- 初步确认思路 ヾ(=・ω・=)o
- 图片样式:图片等宽,保持图片的宽高比
- 图片摆放位置方式:利用绝对定位的固定图片(计算当前列的高度,图片摆放在当前最矮列上)
- 请求数据方式:mounted时请求一次接口,获取首屏的图片信息渲染,每次滚动条滑动距离到底部100px内时重新发送请求,由于每次滚动都会触发scroll事件, 额外使用防抖来减少请求次数,图片全部都请求完成后不再请求数据
- 大胆实现(管它有枣没枣,打一杆子再说!) ( ̄▽ ̄)~*
把瀑布流的每一项称为 卡片
// 子组件,进行渲染
waterfallHandler(isInit) {
// 瀑布流的所有卡片DOM
var oImages = document.getElementsByClassName('picture-stream-item')
// 当前图片数量
var imagesLen = oImages.length
// 瀑布流容器
var oBox = document.getElementsByClassName('picture-stream__inner')[0]
// 瀑布流容器宽度
var boxWidth = oBox.clientWidth
// 每张图片的宽度(含间隙) GAP是行、列间隙值
var imgWidth = IMG_WIDTH + GAP
// 获取图片的列数
const column = Math.ceil(boxWidth / imgWidth)
// 每列高度 初始化
if (isInit) {
for (let i = 0; i < column; i++) {
this.heightArr[i] = 0
}
}
// 遍历所有图片进行定位处理 只对新增的图片进行定位
// this.imageArr 是请求接口获取的 新的卡片数据
for (let index = imagesLen - this.imageArr.length; index < imagesLen; index++) {
// 每张卡片的图片
const imgItem = oImages[index].getElementsByTagName('img')[0]
// 计算当前图片描述的 行数 最多8行
const lineNum = Math.ceil(this.imageArr[index].content.length / COL_NUM)
// 当前卡片的高度(图片高度 + 定位高度 + 描述高度 + 价格高度)
const itemHeight = imgItem.clientHeight + (this.imageArr[index].location ? 42 : 20) + 46 + 20 * (lineNum > MAX_LINE_NUMBER ? MAX_LINE_NUMBER : lineNum)
// 高度数组最小的高度
const minHeight = Math.min(...this.heightArr)
// 高度数组最小的高度的索引
const minIndex = this.heightArr.indexOf(minHeight)
imgItem.style.top = minHeight + 'px'
imgItem.style.left = minIndex * imgWidth + 'px'
// GAP是行、列间隙值
this.heightArr[minIndex] += itemHeight + GAP
}
}
- 明确问题 ( ̄▽ ̄)""
- 图片加载是需要时间的,如果图片还处于加载中,则获取不到图片的高度,导致计算图片位置错误
- 解决问题、新问题 <( ̄︶ ̄)>
- 当页面加载完成之后
onload之后 在计算卡片位置 - 新问题:瀑布流的所有卡片都设置了 绝对定位 属性,随后才计算图片位置的, 导致图片一开始会都叠在一起
- 当页面加载完成之后
- 升级(更为优雅的解决问题) (~ ̄▽ ̄)~
- 接口中,返回图片的宽高比,固定了每张图片的实际宽度,便可根据图片的宽高比计算出,该图片的实际高度,进而计算 每张图片的位置,把位置信息传递给子组件,子组件直接渲染
- 图片从无到有会显得很突兀,可以使用element-ui的图片组件,设置占位图片,当
load事件触发时,显示该图片
- 项目缺陷 ╮( ̄▽ ̄)╭
项目虽然做完了,但仍有一些可以完善的点的,例如:
- 可以让卡片像 虚拟列表那样 只显示可视区域的 数据,节省DOM,优化性能
- 现在已经有一些非常优秀的轮子可以直接用了,如果项目紧急,何不直接用呢~
由于视频没法上传~ 最后附上代码核心部分~ 仅供参考 ╰( ̄▽ ̄)╭
waterfallHandler(isInit) {
// var oImages = document.getElementsByClassName('picture-stream-item')
var imgArr = this.pictures
var imagesLen = imgArr.length
var oBox = document.getElementsByClassName('picture-stream__inner')[0]
var boxWidth = oBox.clientWidth
// 固定每张图片的宽度(含间隙)
var imgWidth = IMG_WIDTH + GAP
// 获取图片的列数
const column = Math.ceil(boxWidth / imgWidth)
// 高度数组 初始化
if (isInit) {
for (let i = 0; i < column; i++) {
this.heightArr[i] = 0
}
}
// 遍历所有图片进行定位处理 只对新增的图片进行定位
for (let index = imagesLen - this.imageArr.length; index < imagesLen; index++) {
const imgItem = imgArr[index]
const lineNum = Math.ceil(imgItem.content.length / COL_NUM)
// 当前元素的高度
const itemHeight = imgItem.articleImage.height / (imgItem.articleImage.width / IMG_WIDTH) + (imgItem.location ? 42 : 20) + 46 + 20 * (lineNum > MAX_LINE_NUMBER ? MAX_LINE_NUMBER : lineNum)
// 高度数组最小的高度
const minHeight = Math.min(...this.heightArr)
// 高度数组最小的高度的索引
const minIndex = this.heightArr.indexOf(minHeight)
this.$set(imgItem, 'top', minHeight + 'px')
this.$set(imgItem, 'left', minIndex * imgWidth + 'px')
this.heightArr[minIndex] += itemHeight + GAP
}
document.getElementsByClassName('picture-stream')[0].style.height = Math.max(...this.heightArr) + 'px'
}