小程序之图片瀑布流(最全实现方式,额外加送懒加载)

效果图

来来来,看啊看,外面的世界多好看,

image

效果图展示的是瀑布流布局 && 懒加载的效果

数据

图片数据来源张鑫旭的网络日志

先说下我们的图片链接格式

所有的链接都是http://cued.xunlei.com/demos/publ/img/P_${name}.jpg这样的格式,我们需要改变name的值就行了,当name值小于10的时候,格式是00x,如002003,大于10的时候就是023这种。

定义

瀑布流布局是一种比较流行的页面布局方式, 最早采用此布局的网站是Pinterest, 图片宽度是固定的,高度自动,产生一种参差不齐的美感。

原理

原理很简单,主要分为以下几步

1、定义高度数组和列数

2、遍历元素,个数小于列数的直接push到数组中

3、大于列数的,获取高度数组中最小的值,定义元素的top和left值

4、重要一点 更新高度数组,将最小高度加上当前元素的高度

知道原理了,代码应该怎么写呢?这里用web端来示例,大概如下

let heightArr = []
let col = 2
let allBox = document.querySelectorAll('.box') // 获取所有盒子

for(let i in allBox){
    
    let boxWidth = allBox[0].offsetWidth  // 获取盒子宽度 都一样直接取第一个
  let boxHeight = allBox[i].offsetHeight
    if(i < col){    
        heightArr.push(boxHeight)  // 把第一行高度都添加进去
    } else {  // 进行布局操作
        
        let minHeight = Mac.min.apply(null, heightArr)  // 获取最小高度
        let minIndex = getIndex(heightArr, minHeight)  // 获取最小高度的下标 要不就是0 要不就是1
        allBox[i].style.position = 'absolute'
        allBox[i].style.top = minHeight + 'px'
        allBox[i].style.width = minIndex * boxWidth + 'px'
        
        heightArr[minIndex] += boxHeight // 更新最新高度 
    }
}

// 获取下标
getIndex(arr, val){
    for(i in arr){
        if(arr[i] == val) {
            return i
        }
    }
}

上面就是实现瀑布流的主要逻辑,这里大概写了下,接下来我们看看小程序怎么实现。

实现

在web页面里面我们可以直接获取、操作DOM,实现起来很方便,何况还有很多的jquery插件可以使用。我们知道小程序里面是没有DOM的,那应该怎么实现呢?我们把思路转换下就行了。

这里我们用三种方式来实现瀑布流布局。

CSS

使用css3来实现是最简单的,我们先捡简单的来说,

使用column-count属性设置列数

使用wx-if进行判断将图片渲染到左侧还是右侧

wxml

<view class='container'>
    <image src='{{item.url}}' wx:if="{{index % 2 != 0 }}" wx:for="{{list}}" mode='widthFix' wx:key="{{index}}"></image>
     <image src='{{item.url}}' wx:if="{{index % 2 == 0 }}" wx:for="{{list}}" mode='widthFix' wx:key="{{index}}"></image> 
</view> 

wxss

.container{
  column-count: 2;  /*设置列数*/ 
  column-gap:2rpx;
  padding-left: 8rpx;
}
image{
  width: 182px;
  box-shadow: 2px 2px 4px rgba(0,0,0,.4);
}

js获取下数据即可,这里就不赘述了。

节点信息

小程序可以通过WXML节点信息API来获取元素的信息,接下来我们来撸码。

wxml

<view class="container">
      <view wx:for="{{group}}" style='position:{{item.position}}; top: {{item.top}}; left:{{item.left}}; width:{{width}}rpx;' class='box box-{{index}}' wx:key="{{index}}">
            <image  src='http://cued.xunlei.com/demos/publ/img/P_{{item.name}}.jpg' style=' height:{{height[index]}}px' bindload='load' data-index='{{index}}' class='image'></image>
      </view>
</view> 

wxss

.container{
  position: relative;
  display: flow-root;
}
.box{
  float: left;
  display: flex;
  margin-left:5rpx;
  box-shadow: 2rpx 2rpx 5rpx rgba(0,0,0,.3);
  border: 1rpx solid #ccc;
  box-sizing: border-box;
  padding: 10px;
}
.box:nth-child(2){
  margin-left: 12rpx;
}
image{
  width: 100%;
}

js

图片链接为http://cued.xunlei.com/demos/publ/img/P_${name}.jpg, 只需要更改name就行了

首先处理我们的数据

// 创建长度为30的数组
const mockData = () => {
  return Array.from(Array(30).keys()).map(item => {
    if (item < 10) {
      return '00' + item
    } else {
      return '0' + item
    }
  })

}
// 扩展成我们需要的数据
const createGroup = () => {
  let group = []
  let list = mockData()
  list.forEach(item => {
    group.push({ name: item, position: 'static', top: '', left: '' })
  })
  return group
}

然后进行瀑布流布局,主要代码如下

load(e){  // 监听图片加载完 获取图片的高度
    this.setData({
      height: [...this.data.height, e.detail.height]
    })
    this.showImg()  // 调用渲染函数
},

showImg(){
    let height = this.data.height
    if (height.lenth != this.data.group .legth){  // 保证所有图片加载完
      return
    }
    setTimeout(()=>{ // 异步执行
      wx.createSelectorQuery().selectAll('.box').boundingClientRect((ret) => {
        let cols = 2
        var group = this.data.group
        var heightArr = [];
        for (var i = 0; i < ret.length; i++) {
          var boxHeight = height[i]
          if (i < cols) {
            heightArr.push(boxHeight + 25)
          } else {
            var minBoxHeight = Math.min.apply(null, heightArr);
            var minBoxIndex = getMinBoxIndex(minBoxHeight, heightArr);
            group[i].position = 'absolute'
            group[i].top = `${minBoxHeight}px`
            group[i].left = minBoxIndex * this.data.width / 2 + 'px'
            group[i].left = minBoxIndex == 0 ?  minBoxIndex * this.data.width / 2 + 'px' : minBoxIndex * this.data.width / 2 + 5 + 'px'
            heightArr[minBoxIndex] += (boxHeight + 25)
          }
        }

        this.setData({
          group
        })
        wx.hideLoading()

      }).exec()
    }, 200)
    
  }

可以看到实现的逻辑和上面的大概类似,只不过这里我们修改的是数据,毕竟小程序是数据驱动的嘛。

这里主要我们监听image组件的bindload事件来获取每张图片的高度,获取了高度才能进行布局,大部分的时间也都用来加载图片了,能不能优化呢?当然可以了,我们使用node把数据包装下。

后端处理数据

上面我们说到在小程序内部获取图片的高度是个费力不讨好的事,我们使用node来获取图片高度,然后包装下再给小程序使用。

  • 使用request进行请求
  • 使用image-size获取图片的高度
  • 最后将获取后将数据写入文件,启动一个服务提供接口

这里主要说下碰到的问题

1、request模块的请求默认返回来的是个String类型的字符串,使用image-size模块传入的必须是Buffer,怎么破呢?在request请求中设置encodingnull即可

2、我们这里爬取了100张图片,怎么保证都已经爬取完了呢?可以这样写

Promise.all(List.map(item => getImgData(item)))  // getImgData函数是获取图片的函数 会返回个promise

3、如果请求了几次,发现有的图片获取不到了,报错了,怎么回事呢,人家毕竟做了防爬的,恭喜你中奖了,换个ip再试吧(可以把代码放在服务器上面,或者换个Wi-Fi),其实我们只需要爬一次就行,生成完文件还爬干嘛啊。

完整代码请戳github

我们回到小程序,此时接口返回的数据如下

image

可以看到每个图片都有高度了,接下来我们实现瀑布流布局,等下,我们搞下瀑布流布局的懒加载,关于小程序的懒加载,猛戳了解更多

怎么实现呢?主要分为两步

1、将元素瀑布流布局

2、创建IntersectionObserver,进行懒加载

先开始我们的布局吧

wxml

<view class='container'>
  <view class='pic pic-{{index}}' wx:for="{{list}}" style="height:{{item.height}}px;left:{{item.left}}; top:{{item.top}}; position:{{item.position}}" wx:key="{{item.index}}">
    <image src='{{item.url}}' wx:if="{{item.show}}"></image>
    <view class='default' wx:if="{{!item.show}}"></view>
  </view>
</view>

上面我们使用wx-if通过show这个字段来进行判断了图片是否加载,

使用一个view组件用来占位,然后更改show字段就可以显示图片了

js

我们使用两个for循环,先来进行布局

let cols = 2
    let list = this.data.list
    let heightArr = [];


    for(let i in list){
      var boxHeight = list[i].height
      if (i < cols) {
        heightArr.push(boxHeight + 5)
      } else {
        var minBoxHeight = Math.min.apply(null, heightArr);
        var minBoxIndex = getMinBoxIndex(minBoxHeight, heightArr);
        list[i].position = 'absolute'
        list[i].top = `${minBoxHeight}px`
        list[i].left = minBoxIndex * 182 + 'px'
        list[i].left = minBoxIndex == 0 ? minBoxIndex * 182 + 'px' : minBoxIndex * 182 + 4 + 'px'
        heightArr[minBoxIndex] += (boxHeight + 5)
      }
    }
    this.setData({
      list
    })

布局完后,创建IntersectionObserver,动态判断image节点的显示

for (let i in list) {
      
    wx.createIntersectionObserver().relativeToViewport({ bottom: 20 }).observe('.pic-' + i, (ret) => {
        if (ret.intersectionRatio > 0) {
          list[i].show = true
        }
        this.setData({
          list
        })
 })
}

最后

我们使用三种方式完成了小程序的瀑布流布局,还额外完成了基于瀑布流的懒加载。可以发现使用css最简便,虽然小程序不能操作DOM,但是我们改完数据其实和改变DOM一样,将观念转变过来,小程序的开发还是很爽的。

最后的最后,各位,周末快乐。

github

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,629评论 1 92
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,563评论 25 707
  • 春天唤醒沉睡的大地 嫩绿的芽儿探出了头 它睁开懵懂的眼睛 看见了一个美丽的姑娘 从它的枝条下走过 长长的头发窈窕的...
    爱宝丫阅读 260评论 6 6
  • 那年长街春意正浓也就一个转身你醉眼迷梦跌入怀袖也就一个眼神注定痴缠一生 你曾是谁的梦梦醒过后注定不是彼此的世界后来...
    小橋君子阅读 607评论 0 2
  • 理想今年你几岁 你总是诱惑着年轻的朋友
    方圆日记阅读 519评论 1 2