原生JavaScript实现瀑布流布局

本文介绍如何使用原生JavaScript实现瀑布流布局,并详细解释了实现过程中的关键步骤,包括计算图片容器的位置、动态调整布局及加载更多数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    常见的网页布局模式有静态,自适应,瀑布流,响应式等模式,本篇将重点讲述通过原生JavaScript代码实现瀑布流布局的过程
    首先看一下最终的效果图:
    首先是H5代码,第一步是整个瀑布流体
 <div id="main">
        <div class="imgBox">
            <img src="../IMG/1.jpg">
        </div>
         <div class="imgBox">
            <img src="../IMG/2.jpg">
        </div>
       <!--先按相同格式放满一张页面,这里不再重复-->
 </div>
    瀑布流的原理是通过计算将内部每一个数据块加载到它应该显示的位置,每一个数据块宽度相同但高度不同,因此父级元素(id为main)的position应设为relative,每一个图片块应设为absolute,同时我们还要给每一个图片块添加边框,阴影,圆角等属性,然后将图片大小设为一个统一的数值,每个imgBox单独占一行(图片宽度固定,右边多出的部分由空白填充),效果如下:

未添加浮动效果图

    接下来给每个imgBox添加左浮动,由于imgBox没有固定宽度,但是内部的img标签有宽度,左浮动会尽量向左靠,因此imgBox右边多余的空白会被压缩,效果如下:

添加左浮动后效果

*{
    margin: 0px;
    padding: 0px;
}

#main{
    position: relative;
    border: 1px red solid;
}

.imgBox{
    padding: 10px;
    margin: 15px;
    border: 1px black solid;
    box-shadow: 0 0 5px black;
    border-radius: 5px;
    float: left;
}
.imgBox img{
    width: 219px;
    height: auto;
    display: block;
}
    接下来是js部分,由于左浮动的影响,imgBox排成一行,但实际上,由于class为mian的元素没有固定大小,因此当浏览器不是全屏状态时,每一行有多少列imgBox并不固定,因此,我们需要给main的元素设置宽度:

column不固定

    首先我们需要一个用于取出位于某个父元素中的所有class相同的子元素的函数,然后计算列数(浏览器可视区宽度/imgBox宽度),最终得到mian对应标签的宽度
     接下来,我们需要将后面的imgBox紧贴在第一行imgBox的下面,依次向下排列,去除中间的空白区域,思路是每次将新添加的imgBox添加到最“矮”的那列imgBox的底部,因此我们需要一个存储列高度的数组,然后每次取出数组中最矮的那一列,然后添加新的imgBox,再更新高度数组, 具体代码如下:
//通过class名获取parent元素的后代元素
function getBoxByClass(parent,className){
    var allElem=parent.getElementsByTagName("*");//获取父元素的所有子元素
    var boxArr=new Array();//创建用于存储class名为参数className的元素
    for(var element in allElem) {
        // statements
        if(allElem[element].className==className){//遍历parent的所有子元素,一旦发现有类名为className的元素就加入数组boxArr
            boxArr.push(allElem[element]);
        }
    };
    return boxArr;
}
function waterFall(){
    var mainDiv=document.getElementById("main");
    var boxes=getBoxByClass(mainDiv,"imgBox");
    //设置main的宽度
    var boxWidth=boxes[0].offsetWidth;//获取imgBox的宽度,由于所有imgBox等宽,所以选取第一个的宽度
    var cols=Math.floor(document.documentElement.clientWidth/boxWidth);//当前浏览器可视区的宽度除以盒子的宽度并向下取整就能得到当前行数
    var mainWidth=cols*boxWidth+cols*30;//加上margin边距
    mainDiv.style.cssText="width:"+mainWidth+"px;margin:0 auto;"
    var colHeights=new Array();//所有列的高度数组
    var minHeight;
    var minIndex;
    for(var i=0;i<boxes.length;i++){

        //如果是第一行的imgBox,就对列的高度数组进行初始化
        if(i<cols){
            colHeights.push(boxes[i].offsetHeight+30);//30是margin的距离,第一行的marginBottom加上第二行的marginTop等于30
        }else{
            //如果不是第一行的imgBox,就将它放到最"矮"那一列的后面
            minHeight=Math.min.apply(null,colHeights);//获取数组最小值
            minIndex=colHeights.indexOf(minHeight);//获取数组最小值的索引
            boxes[i].style.cssText="position: absolute;top:"+(minHeight)+"px;left:"+(boxes[minIndex].offsetLeft-boxes[i].offsetLeft)+"px";
            colHeights[minIndex]=minHeight+boxes[i].offsetHeight+30;//更新列高数组
        }
    }
}

效果图:
效果

接下来,是模拟后台添加数据,后台通常传输json数据,我们需要对其进行一定地处理再显示出来

function simulateGetDate() {
    //模拟后台返回的带有图片地址的json字符串
    var data = { "src": ["../IMG/" + Math.ceil(Math.random() * 10) + ".jpg", "../IMG/" + Math.ceil(Math.random() * 10) + ".jpg", "../IMG/" + Math.ceil(Math.random() * 10) + ".jpg", "../IMG/" + Math.ceil(Math.random() * 10) + ".jpg", "../IMG/" + Math.ceil(Math.random() * 10) + ".jpg", "../IMG/" + Math.ceil(Math.random() * 10) + ".jpg"], "msg": "success" };
    return data;
}
    最后我们还需要添加一个onscroll事件监听,监听滚动条的移动,当最后一个定位的imgBox距离父元素上边距的距离lastLoadOffsetTop**小于**滚动条距离上端的距离scrollTop与整个浏览器可视区的高度之和时,加载新的数据块,如下图:
    因此,最终代码为:
window.onload = function() {
    waterFall();
    window.onscroll = function() {
        var mainDiv = document.getElementById("main");
        var boxes = getBoxByClass(mainDiv, "imgBox"); //获取所有的imgBox
        //获取最后一次加载的imgBox的offseTop
        var lastLoadOffsetTop = boxes[boxes.length - 1].offsetTop;
        var height = (document.body.clientHeight || document.documentElement.clientHeight) + (document.body.scrollTop || document.documentElement.scrollTop);
        var newData;
        var newImgBox;
        var newImg;
       // alert(lastLoadOffsetTop+"  "+height);
        if (lastLoadOffsetTop < height) {

            newData = simulateGetDate();
            for (var i = 0; i < newData.src.length; i++) {
                //创建新的imgBox
                newImgBox = document.createElement("div");
                newImgBox.className = "imgBox";
                //创建新的img标签
                newImg = document.createElement("img");
                newImg.src = newData.src[i];
                //将imgBox和img分别连接到对应位置
                newImgBox.appendChild(newImg);
                mainDiv.appendChild(newImgBox);
            }

        }
            waterFall();
    }

}

function waterFall() {
    var mainDiv = document.getElementById("main");
    var boxes = getBoxByClass(mainDiv, "imgBox");
    //设置main的宽度
    var boxWidth = boxes[0].offsetWidth; //获取imgBox的宽度,由于所有imgBox等宽,所以选取第一个的宽度
    var cols = Math.floor(document.documentElement.clientWidth / boxWidth); //当前浏览器可视区的宽度除以盒子的宽度并向下取整就能得到当前行数
    var mainWidth = cols * boxWidth + cols * 30; //加上margin边距
    mainDiv.style.cssText = "width:" + mainWidth + "px;margin:0 auto;"
    var colHeights = new Array(); //所有列的高度数组
    var minHeight;
    var minIndex;
    for (var i = 0; i < boxes.length; i++) {
        //如果是第一行的imgBox,就对列的高度数组进行初始化
        if (i < cols) {
            colHeights.push(boxes[i].offsetHeight + 30); //30是margin的距离,第一行的marginBottom加上第二行的marginTop等于30
        } else {
            //if(i=cols){alert(colHeights[0]+"   "+colHeights[1])}
            //如果不是第一行的imgBox,就将它放到最"矮"那一列的后面
            minHeight = Math.min.apply(null, colHeights); //获取数组最小值
            minIndex = colHeights.indexOf(minHeight); //获取数组最小值的索引
            boxes[i].style.cssText = "position: absolute;top:" + minHeight + "px;left:" + (boxes[minIndex].offsetLeft - 15) + "px";
            colHeights[minIndex] = minHeight + boxes[i].offsetHeight + 30; //更新列高数组
        }
    }
}
//通过class名获取parent元素的后代元素
function getBoxByClass(parent, className) {
    var allElem = parent.getElementsByTagName("*"); //获取父元素的所有子元素
    var boxArr = new Array(); //创建用于存储class名为参数className的元素
    for (var element in allElem) {
        // statements
        if (allElem[element].className == className) { //遍历pare nt的所有子元素,一旦发现有类名为className的元素就加入数组boxArr
            boxArr.push(allElem[element]);
        }
    };
    return boxArr;
}

function simulateGetDate() {
    //模拟后台返回的带有图片地址的json字符串
    var data = { "src": ["../IMG/" + Math.ceil(Math.random() * 10) + ".jpg", "../IMG/" + Math.ceil(Math.random() * 10) + ".jpg", "../IMG/" + Math.ceil(Math.random() * 10) + ".jpg", "../IMG/" + Math.ceil(Math.random() * 10) + ".jpg", "../IMG/" + Math.ceil(Math.random() * 10) + ".jpg", "../IMG/" + Math.ceil(Math.random() * 10) + ".jpg"], "msg": "success" };
    return data;
}
   效果图:

效果图

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值