瀑布流实现方案
column方案
multi-column
(mʌlti kɒləm)实现瀑布流
主要依赖以下几个属性:
column-count
: 设置共有几列column-width
: 设置每列宽度,列数由总宽度
与每列宽度
计算得出column-gap
: 设置列与列之间的间距
column-count
和column-width
都可以用来定义分栏的数目,而且并没有明确的优先级之分。优先级的计算取决于具体的场景。
计算方式为:计算column-count
和column-width
转换后具体的列数,哪个小就用哪个。
而这种展示方式无疑是我们不希望看到的,我们希望的是每个元素都是独立的,前后不断开,此时我们需要使用break-inside
来实现。
break-inside: auto | avoid
- auto: 元素可以中断
- avoid: 元素不能中断
效果实现了,但由于multi-column
布局中子元素的排列顺序是先从上往下
再从左至右
,所以这种方式仅适用于数据固定不变的情况,对于滚动加载更多等可动态添加数据的情况就并不适用了。
Flexbox 实现瀑布流
Flexbox
布局到今天已经是使用非常广泛的,也算是很成熟的一个特性。在此就不再介绍Flexbox
布局的相关内容,如果还有不是很了解的朋友,可参见阮一峰的《Flex 布局教程:语法篇》
那接下来我们就看Flexbox
怎么实现瀑布流布局。
此时,我们需要将html结构设计成如下结构:
html
<div class="masonry">
<!-- 第一列 -->
<div class="column">
<div class="item"></div>
<!-- more items-->
</div>
<!-- 第二列 -->
<div class="column">
<div class="item"></div>
<!-- more items-->
</div>
<!-- 第三列 -->
<div class="column">
<div class="item"></div>
<!-- more items-->
</div>
</div>
上面代码中div.masonry
代表当前瀑布流容器,div.column
代表每一列的容器,div.item
代表每一列中的每一项。
我们需要将div.masonry
和div.column
都通过display:flex
将其设置为Flex
容器。
不同的是瀑布流容器
主轴方向设置为水平方向flex-direction:row
,列容器
主轴方向设置为垂直方向flex-direction:column
css
.masonry {
display: flex; // 设置为Flex容器
flex-direction: row; // 主轴方向设置为水平方向
}
.column {
display: flex; // 设置为Flex容器
flex-direction: column; // 主轴方向设置为垂直方向
}
由于当前的html结构分为了瀑布流容器
和列容器
,并且常见的需求图片均是从左至右
再从上到下
来进行排列,所以需要通过Javascript
来区分每一列的具体数据:
假设分为三列,伪代码如下:
js
let data1 = [], //第一列
data2 = [], //第二列
data3 = [], //第三列
i = 0;
while (i < data.length) {
data1.push(data[i++]);
if (i < data.length) {
data2.push(data[i++]);
}
if (i < data.length) {
data3.push(data[i++]);
}
}
return {
//第一列
data1,
//第二列
data2,
//第三列
data3
};
使用场景
在个人博客中想要使用瀑布流的方式展示一些照片,首先使用css的多栏布局进行设置,但这会导致图片排列按照先从上到下,再从左到右的方式,不符合自己的要求,因此使用flex布局,将瀑布流容器设置为flex布局,flex-direction设置为水平方向flex-direction:row
,将页面分为三列,列容器
主轴方向设置为垂直方向flex-direction:column
。同时对图片数据进行处理,将他们按照顺序放到三个列表中,实现图片从左到右排列。
有的列过长的问题
可以看该文章前端 - 5 种瀑布流场景的实现原理解析 - 凹凸实验室中的第六部分------横向+高度排序,即每一次都将图片放到最短的那一列中。 首先后端要知道图片的大小,再将其放入列表中,获取列表的高度,就可以知道最短的列表是哪一个并将下一张图片放进去。
我的实现
该博客针对瀑布流给出了完全基于原生js的方法基于react的瀑布流组件 - 掘金 (juejin.cn)。基于该方法,我写了一个基于flex布局的瀑布流,代码如下,核心内容有:
- 首先使用 flex布局 实现了图片的三列布局,即这三列容器的主轴方向设置为行,而每一列中也设置为flex布局并将主轴方向设置为列
- 通过不断比较三个列的长短,每次都将图片放入最短的那一列中,实现图片的均匀分布
- 在第二步中,由于要动态比较三个列的长短,因此需要知道每一张图片的高度,但本项目中图片是从网页链接中下载得来的,无法获取高度,因此需要先通过监听onload事件 获取图片的高度,这里使用到了promise(代码第32-43行)
问题:
- 每一列的宽度设置为30%,因此图片会出现缩放的问题,但第三步只能获取到图片本身的高度,而不能获取图片缩放后的高度,因此会对最短的列的计算产生影响。目前的解决方法: 对所有图片设置一个总体的宽度。
- 还有一个useEffect的问题,在第75-77行代码的
useEffect
方法依赖于数组imgList
,如果不使用useMemo将imgList
缓存,是会出现页面频繁渲染的情况,答案在该博客中useEffect使用注意事项。因为当useEffect
依赖项是数组时,useEffect
使用浅层比较法来比较数值,而每一次js都会给数组创建一个新的地址,因此比较将总是给出 false,因此会一直触发回调。我使用useMemo
将imgList
进行了缓存,这样react不会每次渲染都重新创建imgList
,而是使用缓存的原数组,地址不变,因此不会引起useEffect
的回调。useMemo
允许我们缓存并重用某个值,只有在指定的依赖项发生变化时才会重新计算useCallback
接收一个函数和一个依赖项数组作为参数,然后返回一个 memoized 版本的函数。如果依赖项数组中的任何一个依赖项发生更改,则useCallback
会返回一个新的函数。如果依赖项数组不变,则useCallback
将返回上次 memoized 的函数。
jsx
import './index.scss'
import { useState, useEffect, useMemo} from 'react'
function allocateItems(
ls,
len,
colWidth
){
/** 各个瀑布流的内容列表 */
const arr = []
/** 各个瀑布流的高度列表 */
const heightArr = []
// 初始化瀑布流的内容列表和高度列表
for (let i = 0; i < len; i++) {
arr.push([])
heightArr.push(0)
}
/** 获取高度最小的流的索引值 */
function getIndexOfMinHeightFlow() {
let minH = Number.MAX_SAFE_INTEGER
let minIndex = 0
heightArr.forEach((h, index) => {
if (h < minH) {
minH = h
minIndex = index
}
})
return minIndex
}
function Create(url) {
return new Promise((resolve, reject) => {
let oimg = new Image();//创建img标签
oimg.onload = () => {
resolve([oimg.height, oimg.width])
}
oimg.onerror = () => {
reject(`'${oimg}' is not find`)
}
oimg.src = url
})
}
ls.forEach(imgsrc => {
Create(imgsrc).then(res => {
const index = getIndexOfMinHeightFlow()
arr[index].push(imgsrc)
let temp = res[1]/350 //350是每一列的宽度
heightArr[index] += res[0] /temp
})
})
return arr
}
const Home = () => {
const imgList = useMemo(()=>{
return ['https://pica.zhimg.com/v2-6b0d0b75d5c743d628f43aa19b98109b_r.jpg?source=1940ef5c',
'https://img.zcool.cn/community/019e1760aa6ab711013f4720ffacc1.jpg@1280w_1l_2o_100sh.jpg',
'https://image.9game.cn/2020/9/13/175850242.jpg',
'https://rss.sfacg.com/web/account/images/avatars/app/2010/25/1c44687f-df90-432e-8d35-cf2f46d1665b.jpg',
'https://filestorage.9917.cn/d/file/p/2020/09-28/aa010ae915f382ea20e8e55cb42fe108.jpg',
'https://img.shoujiwan.com/upload/image/20201229/1609209793883552.png',
'https://tse3-mm.cn.bing.net/th/id/OIP-C.rUVejKHCnusjVhk2p11Q3wHaKe?pid=ImgDet&rs=1',
'https://pica.zhimg.com/v2-6b0d0b75d5c743d628f43aa19b98109b_r.jpg?source=1940ef5c',
'https://syimg.3dmgame.com/uploadimg/upload/image/20200928/20200928182149_29863.jpg',
'https://ts1.cn.mm.bing.net/th/id/R-C.de96a943effbb5a98ef676bec47c1ca1?rik=nvP9O8O%2bCluqtA&riu=http%3a%2f%2fimg.18183.cn%2fuploads%2fallimg%2f210326%2f112-2103261F410.jpg&ehk=4dfGp%2buYkfJXrNMnW72C2K%2fiu6dzY8cHJkwu9qFk%2fSc%3d&risl=&pid=ImgRaw&r=0',
'https://pica.zhimg.com/v2-6b0d0b75d5c743d628f43aa19b98109b_r.jpg?source=1940ef5c',
'https://dl.bbs.9game.cn/attachments/forum/202008/26/154724lvwu4cau55yav7wu.jpg',
'https://c-img.18183.com/images/2020/11/23/197e16b31bb7d2d209cbab22f09cb4d4.jpg@!18183',
'https://img.zcool.cn/community/019e1760aa6ab711013f4720ffacc1.jpg@1280w_1l_2o_100sh.jpg',
'https://rss.sfacg.com/web/account/images/avatars/app/2010/25/1c44687f-df90-432e-8d35-cf2f46d1665b.jpg']
}, [])
const [colList, setColList] = useState(
allocateItems(imgList, 3, 100)
)
useEffect(() => {
setColList(allocateItems(imgList, 3, 100))
}, [imgList])
return (
<div className="masonry">
<div className="column">
{
colList[0].map((item, index) => {
return (<>
<img src={item} width={350} alt='钟离' key={index}></img>
</>)
})
}
</div>
<div className="column">
{
colList[1].map((item, index) => {
return (<>
<img src={item} width={350} alt='钟离' key={index}></img>
</>)
})
}
</div>
<div className="column">
{
colList[2].map((item, index) => {
return (<>
<img src={item} width={350} alt='钟离' key={index}></img>
</>)
})
}
</div>
</div>
)
}
export default Home
图片懒加载
如何实现
可以使用IntersectionObserver
这个api来实现懒加载react项目优化重要手段之一------图片懒加载-CSDN博客