瀑布流效果实现过程总结

232 阅读3分钟

theme: juejin

瀑布流效果实现过程总结

起因

23年的1月12日,本该沉浸在放年假的愉悦氛围中,没想到偏偏来了个紧急的需求,要求年前完成~ 我被分配需要完成瀑布流部分 😒

实现过程(异常艰辛)

由于之前没有写过这类的需求,拿到需求后,异常懵逼 😳,这可怎么办呢~头疼

  1. 寻找思路(千万不要闭门造车)  ̄ω ̄= 不管了,先找找思路吧,逛一逛掘金、百度,发现有不少关于瀑布流效果实现的,但都是用的jquery库,而且实现的效果跟想要的效果 相差的有点大~(基本都是只教了拿到数据怎么渲染实现瀑布流,没有具体的怎么在滚动到底部时获取数据,预渲染等等) 但,好在思路有了 😄
  2. 初步确认思路 ヾ(=・ω・=)o
    1. 图片样式:图片等宽,保持图片的宽高比
    2. 图片摆放位置方式:利用绝对定位的固定图片(计算当前列的高度,图片摆放在当前最矮列上)
    3. 请求数据方式:mounted时请求一次接口,获取首屏的图片信息渲染,每次滚动条滑动距离到底部100px内时重新发送请求,由于每次滚动都会触发scroll事件, 额外使用防抖来减少请求次数,图片全部都请求完成后不再请求数据
  3. 大胆实现(管它有枣没枣,打一杆子再说!) ( ̄▽ ̄)~* 把瀑布流的每一项称为 卡片 2023-01-30-16-31-09.png
// 子组件,进行渲染
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
    }
}
  1. 明确问题 ( ̄▽ ̄)""
    1. 图片加载是需要时间的,如果图片还处于加载中,则获取不到图片的高度,导致计算图片位置错误
  2. 解决问题、新问题 <( ̄︶ ̄)>
    1. 当页面加载完成之后onload之后 在计算卡片位置
    2. 新问题:瀑布流的所有卡片都设置了 绝对定位 属性,随后才计算图片位置的, 导致图片一开始会都叠在一起
  3. 升级(更为优雅的解决问题) (~ ̄▽ ̄)~
    1. 接口中,返回图片的宽高比,固定了每张图片的实际宽度,便可根据图片的宽高比计算出,该图片的实际高度,进而计算 每张图片的位置,把位置信息传递给子组件,子组件直接渲染
    2. 图片从无到有会显得很突兀,可以使用element-ui的图片组件,设置占位图片,当load事件触发时,显示该图片
  4. 项目缺陷 ╮( ̄▽ ̄)╭ 项目虽然做完了,但仍有一些可以完善的点的,例如:
    1. 可以让卡片像 虚拟列表那样 只显示可视区域的 数据,节省DOM,优化性能
    2. 现在已经有一些非常优秀的轮子可以直接用了,如果项目紧急,何不直接用呢~

由于视频没法上传~ 最后附上代码核心部分~ 仅供参考 ╰( ̄▽ ̄)╭

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'
}