瀑布流 滚动加载,兼容ssr

瀑布流

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。最早采用此布局的网站是Pinterest,逐渐在国内流行开来。国内大多数清新站基本为这类风格。这是百度百科对其的描述

简而言之:就是一种自适应布局,根据每一项的高度以及每一列的高度,决定这一项放在哪一列中展示,随着加载数据的增多,使得每一列的高度不会相差太多,不能出现一列过长或过短的情况

展示效果:

实现代码

vue3实现过程:

有几项就初始化几项数据,_allHeight:记录当前列的高度,将下一项放在目前最短的一列中,即:_allHeight最小的一列中

<div v-for="(col, colIndex) in state.xRenderList" :key="colIndex" class="col">
  <div
    v-for="item in col.cardArr"
    :key="item.id"
    class="col-card"
   >
     <img :src="item.link" class="card-img" />
     <div class="card-des">
       <div class="card-title ellipsis">{{ item.title }}</div>
         <div class="card-con">{{ item.overview }}</div>
       </div>
   </div>
 </div>
<script setup>

  const state = reactive({
    // 分页信息
    pagination: {
      pageIndex: 1, // 页码
      pageSize: 20, // 每页数据条数
    },
    colList: [], //每一次从远程获取的列表
    //渲染列表:想要展示几列就初始化几项
    xRenderList: [
      {
        _allHeight: 0, //用于计算一列的总高度
        cardArr: [],
      },
      {
        _allHeight: 0,
        cardArr: [],
      },
      {
        _allHeight: 0,
        cardArr: [],
      },
      {
        _allHeight: 0,
        cardArr: [],
      },
    ],
    isLoading: false, //是否正在加载计算高度
  })
</script>

当获取到数据之后,开始加载每一项的数据:

 // 获取列表
 // calc:true:标识需要计算高度
  async function getCaseList(calc) {
    state.isLoading = true
    const params = {
      pageIndex: state.pagination.pageIndex,
      pageSize: state.pagination.pageSize,
    }
    const res = await getList(params).catch((e) => {})
    if (res?.code === 200) {
      const { records } = res.data.data
      //先置空
      state.colList = []
      state.colList = records || []
      for (let i = 0; i < state.colList.length; i++) {
        const item = state.colList[i]
        //根据每一项的高度,加载方法,
        await addItem(item,calc)
      }
    }
    state.isLoading = false
  }

//加载初始内容
 await getCaseList()

预加载每一项图片的高度,文本高度先忽略。

预加载图片方法:(大致计算每一项的高度)

  // 假设图片、概述初始宽度
  const _imgWidth = 285
  const _txtWidth = 0
  const _marginHight = 30
  // 预加载图片高度,计算单个卡片高度
  function preload(item) {
    return new Promise(async (resolve, reject) => {
      let currentHeight = 0
      // 无图时
      if (!item.link) {
        currentHeight = _txtWidth + _marginHight
        resolve(currentHeight)
      } else {
        const oImg = new Image()
        oImg.src = item.link
        oImg.onload = oImg.onerror = (e) => {
          // 预加载图片,计算图片容器的高
          const imgHeight = e.type === 'load' ? Math.round(_imgWidth * (oImg.height / oImg.width)) : _imgWidth
          currentHeight = imgHeight + _txtWidth + _marginHight
          resolve(currentHeight)
        }
      }
    })
  }

根据预加载的高度加载每一项的方法

async function addItem(item, calc) {
   // 初始时,不计算高度
   const itemHeight = calc ? await preload(item) : 1000
    // 放入最小高度的一列中
    let minHight = state.xRenderList[0]._allHeight
    let minIndex = 0
    for (let i = 0; i < state.xRenderList.length; i++) {
      const col = state.xRenderList[i]
      if (minHight > col._allHeight) {
        minHight = col._allHeight
        minIndex = i
      }
    }
    //在列里添加这一项
    state.xRenderList[minIndex].cardArr.push(item)
    state.xRenderList[minIndex]._allHeight = state.xRenderList[minIndex]._allHeight + itemHeight
  }

因为初始时不计算高度,所以到客户端时,再重新计算图片高度加载每一个card

 onMounted(async () => {
    state.xRenderList = [
      {
        _allHeight: 0, //用于服务端渲染计算一列的总高度
        cardArr: [],
      },
      {
        _allHeight: 0,
        cardArr: [],
      },
      {
        _allHeight: 0,
        cardArr: [],
      },
      {
        _allHeight: 0,
        cardArr: [],
      },
    ]
    for (const v of state.caseList) {
      await addItem(v, true)
    }
  })

向下滚动时,继续加载数据,改变pageindex,向后端请求下一页的数据

优化:当上一次请求还没结束或上一次请求为空,滚动时就不再向后端发请求了

onMounted(async () => {
    document.addEventListener('scroll', async function () {
      /*判断是否触底*/
      //如果正在预加载
      if (state.isLoading) {
        return
      }
      // 如果上次请求的数据为空表明没有下一页数据了,就阻止请求
      if (state.colList.length === 0) {
        return
      }

      //获取所有列元素,重新计算高度(防止有的文本过长,导致有的列加载不平均 )
      const cols = document.getElementsByClassName('col')
      for (let i = 0; i < cols.length; i++) {
        state.xRenderList[i]._allHeight = cols[i].clientHeight
      }

      //滚动条距离顶部距离
      const st = document.documentElement.scrollTop
      //页面可视区域高度
      const ch = document.documentElement.clientHeight
      //页面总高度
      const sh = document.documentElement.scrollHeight
      //页面可视区域高度 + 滚动条距离顶部距离,大于页面总高度的99%则表示触底
      //之后执行加载数据方法即可
      if (ch + st > sh * 0.99) {
        //触底,向后端请求下一页的数据
        state.pagination.pageIndex++
        // 滚动时,需要计算高度加载样式
        await getList(true)
      }
    })
  })

补充:

样式:

.waterfall-wrap::after {
      display: block;
      width: 100%;
      height: 1px;
      clear: both;
      content: '';
    }

    .waterfall-wrap {
      padding-top: 30px;

      .col {
        float: left;
        width: calc(25% - 15px);
        margin-right: 20px;

        .col-card {
          width: 100%;
          margin-bottom: 20px;

          .card-img {
            width: 100%;
            border-radius: 8px 8px 0 0;

            // object-fit: scale-down;
          }

          .card-des {
            padding: 5px 22px 18px;
            background: rgba(#fff, 0.85);
            border-radius: 0 0 8px 8px;

            .card-title {
              font-size: 18px;
              font-weight: bold;
              line-height: 36px;
              color: #4d4d4d;
            }

            .card-con {
              font-size: 14px;
              line-height: 20px;
              color: #7a8ba6;
            }
          }
        }
      }
    }

    .waterfall-wrap > div:nth-child(4n) {
      margin-right: 0;
    }

    .case-img-link {
      cursor: pointer;
    }
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值