2022年了!再来手撕一下前端瀑布流代码吧!

简介: **前言:**知识是学不完的,可是我们为什么还是要不停的去学习呢。原因很简单,因为我们要产生更多的知识,让更多的人学不完!前端技术也是在不停的革新,我们要做那个让别人有学不完的知识的人

1. 什么是瀑布流?


瀑布流,又被称作为瀑布流式布局,是一种比较流行的网站页面布局方式,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。这种布局方式常见于一些图片为主的网站,就比如说一些常见的网站:


40.pngimage.png

由上面的两张图我们可以很容易的明白什么是瀑布流式布局,可以看出,每个图片的高度是不一样的但是它们的宽度是统一的,每个图片之间的间隙也是均匀的。


总结瀑布流式布局的特征如下:

  • 内容框宽度固定,高度不固定。
  • 内容框从左到右排列,一行排满后,其余内容框就会按顺序排在短的一列后。


2. 为什么要用瀑布流布局


1、吸引用户

当用户在浏览瀑布流式布局的时候(这里抛开懒加载),用户会产生一种错觉,就是信息是不停的在更新的,这会激发用户的好奇心,使用户不停的往下滑动。


2.良好视觉体验

采用瀑布流布局的方式可以打破常规网站布局排版,给用户眼前一亮的新鲜感,用户在浏览内容时会感到很有新鲜感,带来良好的视觉体验。


3.更好的适应移动端

由于移动设备屏幕比电脑小,一个屏幕显示的内容不会非常多,因此可能要经常翻页。而在建网站时使用瀑布流布局,用户则只需要进行滚动就能够不断浏览内容。(这一点和懒加载有一点像)


3. 实现原理


我们已经总结出了瀑布流式布局的两大特征:

  • 内容框宽度固定,高度不固定。
  • 内容框从左到右排列,一行排满后,其余内容框就会按顺序排在短的一列后。


那么我们根据这两大特征就不难想出它的实现原理:


首先我们先通过计算一行能够容纳几列元素(因为我们需要在不同的设备上浏览),然后在通过计算比较找出这一列元素中高度之和最小一列,然后将下一行的第一个元素添加至高度之和最小的这一列的下面,然后继续计算所有列中高度之和最小的那一列,然后继续将新元素添加至高度之和最小的那一列后面,直至所有元素添加完毕。


可能饶了这么一大段话还是有小伙伴不明白,我们还是看图说话,这才是最容易理解的;


(1)首先我们放上一行

,它们高度不同,宽度相同:42.png

(2)找出所有元素高度之和的最小的那列在那一列的下面添加新的元素43.png

(3)然后继续计算,获取高度之和最小的那一列,在那一列继续添加新元素44.png


看了这几张图是不是明确了很多,就是这样不停的计算,不停的添加新元素,直至所有元素添加完成。但是我们都知道一些元素是块级元素,一个元素占据一行,这里就会有小伙伴想到了,我们可以利用浮动啊,具体是不是,我们继续实验。


现在明白了瀑布流是布局是怎么实现的了,那么就来写具体的代码来实现一下吧!


4. 实现步骤及方法

1.通过浮动的方式进行布局


首先我们添加一些

元素,并设置为左浮动,分别给这些

元素给予不同的背景方便我们观察,注意,这些元素的宽度是一样的,只是高度不一样。45.png


通过左浮动设置的布局


这就是通过左浮动设置的布局样式,我们可以看到在第二行的时候,第七个盒子浮动是根据第三个盒子来进行定位的。第三行的时候第七个盒子又挡住了第十个盒子的去路,所以这样我们是没法完成布局的


2.通过定位的方式进行布局


前面我们已经知道了浮动布局不能满足我们的需求,所以我们寻求另外一种布局方式,那就是通过定位,给每个盒子设置定位属性后,我们就可以通过动态的设置相应的top,left值来让盒子规规矩矩的为我们排列。


(一)给所有盒子的父元素加上相对定位属性,给所有盒子加上绝对定位属性:

position:relative;
position:absolute;


(二)定位之后我们得到的界面是这样的:

46.png


(三)如何判断一行有多少列:


因为我们的界面宽度是变化的,所以列数也需要跟着变化,动态设置列数的原理大致如下:

1.获取到页面的宽度

2.获取到每个盒子的宽度

3.需要显示的列数 = 页面宽度/盒子宽度


**注意:**一般盒子之间都有一个间隙,所以我们的公式变成如下:

显示的列数(column) = 页面宽度(pageWidth)/(盒子宽度(itemWidth)+间隙(gap))


(四)排列第一行


  • 我们有很多盒子,太多了我们就不好管理,所以我们把所有盒子装进一个数组arr里面,下标从0开始,用i来代表盒子。那么我们如何知道这个盒子是不是应该排列在第一行呢。其实很简单,想一下,我们这么多盒子,全部都要动态布局,动态的设置他的left、top值,那么for循环肯定是避免不了的,当**i(所有盒子的索引)<column(显示的列数)**的时候,盒子应该在第一行。比如说我们一行有5个盒子,那么第五个盒子的索引就是4,由于4<5,所以在第一行,如果索引为6,由于6>5,所以在第二行。代码基本就是这个样子
    if (i < columns) { //确定第一行 items[i].style.top = 0; items[i].style.left = (itemWidth + gap) * i + 'px';
    }
  • 知道了盒子在第1行之后,我们只需要动态的设置盒子的left值就能排列好了。
  • 怎么动态计算每个盒子的left值呢:left=i*(itemWidth+gap),比如说我们排列第二个盒子,它的索引值i就是1,i*(itemWidth+gap)刚好就是第一个盒子和间隙的宽度和,所以距离left值也刚刚好。


说了这么多,画个图理解一下:48.png

(五)获取所有列的高度和


  • 在第一行排列好后,我们就需要排列第二行了,但是第二行我们需要用到top值了,前面原理的时候说了,我们就是根据每一列的最小高度和来进行排列,所以这里我们需要计算出第一行所有列的高度和并保存。
  • 我们定义一个数组arr来保存高度。


我们需要在页面一进入的时候就获取高度,也就是写在onload里面,因为图片的加载特征是:等页面都加载完之后才去请求加载,所以不写在入口函数里可能会出现高度获取不到的情况。


(六)排列第二行


  • 获取到刚刚数组中,高度最小的那一列,将第2行的第1个盒子放置在它的下方;
  • 此时的left值就是高度最小列的offsetLefttop值就是第1行高度最小列的高度(为了布局美观可以加上上下间隙gap)。
  • 记录下高度最小列的索引index,后面计算会用到;
  • 设置完成之后,会发现后面所有的盒子都叠在这个高度最小列的下面,原因就是此时的最小列高度没有改变,应该加上下面盒子 的高度,得出一个新高度。我们需要在最小列后面加了一个盒子之后重新计算所有列的最小高度的列。


看图说话:我们看到第六个盒子下面下面全是其他盒子49.png

(七)重新获取最小高度列的高度


  • 排了第二行的第一个盒子之后,最小高度列已经发生了变化,我们需要重新计算一下
  • 当前高度最小列的高度 = 当前高度最小列的高度 + 间隙 + 下面图片盒子的高度


看图说话:现在知道我们保留index做什么了吧。


50.png

(八)设置为响应式


  • 将整个设置样式的部分封装成一个函数,在onload里面注册一个resize事件,只要页面一发生改变,就触发样式部分的代码。

实时改变pageWidth的宽度,这样瀑布流就会是一个响应式的效果了


(九)添加懒加载效果

懒加载的实现原理可以参考我的上一篇文章,说得很清楚详细。大致原理用一张图来描述

51.png

(十)所有代码(没有包含懒加载代码)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>瀑布流</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        .item {
            float: left;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 30px;
            font-weight: 700;
            color: aliceblue;
            margin-right: 15px;
            margin-bottom: 15px;
            width: 205px;
            position: absolute;
        }
        .item-1 {
            background-color: rgb(206, 169, 169);
            height: 300px;
        }.item-2 {
            background-color: rgb(131, 226, 174);
            height: 150px;
        }.item-3 {
            background-color: rgb(77, 30, 30);
            height: 350px;
        }.item-4 {
            background-color: rgb(49, 62, 134);
            height: 300px;
        }.item-5 {
            background-color: rgb(230, 99, 99);
            height: 200px;
        }.item-6 {
            background-color: rgb(206, 169, 169);
            height: 300px;
        }.item-7 {
            background-color: rgb(124, 126, 145);
            height: 400px;
        }.item-8 {
            background-color: rgb(169, 199, 38);
            height: 230px;
        }.item-9 {
            background-color: rgb(114, 128, 53);
            height: 300px;
        }.item-10 {
            background-color: rgb(48, 54, 18);
            height: 260px;
        }.item-11 {
            background-color: rgb(118, 122, 96);
            height: 230px;
        }.item-12 {
            background-color: rgb(118, 122, 96);
            height: 240px;
        }.item-13 {
            background-color: rgb(118, 122, 96);
            height: 250px;
        }.item-14 {
            background-color: rgb(118, 122, 96);
            height: 270px;
        }.item-15 {
            background-color: rgb(118, 122, 96);
            height: 330px;
        }.item-16 {
            background-color: rgb(118, 122, 96);
            height: 200px;
        }.item-17 {
            background-color: rgb(118, 122, 96);
            height: 100px;
        }.item-18 {
            background-color: rgb(118, 122, 96);
            height: 400px;
        }.item-19 {
            background-color: rgb(118, 122, 96);
            height: 340px;
        }.item-20 {
            background-color: rgb(118, 122, 96);
            height: 350px;
        }.item-21 {
            background-color: rgb(118, 122, 96);
            height: 360px;
        }.item-22 {
            background-color: rgb(118, 122, 96);
            height: 370px;
        }
    </style>
</head>
<body>
    <div id="box">
        <div class="item item-1">1</div>
        <div class="item item-2">2</div>
        <div class="item item-3">3</div>
        <div class="item item-4">4</div>
        <div class="item item-5">5</div>
        <div class="item item-6">6</div>
        <div class="item item-7">7</div>
        <div class="item item-8">8</div>
        <div class="item item-9">9</div>
        <div class="item item-10">10</div>
        <div class="item item-11">11</div>
        <div class="item item-12">12</div>
        <div class="item item-13">13</div>
        <div class="item item-14">14</div>
        <div class="item item-15">15</div>
        <div class="item item-16">16</div>
        <div class="item item-17">17</div>
        <div class="item item-18">18</div>
        <div class="item item-19">19</div>
        <div class="item item-20">20</div>
        <div class="item item-21">21</div>
        <div class="item item-22">22</div>        
    </div>
</body>
<script>
    var items = document.getElementsByClassName('item');
    //定义间隙10像素
    var gap = 10;
    //进页面执行函数
    window.onload = function () {
        waterFall();
    }
    function waterFall() {
        //首先确定列数 = 页面的宽度 / 图片的宽度
        var pageWidth = getClient().width;
        var itemWidth = items[0].offsetWidth;
        var columns = parseInt(pageWidth / (itemWidth + gap));
        var arr = [];//定义一个数组,用来存储元素的高度
        for(var i = 0;i < items.length; i++){
            if(i < columns) {
                //满足这个条件则说明在第一行,文章里面有提到
                items[i].style.top = 0;
                items[i].style.left = (itemWidth + gap) * i + 'px';
                arr.push(items[i].offsetHeight);
            }else {
                //其他行,先找出最小高度列,和索引
                //假设最小高度是第一个元素
                var minHeight = arr[0];
                var index = 0;
                for(var j = 0; j < arr.length; j++){//找出最小高度
                   if(minHeight > arr[j]){
                       minHeight = arr[j];
                       index = j;
                   } 
                }
                //设置下一行的第一个盒子的位置
                //top值就是最小列的高度+gap
                items[i].style.top = arr[index] + gap + 'px';
                items[i].style.left = items[index].offsetLeft + 'px';
                //修改最小列的高度
                //最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 间隙的高度
                arr[index] = arr[index] + items[i].offsetHeight + gap;
            }
        }
    }
    //当页面尺寸发生变化时,触发函数,实现响应式
    window.onresize = function () {
        waterFall();
    }
    // clientWidth 处理兼容性
    function getClient() {
        return {
            width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
            height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
        }
    }
    // scrollTop兼容性处理
    function getScrollTop() {
        return window.pageYOffset || document.documentElement.scrollTop;
    }
</script>
</html>

**注意:**如果要加入懒加载效果的话,可以参考我的之前一篇文章,讲的较为详细,由于这里没有在


里面添加图片,所以,不能实现懒加载效果。这里只设置了div,并且分别给div设置的高度,稍加繁琐。如果有合适的图片资源,可以不用设置。


5. 总结


到这里,我们的瀑布流式布局布局也算讲完了,讲得过程比较啰嗦,也是为了让更多的人能够轻松理解。从整个实现过程来看,我们主要有两个重点:

  • 瀑布流式布局是利用定位来实现的,动态的改变元素的top和left值。
  • 获取最小高度和的列并保存它的索引,以便让下一行的元素知道该放在哪里。


6. 源码

更多源码请移步GitHub:Javascript

相关文章
|
2天前
|
缓存 监控 前端开发
探索前端性能优化:关键策略与代码实例
本文深入探讨前端性能优化的关键策略,结合实际代码示例,帮助开发者提升网页加载速度和用户体验,涵盖资源压缩、懒加载、缓存机制等技术。
|
2月前
|
JavaScript 前端开发 Docker
前端全栈之路Deno篇(二):几行代码打包后接近100M?别慌,带你掌握Deno2.0的安装到项目构建全流程、剖析构建物并了解其好处
在使用 Deno 构建项目时,生成的可执行文件体积较大,通常接近 100 MB,而 Node.js 构建的项目体积则要小得多。这是由于 Deno 包含了完整的 V8 引擎和运行时,使其能够在目标设备上独立运行,无需额外安装依赖。尽管体积较大,但 Deno 提供了更好的安全性和部署便利性。通过裁剪功能、使用压缩工具等方法,可以优化可执行文件的体积。
129 3
前端全栈之路Deno篇(二):几行代码打包后接近100M?别慌,带你掌握Deno2.0的安装到项目构建全流程、剖析构建物并了解其好处
|
15天前
|
Web App开发 缓存 监控
前端性能优化实战:从代码到部署的全面策略
前端性能优化实战:从代码到部署的全面策略
20 1
|
18天前
|
前端开发 JavaScript
前端界的革命:掌握这些新技术,让你的代码简洁到让人惊叹!
前端技术的快速发展带来了许多令人惊叹的新特性。ES6及其后续版本引入了箭头函数、模板字符串等简洁语法,极大减少了代码冗余。React通过虚拟DOM和组件化思想,提高了代码的可维护性和效率。Webpack等构建工具通过模块化和代码分割,优化了应用性能和加载速度。这些新技术正引领前端开发的革命,使代码更加简洁、高效、可维护。
21 2
|
18天前
|
前端开发 JavaScript 测试技术
前端工程师的必修课:如何写出优雅、可维护的代码?
前端工程作为数字世界的门面,编写优雅、可维护的代码至关重要。本文从命名规范、模块化设计、注释与文档、遵循最佳实践四个方面,提供了提升代码质量的方法。通过清晰的命名、合理的模块划分、详细的注释和持续的学习,前端工程师可以写出高效且易于维护的代码,为项目的成功打下坚实基础。
29 2
|
24天前
|
监控 前端开发 JavaScript
前端开发的终极奥义:如何让你的代码既快又美,还不易出错?
【10月更文挑战第31天】前端开发是一个充满挑战与机遇的领域,本文从性能优化、代码美化和错误处理三个方面,探讨了如何提升代码的效率、可读性和健壮性。通过减少DOM操作、懒加载、使用Web Workers等方法提升性能;遵循命名规范、保持一致的缩进与空行、添加注释与文档,让代码更易读;通过输入验证、try-catch捕获异常、日志与监控,增强代码的健壮性。追求代码的“快、美、稳”,是每个前端开发者的目标。
31 3
|
25天前
|
前端开发 JavaScript 开发者
前端开发的终极技巧:如何让你的代码既简洁又高效,还能减少bug?
【10月更文挑战第30天】前端开发充满挑战与创新,如何编写简洁高效且少bug的代码是开发者关注的重点。本文介绍五大技巧:1. 模块化,提高代码复用性;2. 组件化,降低代码耦合度;3. 使用现代框架,提高开发效率;4. 统一代码规范,降低沟通成本;5. 利用工具,优化代码质量。掌握这些技巧,让前端开发更高效。
56 1
|
1月前
|
前端开发 JavaScript 开发者
揭秘前端高手的秘密武器:深度解析递归组件与动态组件的奥妙,让你代码效率翻倍!
【10月更文挑战第23天】在Web开发中,组件化已成为主流。本文深入探讨了递归组件与动态组件的概念、应用及实现方式。递归组件通过在组件内部调用自身,适用于处理层级结构数据,如菜单和树形控件。动态组件则根据数据变化动态切换组件显示,适用于不同业务逻辑下的组件展示。通过示例,展示了这两种组件的实现方法及其在实际开发中的应用价值。
35 1
|
2月前
|
前端开发 JavaScript 开发者
利用代码分割优化前端性能:高级技巧与实践
【10月更文挑战第2天】在现代Web开发中,代码分割是优化前端性能的关键技术,可显著减少页面加载时间。本文详细探讨了代码分割的基本原理及其实现方法,包括自动与手动分割、预加载与预取、动态导入及按需加载CSS等高级技巧,旨在帮助开发者提升Web应用性能,改善用户体验。
|
2月前
|
前端开发 小程序 JavaScript
信前端里的循环显示如何编写代码?
信前端里的循环显示如何编写代码?
71 5