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

瀑布流实现方案

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

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)

相关推荐
fg_411几秒前
无网络安装ionic和运行
前端·npm
理想不理想v2 分钟前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云12 分钟前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:1379712058714 分钟前
web端手机录音
前端
齐 飞19 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹36 分钟前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
GIS程序媛—椰子1 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0012 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端2 小时前
Content Security Policy (CSP)
前端·javascript·面试
木舟10092 小时前
ffmpeg重复回听音频流,时长叠加问题
前端