原生js一步一步实现瀑布流

瀑布流一般的常见的需求有这三种,一是每列固定宽度,这种比较常见,比如花瓣网;另一种是每行固定高度,这种少见一些,典型的例子是百度图片,bing,谷歌图片,还有一种是宽高都不确定,这种需求就比较奇葩了,我们仅讨论第一种瀑布流。

实现这类瀑布流一般的做法是维护一个包含不同列高的数组,先排列第一行,初始化这个数组以后,然后后面的图片(或者是图片的容器)通过绝对定位来排列到最短的列上,并更新数组,随后下一个元素做相同的操作。当窗口尺寸变化怎么办呢?最偷懒的是初始化时就写死容器宽度,当页面太小就出现横向滚动条,这种没啥难度;或者每次修改尺寸就刷新一下页面,可能有些人会嗤之以鼻,还真有大厂这么干,微软的bing搜索的图库就是这么做的;另一种方式是像花瓣网一样,对所有图片进行重新定位,这也是我们接下来要使用的方式。

首先,我们要准备一系列尺寸不一的图片。一般来说我们使用瀑布流的方式,后台会返回给我们一组图片,里面会包含尺寸信息,虽然前端可以计算出来,但是出于性能优化考虑,这样显然是不提倡的。

出于简单考虑,我的数据只包括了图片地址和尺寸信息,并在页面写死了数据而不是通过后台请求,以此为基础,当滚动页面获取新数据的时候,我们回从这些数据中随机拿出几个组成新的数组来模仿后台响应。

准备图片数据

这里是我预先准备的数据:

var imgInfoList = [
    {src: 'http://osf91b2a8.bkt.clouddn.com/1.jpg', width: 673, height: 768},
    {src: 'http://osf91b2a8.bkt.clouddn.com/2.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/3.jpg', width: 440, height: 1863},
    {src: 'http://osf91b2a8.bkt.clouddn.com/4.jpg', width: 440, height: 436},
    {src: 'http://osf91b2a8.bkt.clouddn.com/5.jpg', width: 765, height: 2169},
    {src: 'http://osf91b2a8.bkt.clouddn.com/6.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/7.jpg', width: 236, height: 236},
    {src: 'http://osf91b2a8.bkt.clouddn.com/8.jpg', width: 440, height: 220},
    {src: 'http://osf91b2a8.bkt.clouddn.com/9.jpg', width: 200, height: 200},
    {src: 'http://osf91b2a8.bkt.clouddn.com/10.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/11.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/12.jpg', width: 615, height: 592},
    {src: 'http://osf91b2a8.bkt.clouddn.com/13.jpg', width: 872, height: 2473},
    {src: 'http://osf91b2a8.bkt.clouddn.com/14.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/15.jpg', width: 772, height: 2195},
    {src: 'http://osf91b2a8.bkt.clouddn.com/16.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/17.jpg', width: 510, height: 834},
    {src: 'http://osf91b2a8.bkt.clouddn.com/18.jpg', width: 440, height: 992},
    {src: 'http://osf91b2a8.bkt.clouddn.com/19.jpg', width: 440, height: 449},
    {src: 'http://osf91b2a8.bkt.clouddn.com/20.jpg', width: 320, height: 900},
    {src: 'http://osf91b2a8.bkt.clouddn.com/21.jpg', width: 716, height: 600},
    {src: 'http://osf91b2a8.bkt.clouddn.com/22.jpg', width: 512, height: 1000},
    {src: 'http://osf91b2a8.bkt.clouddn.com/23.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/24.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/25.jpg', width: 750, height: 2133},
    {src: 'http://osf91b2a8.bkt.clouddn.com/26.png', width: 732, height: 903},
    {src: 'http://osf91b2a8.bkt.clouddn.com/27.jpg', width: 1024, height: 768},
    {src: 'http://osf91b2a8.bkt.clouddn.com/28.jpg', width: 700, height: 622},
    {src: 'http://osf91b2a8.bkt.clouddn.com/29.jpg', width: 440, height: 642},
    {src: 'http://osf91b2a8.bkt.clouddn.com/30.jpg', width: 700, height: 525},
    {src: 'http://osf91b2a8.bkt.clouddn.com/31.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/32.jpg', width: 700, height: 525},
    {src: 'http://osf91b2a8.bkt.clouddn.com/33.png', width: 658, height: 494},
    {src: 'http://osf91b2a8.bkt.clouddn.com/34.jpg', width: 700, height: 525},
    {src: 'http://osf91b2a8.bkt.clouddn.com/35.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/36.jpg', width: 700, height: 525},
    {src: 'http://osf91b2a8.bkt.clouddn.com/37.jpg', width: 658, height: 908},
    {src: 'http://osf91b2a8.bkt.clouddn.com/38.jpg', width: 401, height: 329},
    {src: 'http://osf91b2a8.bkt.clouddn.com/39.jpg', width: 700, height: 517},
    {src: 'http://osf91b2a8.bkt.clouddn.com/40.jpg', width: 700, height: 525},
    {src: 'http://osf91b2a8.bkt.clouddn.com/41.jpg', width: 440, height: 635},
    {src: 'http://osf91b2a8.bkt.clouddn.com/42.jpg', width: 700, height: 525},
    {src: 'http://osf91b2a8.bkt.clouddn.com/43.jpg', width: 250, height: 250},
    {src: 'http://osf91b2a8.bkt.clouddn.com/44.jpg', width: 700, height: 525},
    {src: 'http://osf91b2a8.bkt.clouddn.com/45.jpg', width: 542, height: 800},
    {src: 'http://osf91b2a8.bkt.clouddn.com/46.jpg', width: 658, height: 370},
    {src: 'http://osf91b2a8.bkt.clouddn.com/47.jpg', width: 700, height: 525},
    {src: 'http://osf91b2a8.bkt.clouddn.com/48.jpg', width: 700, height: 1133},
    {src: 'http://osf91b2a8.bkt.clouddn.com/49.jpg', width: 200, height: 200},
    {src: 'http://osf91b2a8.bkt.clouddn.com/50.jpg', width: 658, height: 494},
    {src: 'http://osf91b2a8.bkt.clouddn.com/51.jpg', width: 700, height: 1133},
    {src: 'http://osf91b2a8.bkt.clouddn.com/52.jpg', width: 680, height: 1020},
    {src: 'http://osf91b2a8.bkt.clouddn.com/53.png', width: 548, height: 452}
];

构造假数据模拟后台

这是从随机获取元素组成新数组的函数:

/**
 * 模拟后台返回数据
 * @returns {Array}
 */
function moreFakeImgInfo () {
    var createCount = 20;
    var moreImgInfoList = [];
    var imgLength = imgInfoList.length;
    // 从原数据随机获取组成新数组
    for (var i = 0; i < createCount; i++) {
        moreImgInfoList.push(imgInfoList[parseInt(Math.random() * (imgLength - 1))]);
    }
    return moreImgInfoList;
}

初始化第一行

页面载入完成后,先计算出第一行可容纳的列数,然后排练第一行数据,并初始化 包含每列高度的数组:

window.onload = function () {
    cols = parseInt((document.documentElement.clientWidth * 0.9 + boxMarginRight) / boxWidth);
    container.style.width = (boxWidth * cols + boxMarginRight * (cols - 1)) + 'px';

    var boxHeight;
    var fragment = document.createDocumentFragment();  // 创建文档碎片
    for (var i = 0; i < imgInfoList.length; i++) {
        if (i < cols) {  // 只加载第一行

            var oImgBox = document.createElement('div');
            oImgBox.className = 'img-box';
            var oImg = document.createElement('img');
            oImg.src = imgInfoList[i].src;
            oImgBox.appendChild(oImg);

            boxHeight = parseFloat((imgInfoList[i].height * (imageWidth / imgInfoList[i].width) + boxBorder * 2 + boxPadding * 2).toFixed(2));
            oImgBox.style.top = 0;
            oImgBox.style.left = (i === 0 ? 0 : (boxWidth + boxMarginRight) * i) + 'px';
            oImgBox.style.height = boxHeight + 'px';
            fragment.appendChild(oImgBox);

            oImgBox.height = imgInfoList[i].height;
            oImgBox.width = imgInfoList[i].width;

            colHeightList.push(boxHeight);
            colLeftList.push(i === 0 ? '0px' : (boxWidth + boxMarginRight) * i + 'px');
        } else {
            break;
        }
    }
    container.appendChild(fragment);
    fallImages(imgInfoList.slice(cols));  // 排列余下的元素
};

    ps:

  1. toFixed会返回string,使用parseFloat可以重新转化为number
  2. createDocumentFragment可以创建文档碎片,然后一次性将所有元素添加到容器中,可以提高性能
  3. 给oImgBox元素添加height和width属性是为了方便后面重排列
  4. colLeftList数组是为了方便后面的元素排列时使用
  5. 对imgInfoList进行slice操作是为了只针对后面新加进来的元素进行排列

新创建新元素并排列

fallImages函数用于创建新的图片并排列,具体的做法和上面的类似,主要的操作是获取数组中的最小列高并将新元素排到该列后面,然后更新该列在数组中的高度:

/**
 * 创建元素并排列
 * @param imgInfoList
 */
function fallImages (imgInfoList) {
    var fragment = document.createDocumentFragment();
    var minColHeight, minIndex, boxHeight;
    for (var i = 0; i < imgInfoList.length; i++) {
        // 创建img-box
        var oImgBox = document.createElement('div');
        oImgBox.className = 'img-box';
        var oImg = document.createElement('img');
        oImg.src = imgInfoList[i].src;
        oImgBox.appendChild(oImg);

        boxHeight = parseFloat((imgInfoList[i].height * (imageWidth / imgInfoList[i].width) + boxBorder * 2 + boxPadding * 2).toFixed(2));
        // 获取最短列高及对应的下标
        minColHeight = Math.min.apply(Math, colHeightList);
        minIndex = colHeightList.indexOf(minColHeight);
        oImgBox.style.top = (minColHeight + boxMarginBottom) + 'px';
        oImgBox.style.left = colLeftList[minIndex];
        oImgBox.style.height = boxHeight + 'px';

        fragment.appendChild(oImgBox);

        oImgBox.height = imgInfoList[i].height;
        oImgBox.width = imgInfoList[i].width;

        // 更新列高
        colHeightList[minIndex] += (boxMarginBottom + boxHeight);
    }

    container.appendChild(fragment);

    container.style.height = Math.max.apply(Math, colHeightList) + 'px';
}

ps:Math.min和Math.max方法并不接受数组参数,所以我们要求数组的最值需要使用Function.prototype.apply方法将其展开。

滚动页面“刷新”数据

接下来,需要添加滚动到底部获取“新数据”的功能:

var end = 0;  // 没有更多数据标识
var time = 0;  // 加载次数标识
/**
 * 下拉展示“新数据”
 */
window.onscroll = function () {
    if (end) {  // 已展示完
        return;
    }
    var scrolledHeight = document.documentElement.scrollTop || document.body.scrollTop;
    if (scrolledHeight + document.documentElement.clientHeight > Math.min.apply(null, colHeightList)) {
        console.log('create more');
        // TODO 这里需要从后台获取数据
        var newImgInfoList = moreFakeImgInfo();
        fallImages(newImgInfoList);
        if (time++ > 3) {
            end = 1;
        }
    }
};

ps:这里的end、time、moreFakeImgInfo都是为了模仿后台响应的假数据

重新排列

到此为止,我们的瀑布流已经实现了,不过还有一些细节需要完善,比如说修改窗口/容器尺寸,如何对所有图片进行重新排列,如果大家看过上面的话,这一步就非常简单了,我们只需要获取页面中所有的img-box,然后对他们进行上面的操作,唯一的不同是,我们不用创建新元素,只需要对定位修改:

/**
 * 行宽改变重新排列
 */
window.onresize = function () {
    oldCols = cols;
    cols = parseInt((document.documentElement.clientWidth * 0.9 + boxMarginRight) / boxWidth);
    container.style.width = (boxWidth * cols + boxMarginRight * (cols - 1)) + 'px';
    // 最大可容纳列数改变
    if (oldCols !== cols) {
        var imageBoxList = container.getElementsByClassName('img-box');
        colHeightList = [];
        colLeftList = [];
        var minColHeight, minIndex;
        for (var i = 0; i < imageBoxList.length; i++) {
            if (i < cols) {  // 初始化第一行

                imageBoxList[i].style.top = 0;
                imageBoxList[i].style.left = (i === 0 ? 0 : (boxWidth + boxMarginRight) * i) + 'px';

                colHeightList.push(parseFloat((imageBoxList[i].height * (imageWidth / imageBoxList[i].width) + boxBorder * 2 + boxPadding * 2).toFixed(2)));
                colLeftList.push(i === 0 ? '0px' : (boxWidth + boxMarginRight) * i + 'px');

            } else {  // 对后面的元素重新排列
                minColHeight = Math.min.apply(Math, colHeightList);
                minIndex = colHeightList.indexOf(minColHeight);
                imageBoxList[i].style.top = (minColHeight + boxMarginBottom) + 'px';
                imageBoxList[i].style.left = colLeftList[minIndex];

                colHeightList[minIndex] += parseFloat(parseFloat((boxMarginBottom + imageBoxList[i].height * (imageWidth / imageBoxList[i].width) + boxBorder * 2 + boxPadding * 2).toFixed(2)));
            }
        }
        container.style.height = Math.max.apply(Math, colHeightList) + 'px';
    }
};

 

是不是很简单,后面如果有时间我会对另外两种瀑布流的实现方式也进行讲解。

完整代码戳这里

在线demo

转载于:https://my.oschina.net/codingDog/blog/1083336

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、下4载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、下4载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、 4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值