先上一张效果图:
什么是瀑布流?
瀑布流是前端的一种常见的布局,具体表现形式是多栏布局,宽度固定,高度不定。与此同时,随着页面的滚动,会不断有新的元素添加到瀑布流尾部的最低列。 常见的瀑布流有如下三种样式:
卡片流: 这种样式常见于移动端购物APP,一行只展示一件物品。随着页面的加载逐步出现更多的内容,也就是我们常说的下拉列表,算是最简单的一种瀑布流了:
固定式瀑布流: 这种样式图片区域大小高度保持不变。统一的高度会使整个界面看起来比较整齐。 PC端淘宝首页:
移动端淘宝某页面:
- 交错式瀑布流: 交错式瀑布流是目前各大APP首页最常采用的布局格式,也是用户视觉上最为舒服的一种布局: ## 实现瀑布流的几种常见布局方式 目前,市面上常见的瀑布流开源插件采用的布局方案主要有以下几种: - 绝对定位:这种布局需要在图片渲染之前得知图片的宽高,以便于每次插入图片时插入到高度最矮的那一列。 - Flex布局:这种布局也是把新增的图片插入到高度最矮的一列中,但不是通过绝对定位。 ## 上手实践 鉴于让后台接口直接返回图片的宽高不太现实&交错式布局难度最大。因此本次实践采用的是Flex布局的方案来实现交错式瀑布流。 ### Flex布局实现瀑布流的过程 - 第一步:明确瀑布流有多少列,假设为x - 第二步:从瀑布流图片接口列表中取出前x个元素,依次渲染至每一列的第一行 - 第三步:从第x+1个元素开始(**此步是精髓**) - 首先计算每一列的高度 - 然后将当前要添加的元素添加至高度最低的那一列中 - 循环第三步,直至所有元素都添加至瀑布流中 ### 实践之前的必备知识 因为在渲染当前图片前,需要得知每一列的高度,因此在添加当前图片至瀑布流之前,我们需要保证前一张添加的图片已出现在页面上。那如何得知前一张图片已经渲染成功呢?并且如何在前一张图片渲染成功之后再添加新的图片呢? 这就涉及到Vue
和React
的生命周期,以及IntersectionObserver
API的概念了: #### 何为IntersectionObserver? ##### IntersectionObserver结构 IntersectionObserver(交叉观察器)
可以自动监听元素是否进入了设备的可视区域之内,而不需要通过频繁的计算(比如我们常见的根据滚动条是否触底来做的一些判断)。由于可见的本质是,目标元素与视口产生一个交叉区,所以这个API叫做“交叉观察器”。 通过上面简述,其实我们常提的图片懒加载就可以采用Intersection
API来实现呀,这样也免去了与滚动条的距离计算,才是上上策~ ```javascript let observerObj = new IntersectionObserver(callback, option); ``` Intersection
是浏览器原生提供的构造函数,接收两个参数: - callback
: 可见性发生变化时的回调函数,包含changes和observer两个参数 - option
:配置对象(可选) 构造函数的返回值是一个观察器实例。实例一共有4个方法: - observe
: 开始监听特定元素 - unobserve
: 停止监听特定元素 - disconnect
: 关闭监听工作 - takeRecords
: 返回所有观察目标的对象数组 callback回调函数的第一个参数changes是一个数组,该数组的每一项都是一个IntersectionObserverEntry对象
##### IntersectionObserverEntry对象 IntersectionObserverEntry对象
是我们本文实现尾部追加元素的重要属性之一!该对象内部包含如下几个属性: - boundingClientRect
:目标元素的矩形区域的信息 - intersectionRatio
: 目标元素的可见比例,即intersectionRect占boundingClientRect的比例。完全可见时为1,完全不可见时小于等于0 - intersectionRect
:目标元素与视口(或根元素)的交叉区域的信息 - isIntersecting
: 布尔值,目标元素与交集观察者的根节点是否相交(**常用**) - isVisible
: 布尔值,目标元素是否可见(该属性还在试验阶段,不建议在生产环境中使用) - rootBounds
: 根元素的矩形区域额的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null - target
: 被观察的目标元素,是一个DOM节点对象(**常用**) - time
: 可见性发生变化的时间,是一个高精度时间戳,单位为毫秒 在本文要实现的瀑布流中,IntersectionObserver
的使用如下: ```javascript // 瀑布流布局:取出数据源中最靠前的一个并添加到瀑布流高度最小的那一列,等图片完全加载后重复该循环 let observerObj = new IntersectionObserver( (entries) => { for (const entry of entries) { const { target, isIntersecting } = entry if (isIntersecting) { // 添加下一张图片 addPicture() // 取消监听当前已加载的图片 observerObj.unobserve(target) } } }, { rootMargin: '0px 0px 300px 0px', // 提前加载 }, ) ``` ##### 在Vue中,使用nextTick实现异步添加 nextTick
是Vue
中异步调用DOM的解决方案,它可以保证我们前一张图片渲染至页面上之后,再执行下一次渲染的操作,个人认为,有点类似于onMounted
钩子函数: ```javascript nextTick(() => { columnArray = document.querySelectorAll('.flex-column')[index].querySelectorAll('.flex-column-ele'); // 添加交叉监听器 observerObj.observe(columnArray[columnArray.length - 1]) }) ``` ##### 在React中,使用useEffect实现渲染后添加 在React
中,useEffect
的执行时机是页面渲染完成之后,因此,我们只需要把监听上一张图片是否渲染完成,以及加载下一张图片的函数在useEffect
里引入即可: ```javascript useEffect(() => { if (dataList.length > 0) {// 跳过页面初始化 console.log('添加图片') addPicture() } }, [hasGet]) ``` ### Vue3.0利用flex实现瀑布流源码 ```typescript
{{ curItem.desc }}