应用里需要做一个瀑布流的页面,类似于小红书的首页,今天终于比较完美地把这个页面做完,记录总结一下思路,分享给大家。

第一部分:CSS将页面分成左右两列,左边循环一个数组,右边循环一个数组。在data里,给左右两列的div设置一个初始高度为0。从后端获取数据的时候,对后端数组进行循环处理,根据左右两列的div高度,决定把当前对象分给左边还是右边的数组。每次分配的时候,根据元素的高低,把左/右边div的高度新增上去,以便下一个对象分配的时候使用。

元素的高需要大家根据自身情况判断,比如图片高度、标题的高度等等,自己判断一下有几种情况。比如我的元素里,图片的高度是比较固定的,影响高度的主要是标题的行数,就根据标题的字数进行判断每个元素的整体高度。

// 获取当前页的清单数据,pageNum默认为1,hasMore默认为true
async getLists(pageNum) {
  if (this.hasMore) {
    // 如果hasMore是true,就可以继续去获取数据
    const moreLists = await ListService.getLists(pageNum)
    this.moreLists = moreLists
    // 如果获取到的lists为空,说明没有更多数据了,将hasMore改成false,页面提示"没有更多数据"了
    if (moreLists.length === 0) {
      this.hasMore = false
      // 如果已经没有更多数据了,就取消监听窗口变化,因为全部数据都展示出来了不需要再监听
      window.removeEventListener('scroll', this.monitorScrollToEnd)
      return
    }
    // 将获取的数据添加到lists中
    for (let list of moreLists) {
      this.lists.push(list)
    }
    // 将获取到的数据分列
    this.spiltList()
  }
},
// 将获取到的数据,分为左右两列
spiltList() {
  // 定义变量,接受list数据源
  let data = {}
  for (let i = 0; i < this.moreLists.length; i++) {
    // 每次只处理新增的lists
    data = this.moreLists[i]
    if (this.heightOfLeft <= this.heightOfRight) {
      // 如果左边矮,就放到左边
      this.listLeft.push(data)
      // console.log('左边有' + this.listLeft)
      // 根据自己的元素逻辑更新容器的高度
      if (data.listTitle.length <= 10) {
        this.heightOfLeft = this.heightOfLeft + 249.67
      } else {
        this.heightOfLeft = this.heightOfLeft + 271.67
      }
      // console.log(
      //   '左边高变成' + this.heightOfLeft + '右边高是' + this.heightOfRight
      // )
    } else {
      // 如果右边矮,就放到右边
      this.listRight.push(data)
      // console.log('右边有' + this.listRight)
      // 根据自己的元素逻辑更新容器的高度
      if (data.listTitle.length <= 10) {
        this.heightOfRight = this.heightOfRight + 249.67
      } else {
        this.heightOfRight = this.heightOfRight + 271.67
      }
      // console.log(
      //   '右边高变成' + this.heightOfRight + '左边高是' + this.heightOfLeft
      // )
    }
  }
  // 数据处理完后,重新启动监听
  window.addEventListener('scroll', this.monitorScrollToEnd)
},

第二部分:页面加载完后,开始监听窗口高度,如果已经滚动到最下面,就向后台请求下一页的数据。这里注意触发请求时,要暂时关闭监听,否则就会出现一连串请求的情况,一下子给请求个七八次。等到新的数据加载完后,再重新启动监听即可。做完这一部分就可以实现瀑布流无限加载到最后。

mounted() {
  // 监听窗口变化
  window.addEventListener('scroll', this.monitorScrollToEnd)
},

monitorScrollToEnd() {
  var scrollTop =
    document.documentElement.scrollTop || document.body.scrollTop
  var windowHeight =
    document.documentElement.clientHeight || document.body.clientHeight
  var scrollHeight =
    document.documentElement.scrollHeight || document.body.scrollHeight
  // 滚动到底部触发
  if (Math.round(scrollTop) + windowHeight > scrollHeight - 220) {
    // 暂时取消监听,等数据都拿到了再重启监听
    window.removeEventListener('scroll', this.monitorScrollToEnd)
    // 获取下一页的数据
    this.pageNum += 1 // 页数加1
    // 要解决触发后连续触发的问题
    this.getLists(this.pageNum) // 获取下一页数据
  }
},

第三部分:由于开启了窗口高度监听,这时候如果跳到详情页,或者其它页面的时候,会发现窗口监听还会继续存活,在别的页面滚动窗口,瀑布流页面还是会不断地加载加载,这个问题就需要使用路由守卫来解决。具体就是设置当路由变化的时候,取消窗口监听事件,当然回到页面的时候,要重新启动窗口监听。

这里有一个坑是beforeRouteLeave千万不要使用箭头函数,否则会获取不到this,感谢知乎大拿的解答。

beforeRouteLeave: function (to, from, next) {
  // 获取当前高度
  let scrollTop =
    document.documentElement.scrollTop || document.body.scrollTop
  // 缓存当前高度
  sessionStorage.setItem('scrollTop', scrollTop)
  // 取消监听窗口变化,否则在别的路径页面也会继续监听
  window.removeEventListener('scroll', this.monitorScrollToEnd)
  next()
},

第四部分:下一个问题就是,瀑布流页面加载到前几屏了,这时候访问了其它路由,又回来的时候,会发现页面会被重新加载,导致瀑布流又回到了顶部。这个问题也很麻烦。解决的办法是使用keep-alive功能,将这个页面保存在缓存中。同时在离开这个路由的时候,将当前页面的高度缓存到sessionStorage里,等回到瀑布流页面,使用scrollTo直接跳转到缓存的位置,即可无感保留瀑布流的位置。

// 第一步:在route/index.js中,添加meta
const routes = [
  {
    path: '/',
    name: 'HomePage',
    component: HomePage,
    meta: {
      keepAlive: true,
    },
  },
]

// 第二步:在App.vue中,添加keep-alive标签
<keep-alive>
  <router-view v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" />

// 第三步:跳出路由时,获取并缓存当前的高度
beforeRouteLeave: function (to, from, next) {
  // 获取当前高度
  let scrollTop =
    document.documentElement.scrollTop || document.body.scrollTop
  // 缓存当前高度
  sessionStorage.setItem('scrollTop', scrollTop)
  // 取消监听窗口变化,否则在别的路径页面也会继续监听
  window.removeEventListener('scroll', this.monitorScrollToEnd)
  next()
},

// 第四步:页面被重新激活时,获取缓存中的高度,并scrollTo缓存位置,同时重新启动监听窗口
activated() {
  if (sessionStorage.getItem('scrollTop') !== 0) {
    window.scrollTo(0, sessionStorage.getItem('scrollTop'))
  }
  // 监听窗口变化
  window.addEventListener('scroll', this.monitorScrollToEnd)
},

做完这些,基本上就是一个比较完善的瀑布流页面了,参考了很多前辈的总结。
如果大家还有疑问,欢迎加我微信交流,微信号:zdplist

最后编辑:2023年10月30日 ©著作权归作者所有

评论已关闭