首页 > 其他分享 >浅析瀑布流布局原理

浅析瀑布流布局原理

时间:2023-01-30 19:57:24浏览次数:55  
标签:index const img 瀑布 columnsHeightArr 原理 图片 浅析 加载

前言

上一文讲到了图片, 这里我们就讲一个常用的图片场景: 瀑布流, 他的实现和优化

什么瀑布流

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。最早采用此布局的网站是 Pinterest,逐渐在国内流行开来。国内大多数清新站基本为这类风格。

更直观的展示如下图所示:

image

优缺点

优点:

  1. 外表美观,更有艺术性。
  2. 用户浏览时的观赏和思维不容易被打断,留存更容易。

缺点:

  1. 用户无法了解内容总长度,对内容没有宏观掌控。
  2. 用户无法了解现在所处的具体位置,不知道离终点还有多远。
  3. 回溯时不容易定位到之前看到的内容。
  4. 容易造成页面加载的负荷。
  5. 容易造成用户浏览的疲劳,没有短暂的休息时间。

实现方案

纯 CSS 实现

这里要介绍 CSS 属性: column

现在常用的 column 属性有这些

  • column-count
    将一个元素的内容分成指定数量的列。涉疫设置为具体数字, 或者auto
  • column-fill
    控制元素的内容在分成多列时如何平衡。有 autobalance 两个值, 分别表示自动填充和水平分配
  • column-gap
    两列间的距离, 可设置: px, 百分比, rem
  • column-rule
    列布局中, 分割线的设置, 可以设置风格、宽度、颜色(和 border 相同的参数)
  • column-span
    列布局中横向的占用元素, 具体作用可查看此处
  • column-width
    每一列的最小宽度, 如果外部容易宽度特别小, 则会失效

另一个 CSS 属性 columns

他是用来设置元素的列宽和列数属性, 是由 column-widthcolumn-count 两属性合并而来的简写属性

关于兼容性, 目前来看兼容性还可以, 可适应大多数情况
image

相关 CSS 属性的详情, 可点击此处

这里再提供一个我使用 columns 的 demo, 预览图:
image

在线查看地址: https://grewer.github.io/JsDemo/waterfallLayout/css.html
(可通过缩放浏览器大小来查看他的效果)

js 实现

通过 js 我们也可以实现瀑布流, 核心思路是:

  1. 我们固定每张图片的宽度, 如 200px, 这样我们就能计算出当前屏幕中一行是 N 张图片
  2. 通过步骤 1 中计算出来的数量, 我们创建高度数组, 用来存放每一列的高度
  3. 遍历图片, 找到步骤 2 中高度数组的最小值(默认为 0), 这样我们在对应序号插入图片, 同时高度数组中, 也更新他的高度

具体实现

第 1,2 步中的初始化和高度数组

    const width = 200; // 默认设置为 200px 的宽度

    const columns = Math.floor(window.innerWidth / 200) // 计算出当前页面的列
    
    const columnsHeightArr = []
    
    // mock 图片地址
    const urls = new Array(10).fill(0).map((it, index) => {
        return `https://grewer.github.io/JsDemo/waterfallLayout/imgs/img_${index}.png`
    })

在获取图片高度时, 我们需要注意的是, 图片未加载完成时, 是无法获取到图片高度的

所以这里我们准备先加载图片, 在图片全部加载完成后, 再进行高度的计算

    let flag = 0
    
    const getImg = (url) => {
        let img = new Image();
        img.src = url;
        const imgCallback = () => {
            flag++;
            if (flag === urls.length) {
                handler();
            }
        }
        
        // 是否缓存
        if (img.complete) {
            imgCallback()
        } else {
            img.onload = imgCallback
        }
    }

在图片全部加载完毕之后, 进入 handler 函数, 正式操作图片:

    // 添加图片到容器中, 通过 position: absolute 的方案来实现
    const appendImages = (url, position, top) => {
        const img = document.createElement('img');
        img.src = url;
        img.style.left = (position * width) + 'px';
        img.style.top = top + 'px';
        container.appendChild(img)
        return img
    }
    
    // 获取高度数组中的最小高度
    const getMin = (arr) => {
        let minHeight = arr[0];
        let index = 0
        for (let i = 1; i < arr.length; i++) {
            if (arr[i] < minHeight) {
                minHeight = arr[i]
                index = i
            }
        }
        return {index, minHeight}
    }
    const insertImages = () => {
        for (let i = 0; i < urls.length; i++) {
            // 判断是否小于一行, 这样我们就能直接加入了
            if (columnsHeightArr.length < columns) {
                const img = appendImages(urls[i], i, 0)
                columnsHeightArr[i] = img.offsetHeight;
            } else {
                const {index, minHeight} = getMin(columnsHeightArr)
                const img = appendImages(urls[i], index, minHeight);
                columnsHeightArr[index] = columnsHeightArr[index] + img.offsetHeight;
            }
        }
    }
    

最后的实现效果:

image

因为图片是一口气加载完, 再 append 到页面中的, 所以页面不会显示有图片的加载流程

在线 demo : 点击查看

优化图片加载

在上一方案中, 我们是先加载完所有图片, 再将图片放到容器中
这样做的缺点就是: 页面加载缓慢, 并且图片也没有加载流程, 页面出现的有点突兀

假设:
如果我们一开始不加载所有的图片,而是加载 N 张, 分批次加载, 这样会不会好很多呢?

尝试解决:


    const insertImages = () => {
        if (!urls || urls.length <= 0) {
            console.log('done 全部加载完毕')
            return
        }
        
        // 一行有 N 张图片的话, 我们就以 N 作为一个批次加载的图片数量
        const arr = urls.splice(0, columns)
        
        let flag = arr.length;
        arr.forEach(async (item, i) => {
            if (columnsHeightArr.length < columns) {
                const img = await loadImage(item);
                appendImages(img, i, 0)
                columnsHeightArr[i] = img.offsetHeight;
            } else {
                const img = await loadImage(item);
                const {index, minHeight} = getMin(columnsHeightArr)
                appendImages(img, index, minHeight);
                columnsHeightArr[index] = columnsHeightArr[index] + img.offsetHeight;
            }
            // 做出检查, 所有图片是否都已经加载完毕
            flag--;
            
            if (flag <= 0) {
                // 调用自身, 直到图片全部加载完毕
                insertImages()
            }
        })
        
    }

效果展示: image

在线 demo : 点击查看

从目前的效果来看, 虽然加载看上去好一点点, 但还是不尽如人意

从根本原因上看, 是因为我们要获取图片的长宽来计算布局, 所以要等待加载

那么我们提出设想, 在获取到图片路径的同时, 能获取尺寸比例, 那么加载速度问题也就解决了

图片源头优化

假设我们的初始数据是这样:

[
    {
        "url": "https://grewer.github.io/JsDemo/waterfallLayout/imgs/img_0.png",
        "width": 1776,
        "height": 1184
    },
    //...省略
]

或者是这样:

[
    "https://grewer.github.io/JsDemo/waterfallLayout/imgs/img_0.png?w=1776&h=1184",
    // ... 省略
]

如果是长宽比例也是没问题的:


[
    "https://grewer.github.io/JsDemo/waterfallLayout/imgs/img_0.png?scale=1.5",
    // scale = height/width
    // ... 省略
]

这样的数据, 需要一定的支持, 比如在上传时, 前端将图片的尺寸一起上传, 或者后端计算尺寸存入数据库等等

我们加载这样的数据, 就只要关心高度即可, 不用再担心图片是什么时候加载完成的

    const insertImages = () => {
        data.forEach( (item, i) => {
            const img = document.createElement('img');
            img.src = item.url;
            const height = ((item.height/item.width) * (200-gap)) + gap;
            if (columnsHeightArr.length < columns) {
                appendImages(img, i, 0, height)
                columnsHeightArr[i] = height;
            } else {
                const {index, minHeight} = getMin(columnsHeightArr)
                appendImages(img, index, minHeight, height);
                columnsHeightArr[index] = columnsHeightArr[index] + height;
            }
        })
    }
    

效果展示:
image

在线 demo : 点击查看

从这里我们摆脱了图片的加载速度限制, 可以说是优化成功了(当然这种方案是需要一定支持)

长列表的优化

瀑布流和正常的列表一样, 也会存在一个列表过长时的渲染问题

之前我在一篇文章里介绍了长列表的优化(点击此处查看), 里面介绍了几种方案

虚拟列表是一种很好的优化方案, 但是他和瀑布流布局并不好兼容, 因为虚拟列表需要的是每一行的高度(即使高度不一样)

但是瀑布流是按照列来布局的, 没有完整行的概念, 这就造成了不兼容的冲突

目前来看 CSS 属性 content-visibility 可支持瀑布流的优化, 但是具体效果还需要在业务中实战出来

总结

本文介绍了瀑布流的概念, 以及两种不同的实现方案, 并对 JS 方案作出了一定的优化解决
CSS 方案比较方便, 但是兼容性略差
JS 方案是一种普适性较强的方案, 针对图片的加载性能和布局来说, 需要对图片的比例作出提前的计算

这里留下本文所有 demo 的源码地址供大家参考: 点此查看

引用

标签:index,const,img,瀑布,columnsHeightArr,原理,图片,浅析,加载
From: https://www.cnblogs.com/Grewer/p/17077106.html

相关文章

  • DAPP 和 APP 有哪些区别?多链跨链 NFT 铸造dapp 系统开发技术原理分析
    DAPP智能合约开发流程是怎样?基本流程Asch有三种网络类型,分别是localnet,testnet,mainnet,后两种是发布到线上的,可以通过公网访问。第一种localnet是运行在本地的,只有一个......
  • HCIA 交换机原理与ARP协议
    一、概述交换机(Switch),在网络通讯中属于非常重要而基础的设备,常见资料往往侧重于介绍某一点或对某个名词的解释,本文试图从用户使用的角度来理解交换和交换机,不过仅限于HCIA......
  • 邮件-原理(SMTP/POP3 或 SMTP/IMAP)
    什么是SMTP?SMTP基于TCP/IP。SMTP是电子邮件发送的行业标准协议。所以使用SMTP协议可以向SMTP服务器发送、中继或转发邮件。什么是SMTP服务器?SMTP服务器用于发送邮件......
  • jQuery_1_封装原理
    jQuery是使用JavaScript 编写的函数库,用于简化了JavaScript编程。 问题:在一个页面中,无论是引入外部的js代码,还是内部分开写的js代码块,在此页面解析执行的时候,......
  • 会话技术Session快速入门与Session原理分析
    ##Session:1.概念:服务器端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中.HttpSession2.快速入门:1.获取HttpSession对象:......
  • 《RPC实战与核心原理》学习笔记Day12
    15|熔断限流:业务如何实现自我保护?为什么我们的服务需要自我保护?RPC是解决分布式系统通信问题的一大利器,它会面临高并发的场景,这意味着我们提供服务的每个服务节点都有......
  • 网络空间情报战-原理篇
    情报的概述、定义、分类情报=军事+网安+数据情报的定义情报是一种有用信息,通常需要被搜集、处理、分析,辅助决策者做出正确行动。基于攻防两方,情报可分为我方......
  • 云原生干货|一文读懂函数编程及其工作原理
    啥是函数编程我先用通俗的大白话给大家解释一下函数(Functions,FunctionasaService,FaaS)的几个要点,这样看后面示例时才不会容易懵。函数就是在云平台体系内运行的、与......
  • 随堂笔记1-spring底层原理解析.md
    userServce->无参构造方法->普通对象->依赖注入->初始化前(postStruct)->初始化(initializationBean)->初始化后(aop)->代理对象->bean通过无参构造方法创建普通bean如......
  • 详解高并发中的限流原理和实现
    电商高并发场景下,我们经常会使用一些常用方法,去应对流量高峰,比如限流、熔断、降级,今天我们聊聊限流。什么是限流呢?限流是限制到达系统的并发请求数量,保证系统能够正常响应......