【项目研究】瀑布流的使用与多张图片加载

瀑布流实现方案

「中高级前端」干货!深度解析瀑布流布局

column方案

multi-column(mʌlti kɒləm)实现瀑布流主要依赖以下几个属性:

  • column-count: 设置共有几列
  • column-width: 设置每列宽度,列数由总宽度每列宽度计算得出
  • column-gap: 设置列与列之间的间距

column-countcolumn-width都可以用来定义分栏的数目,而且并没有明确的优先级之分。优先级的计算取决于具体的场景。

计算方式为:计算column-countcolumn-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.masonrydiv.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布局的瀑布流,代码如下,核心内容有:

  1. 首先使用 flex布局 实现了图片的三列布局,即这三列容器的主轴方向设置为行,而每一列中也设置为flex布局并将主轴方向设置为列
  2. 通过不断比较三个列的长短,每次都将图片放入最短的那一列中,实现图片的均匀分布
  3. 在第二步中,由于要动态比较三个列的长短,因此需要知道每一张图片的高度,但本项目中图片是从网页链接中下载得来的,无法获取高度,因此需要先通过监听onload事件 获取图片的高度,这里使用到了promise(代码第32-43行)

问题:

  1. 每一列的宽度设置为30%,因此图片会出现缩放的问题,但第三步只能获取到图片本身的高度,而不能获取图片缩放后的高度,因此会对最短的列的计算产生影响。目前的解决方法: 对所有图片设置一个总体的宽度。
  2. 还有一个useEffect的问题,在第75-77行代码的useEffect方法依赖于数组imgList,如果不使用useMemo将imgList缓存,是会出现页面频繁渲染的情况,答案在该博客中useEffect使用注意事项。因为当useEffect依赖项是数组时,useEffect 使用浅层比较法来比较数值,而每一次js都会给数组创建一个新的地址,因此比较将总是给出 false,因此会一直触发回调。我使用useMemoimgList进行了缓存,这样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博客

超简单直观理解懒加载(Lazyload)

相关推荐
却尘5 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare6 分钟前
浅浅看一下设计模式
前端
Lee川10 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix36 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人40 分钟前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl43 分钟前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空1 小时前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust