美文网首页微信小程序小程序微信小程序(应用号)
微信小程序中实现瀑布流布局和无限加载

微信小程序中实现瀑布流布局和无限加载

作者: 一斤代码 | 来源:发表于2016-11-25 18:41 被阅读11847次

瀑布流布局是一种比较流行的页面布局方式,最典型的就是Pinterest.com,每个卡片的高度不都一样,形成一种参差不齐的美感。

在HTML5中,我们可以找到很多基于jQuery之类实现的瀑布流布局插件,轻松做出这样的布局形式。在微信小程序中,我们也可以做出这样的效果,不过由于小程序框架的一些特性,在实现思路上还是有一些差别的。

今天我们就来看一下如何在小程序中去实现这种瀑布流布局:

小程序瀑布流布局

我们要实现的是一个固定2列的布局,然后将图片数据动态加载进这两列中(而加载进来的图片,会根据图片实际的尺寸,来决定到底是放在左列还是右列中)。

/* 单个图片容器的样式 */
.img_item {
  width: 48%;
  margin: 1%;
  display: inline-block;
  vertical-align: top;
}

我们知道,在HTML中,我们要动态加载图片的话,通常会使用new Image()创建一个图片对象,然后通过它来动态加载一个url指向的图片,并获取图片的实际尺寸等信息。而在小程序框架中,并没有提供相应的JS对象来处理图片加载。其实我们可以借助wxml中的<image>组件来完成这样的功能,虽然有点绕,但还是能满足我们的功能要求的。

<!-- 在页面上放一个隐藏区域,并用image组件去加载一个或多个图片资源 -->
<view style="display:none">
  <image wx:for="{{images}}" wx:key="id" id="{{item.id}}" src="{{item.pic}}" bindload="onImageLoad"></image>
</view>

我们可以在Page中通过数据绑定,来传递要加载的图片信息到wxml中,让<image>组件去加载图片资源,然后当图片加载完成的时候,通过bindload指定的事件处理函数来做进一步处理。

我们来看一下Page文件中定义的onImageLoad函数。在其中,我们可以从传入的事件对象e上,获取到<image>组件的丰富信息,包括通过它加载进来的图片的实际大小。然后我们将图片按照页面上实际需要显示的尺寸,计算出同比例缩放后的尺寸。接着,我们可以根据左右两列目前累积的内容高度,来决定把当前加载进来的图片放到哪一边。

let col1H = 0;
let col2H = 0;

Page({

    data: {
        scrollH: 0,
        imgWidth: 0,
        loadingCount: 0,
        images: [],
        col1: [],
        col2: []
    },

    onLoad: function () {
        wx.getSystemInfo({
            success: (res) => {
                let ww = res.windowWidth;
                let wh = res.windowHeight;
                let imgWidth = ww * 0.48;
                let scrollH = wh;

                this.setData({
                    scrollH: scrollH,
                    imgWidth: imgWidth
                });

                //加载首组图片
                this.loadImages();
            }
        })
    },

    onImageLoad: function (e) {
        let imageId = e.currentTarget.id;
        let oImgW = e.detail.width;         //图片原始宽度
        let oImgH = e.detail.height;        //图片原始高度
        let imgWidth = this.data.imgWidth;  //图片设置的宽度
        let scale = imgWidth / oImgW;        //比例计算
        let imgHeight = oImgH * scale;      //自适应高度

        let images = this.data.images;
        let imageObj = null;

        for (let i = 0; i < images.length; i++) {
            let img = images[i];
            if (img.id === imageId) {
                imageObj = img;
                break;
            }
        }

        imageObj.height = imgHeight;

        let loadingCount = this.data.loadingCount - 1;
        let col1 = this.data.col1;
        let col2 = this.data.col2;

        //判断当前图片添加到左列还是右列
        if (col1H <= col2H) {
            col1H += imgHeight;
            col1.push(imageObj);
        } else {
            col2H += imgHeight;
            col2.push(imageObj);
        }

        let data = {
            loadingCount: loadingCount,
            col1: col1,
            col2: col2
        };

        //当前这组图片已加载完毕,则清空图片临时加载区域的内容
        if (!loadingCount) {
            data.images = [];
        }

        this.setData(data);
    },

    loadImages: function () {
        let images = [
            { pic: "../../images/1.png", height: 0 },
            { pic: "../../images/2.png", height: 0 },
            { pic: "../../images/3.png", height: 0 },
            { pic: "../../images/4.png", height: 0 },
            { pic: "../../images/5.png", height: 0 },
            { pic: "../../images/6.png", height: 0 },
            { pic: "../../images/7.png", height: 0 },
            { pic: "../../images/8.png", height: 0 },
            { pic: "../../images/9.png", height: 0 },
            { pic: "../../images/10.png", height: 0 },
            { pic: "../../images/11.png", height: 0 },
            { pic: "../../images/12.png", height: 0 },
            { pic: "../../images/13.png", height: 0 },
            { pic: "../../images/14.png", height: 0 }
        ];

        let baseId = "img-" + (+new Date());

        for (let i = 0; i < images.length; i++) {
            images[i].id = baseId + "-" + i;
        }

        this.setData({
            loadingCount: images.length,
            images: images
        });
    }

})

这里是显示在两列图片的wxml代码,我们可以看到在<scroll-view>组件上,我们通过使用bindscrolltolower设置了事件监听函数,当滚动到底部的时候,会触发loadImages去再加载下一组的图片数据,这样就形成了无限的加载:

<scroll-view scroll-y="true" style="height:{{scrollH}}px" bindscrolltolower="loadImages">
  <view style="width:100%">
    <view class="img_item">
      <view wx:for="{{col1}}" wx:key="id">
        <image src="{{item.pic}}" style="width:100%;height:{{item.height}}px"></image>
      </view>
    </view>
    <view class="img_item">
      <view wx:for="{{col2}}" wx:key="id">
        <image src="{{item.pic}}" style="width:100%;height:{{item.height}}px"></image>
      </view>
    </view>
  </view>
</scroll-view>

好了,挺简单的一个例子,如果你有更好的方法,不吝分享一下哦。

完整代码可以在我的Github下载:https://github.com/zarknight/wx-falls-layout

相关文章

网友评论

  • 9765d9f0b488:为什么我的瀑布流图片只有一张的时候 就显示在右面了 怎么解决 求楼主指教
  • seporga:如果用户不但触发bindscrolltolower事件,就会导致只有一边的瀑布流,有什么解决方案吗?
  • vonson:作者你好 想问一下这个瀑布流写法的分页是不是也是一直push到一个数组里呢?也就是loadImages方法中的images里面?这样到十多页的时候整个数组数据量太大最后加载越来越慢的这种情况,应该怎么解决呢?谢谢
    f331f5016efe:@vonson 上滑再重新load,保持一个fixed length window
    vonson:@一斤代码 但是这个时候我往上滑,就会出现前面几页看不到的情况啊,这也很尴尬。。。看了哈花瓣的小程序到十五页差不多也就崩了,不晓得是不是也是这个问题。。。现在解决着脑壳痛,楼主空了帮忙研究一下啊,谢谢
    一斤代码:这个demo比较简单,只是一个示意性的功能。如果考虑你说的问题的话,可以在加载到一定页数时去清楚掉前面几页的内容,控制数组保持在一定的数据量内。
  • 2c20d3a5596d:你好,按照你这样写的,去加载数据的时候加载第六页(每一页10个数据)时小程序出现了意外退出,每次都是这样。
    2c20d3a5596d:@vonson 你好,我是在loadingCount 这里写的,博主已经写过了,data.images=[] 我用的是另外自定义的一个数组渲染的,在这里设置了allList为空
    vonson:你好 想问一下你分页是不是也是一直push到一个数组里呢?也就是方法中的images里面的么?这样到十多页的时候整个数组数据量太大最后加载越来越慢的这种情况,你是怎么解决的呢?谢谢
    2c20d3a5596d:我是把scroll-view改为了view,之所以这样做是因为之前设置的回到顶部在使用scroll-view的时候由于有一个view(display:none)导致这个回到顶部无法出现。然后改为view后使用onReachBottom上拉触底时进行再次加载数据一切正常,但是在真机上测试的时候发现在第六页的时候出现了小程序异常退出,并且不是偶然出现的。应该是出现了内存泄露,所以我在onImageLoad下面先进行了
    let data = {
    col1: [],
    col2: []
    };
    这样问题就解决了。请大神有空的时候指教一下
  • 编程界的小学生:let imageObj = null;

    for (let i = 0; i < images.length; i++) {
    let img = images[i];
    if (img.id === imageId) {
    imageObj = img;
    break;
    }
    }

    imageObj.height = imgHeight;

    若imageObj=null,那不是报错了吗
    编程界的小学生:@一斤代码 我们能否加个好友,然后贴图给您看下
    编程界的小学生:@一斤代码 我现在发生了,不知道该怎么解决。能说下嘛
    TypeError: Cannot set property 'height' of null
    一斤代码:虽然单独看你这段代码,是会的。但是你仔细体会一下这整个onImageLoad函数,就会发现这是不会发生的。
  • 1e2a6ad621ce:大神知道为什么在使用scroll-view 组件下纯css不能实现瀑布流布局吗
  • 62193de55d4d:你好 如果我想获取view的高度应该怎么改
    62193de55d4d:@一斤代码 请大神指教
    62193de55d4d:@一斤代码 你可以帮我大概改改吗 我刚看了半天没改对
    一斤代码:你要获取view的高度,可以用wx.createSelectorQuery()极其配套的相关接口获取你要的元素高度。https://mp.weixin.qq.com/debug/wxadoc/dev/api/wxml-nodes-info.html#nodesreffieldsfieldscallback
  • a4e04187723c:在真机上有时候会出现只有一排有图片,另一边没图,请问这是什么原因造成的
    0251588ea6ee: @豆腐_4984 请大神指教
    0251588ea6ee: @豆腐_4984 我也出现了 是加载的问题么?
    一斤代码:因为我这边没出现这种情况,所以不是很清楚。你可以在你的代码中多加一些log信息,在真机上打开调试面板进行跟踪一下
  • 58735fca0607:你好,我最近在使用这个例子,数据是从后台加载过来的,但是我发现有时候数据加载过来了但是却没有调用图片的加载函数,然后新加载出来的图片就没有显示出来。请问这是怎么回事呢
    6104a8ff14cb:@人潮汹涌 请问问题解决了吗?我现在页遇到了这个问题
    58735fca0607:@一斤代码 数据读出来了,但是onImageLoad()函数没被调用,然后该加载出来的数据就没有出来。一般在我下拉比较快的时候就会出现这种情况。这种情况出现后再下拉的时候也是只读数据不加载图片了。我本来想会不会是因为我下拉的太快导致图片还没加载出来就重新设置images了呢。但是如果是这样的话,我停止下拉或者等一会再下拉图片应该能加载出来,但是并没有。所以请问题主对这个问题有没有什么解决办法
    一斤代码:你是说onImageLoad()函数没被调用还是?你要确保你从后台获取到的数据是否和前端需要的数据格式一致,然后是否正确的通过setDate()被更新到view中去了
  • 58735fca0607:请问如果在每一个图片下面都有一段对图片介绍的文字,那么怎么计算图片和文字的高度呢
    58735fca0607:@一斤代码 对于这个问题,我是在一个微信小程序《三言两鱼》上看到了这种布局。觉得很好奇,但是始终找不到比较满意的实现方法。题主有兴趣的话可以参考一下。另外,感谢题主的回答。
    一斤代码:在基础库1.4.0之前,你这个需求不好弄,可能最好的方式是文字部分的容器高度使用固定高度。1.4.0之后,小程序增加了获取WXML节点信息的API(wx.createSelectorQuery()等API),可以获取到各种元素的比如宽高等信息
  • 435562c42074: //判断当前图片添加到左列还是右列
    if (col1H <= col2H) {
    col1H += imgHeight;
    col1.push(imageObj);
    } else {
    col2H += imgHeight;
    col2.push(imageObj);
    }

    这段代码中的col1H和col2H没有找到在哪里定义的哦
    CENT2023:同理,如何利用video组件实现下滑加载更多视频?参照二更视频。
    435562c42074:@一斤代码 看到了,谢谢
    一斤代码:就在第一和第二行:
    let col1H = 0;
    let col2H = 0;
  • 391723ea0acd:你好,想问下,如何实现图片有几张就加载几张,不会无限加载呢?
    一斤代码:@小火柴爱女孩 没有明白你的意思,能不能再详细解释下?
    391723ea0acd:@一斤代码 请问一个数组的数据如何适配左右两边的布局呢
    一斤代码:把wxml里的bindscrolltolower="loadImages"去掉就可以啦
  • 7f9e1193702e:您好,我问下,您这代码涉及到后了吗?假如我想让图片是通过接通地址调用出来显示的,该怎么办?
    一斤代码:@喜歡你的笑 看起来类似这样:

    loadImages: function () {
    var that = this;

    wx.request({

    url: '后台API地址',

    data: {
    //一些参数
    },

    success: function (res) {
    let images = res.data; //从后台获取到的数据

    let baseId = "img-" + (+new Date());

    for (let i = 0; i < images.length; i++) {
    images[i].id = baseId + "-" + i;
    }

    that.setData({
    loadingCount: images.length,
    images: images
    });
    }

    })

    }
    7f9e1193702e:@一斤代码 是要把后台相关的图片信息写到一个images数组中,然后请求接口,在前台循环显示吗?
    一斤代码:这段代码里没有调用后台API来拿数据,在loadImages方法中,使用的数据是硬编码的一个数组(images),但是这段代码写的时候是预先考虑到了这样的使用情况的,你只要将loadImages稍微改写一下,在其中请求你后台的接口来获取images数据就行了。
  • Kidwind:文章的思路不错,不过指出两个问题希望探讨一下。
    一是实现原理是通过隐藏的image域加载图片,图片加载完成后通过bindload回调方法来获得图片的宽高,从页计算应该将该图放置左列还是右列,但有一个问题,图片加载完成的顺序不一定与原image队列相同,是根据图片载入完成的先后顺序插入左右列的,有可能会导致实际排版出来的顺序与原列表顺序不符。
    二是通过滚动到底部来加载更多的图片,如果此时前一页的获取图片宽高的bindload还未执行完,即data.images不为空,此时又载入了下一页的数据,会清空data.images队列,将会导致瀑布流中的数据缺失。
    一斤代码:@我是一个大海绵 可以在onImageLoad中,等待当前图片组全部下载完成后,再进行左右列的添加操作就行了。
    1043f8e43ead:@一斤代码 如何规避实际排版出来的顺序与原列表顺序不符呢?
    一斤代码:嗯,您说的2个问题在本例子中确实是存在的,不过通过一些简单的控制都可以规避。
    这个例子由于只是做个简单性的代码演示,所以没有考虑的非常全面,你在实际的开发过程中可按照自己的需求来完善。
  • 陌白_:点赞
    一斤代码:@2f981eb9ecfe 谢谢~~
  • 青衫醉:好棒!请问可以转载吗?会注明出处 :relaxed:
    一斤代码:@青衫醉 ok :v:
  • 沉睡的古莲:看完,记得打赏哦😄
    一斤代码:@modie 你没打赏呀。。。。

本文标题:微信小程序中实现瀑布流布局和无限加载

本文链接:https://www.haomeiwen.com/subject/cjlwpttx.html