小程序瀑布流组件实现翻页与图片懒加载

电商小程序中,用到瀑布流的地方非常多,每次都写一个瀑布流,重复一次逻辑,作为程序员,肯定是非常不愿意的。
瀑布流的形式都是大同小异,不同的是瀑布流中每个模块的内容,随业务而变化。
所以,我们把瀑布流框架抽象成组件,瀑布流的内容由业务确定。这样即可实现组件化和自定义的最大平衡,微信小程序组件源码。

首先,我们来看一下瀑布流组件在实际项目中的实际效果。

1 实际效果

瀑布流组件实际效果如下图所示,左侧为用户交互效果,右侧为图片懒加载实际效果。

2 什么是瀑布流?

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,waterfall-item宽度固定,高度不定,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。如下图所示:

3 实现功能

该瀑布流组件实现了以下几个功能:

  • 支持图片懒加载
  • 支持上拉数据翻页
  • 支持自定义样式
  • 支持瀑布流Item间隔底层自动计算
  • 原生组件模式:即类swiper和swiper-item 组件用法
  • 组件与数据完全解耦

4 实现原理

4.1 waterfall 和waterfall-item实现原理

第一步:在 waterfall-layout 目录下创建 waterfallwaterfall-item 组件,目录结构如下:

.
├── query-node.js
├── waterfall-item.js
├── waterfall-item.json
├── waterfall-item.wxml
├── waterfall-item.wxss
├── waterfall.js
├── waterfall.json
├── waterfall.wxml
└── waterfall.wxss

第二步:分别在waterfall.jswaterfall-item.jsrelations选项中指定组件父、子级关系:

// waterfall.js
Component({
 // ... other code
 relations: {
 './waterfall-item': {
  type: 'child',
 },
 // ... other code
 }
})
// waterfall-item.js
Component({
 // ... other code
 relations: {
 '././waterfall': {
  type: 'parent',
 },
 // ... other code
 }
})

指定彼此的父、子组件的关系后,即可通过 this.getRelationNodes 原生 API,就能访问彼此实例对象及其属性和方法。

第三步:实现waterfall.wxmlwaterfall-item.wxml代码:
waterfall.wxml代码实现非常简单,只有5行代码:

<view class="waterfall custom-class">
 <view class="waterfall-inner">
 <slot ></slot>
 </view>
</view>

同样,waterfall-item.wxml代码实现也非常简单,只有5行代码:

<view
 class="waterfall-item custom-class"
 style="{{position}}:0;top:{{(top >= 0 ? top + 'px' : 0 + 'rpx')}};"
>
 <slot ></slot>
</view>

不知道slot用法的童鞋,请参考微信小程序自定义组件模板和样式文档。

4.2 瀑布流原理

其实,不管是微信小程序、web、还是原生APP,瀑布流的实现原理都是一样的。都可以绝对定位和位置计算来实现。
瀑布流的大体过程如下图所示:

第一步:数据通过this.setData从逻辑层传输到视图层,进行第一渲染,由于每个waterfall-itemtop:0;position:left;,所以都重叠了在一起。

第二步:通过节点查询API获取每个waterfall-item元素信息,并且计算出正确的topposition值。

第三步:setData每个waterfall-itemtopposition,实现重排。

具体逻辑实现如下:

首先,我们来实现一个节点查询API querySelector,之后会用到:

// query-node.js
/**
 * 获取当前页面中,选择器为 selector 的第一个node节点
 * @param {String} selector 符合微信小程序规范的选择器
 * @param {Object} context 调用环境,普通页面中为wx,自定义组件中为this;默认值为wx.
 * @return {Array} 返回一个数组,第一个元素为 node 节点
 */
export const querySelector = function (selector, context = wx) {
 return new Promise((resolve, reject) => {
 context.createSelectorQuery()
 .select(selector)
 .boundingClientRect((res) => {
  if (res) {
  resolve(res);
  } else {
  reject(`不存在选择器为 ${selector} 的节点`);
  }
 })
 .exec();
 })
};

接着,看一下组件waterfallwaterfall-item在实际项目中的用法:

 <waterfall
  loading="{{loadMorePending}}"
  isAllLoaded="{{isAllLoaded}}"
 >
  <block wx:for="{{data.sections}}" wx:key="id" wx:for-item="product">
  <waterfall-item
   index="{{index}}"
   custom-class="flow-item-wrapper"
  >
   <view class="product-item">
   业务代码
   </view>
  </waterfall-item>
  </block>
 </waterfall>

当第一个waterfall-item组件,在视图层布局完成后会执行ready生命周期钩子。
ready 生命周期钩子中,我们需要做两件事:

  • 获取父组件waterfall的实例对象,并挂载在waterfall-item组件的 this实例对象上。因为之后我们需要在waterfall-item组件中修改waterfall上的数据。
  • 获取waterfall-item组件的高度,计算waterfall-item组件的位置信息topposition
// waterfall-item.js
import { querySelector } from './query-node';
Component({
 // ... other code
 lifetimes: {
 ready() {
  const [waterfall] = this.getRelationNodes('./waterfall');
  this.parent = waterfall;
  this.setWaterfallItemPosition();
 },
 }
 methods:{
 async setWaterfallItemPosition() {
  querySelector('.waterfall-item', this)
  .then(async (node) => {
   const { top, position } = await this.parent.getWaterfallItemPostionInfo(node);
   this.setData({
   top,
   position
   })
  })
 },
 }
 // ... other code
})

setWaterfallItemPosition方法中,我们调用了父组件上的方法this.parent.getWaterfallItemPostionInfo,获取当前waterfall-item组件的topposition信息。并把已经渲染好的waterfall-item组件的累计高度缓存在waterfallleftHeightsrightHeights属性上,用于计算下一个waterfall-item组件位置,主要逻辑如下:

// waterfall.js
const POSITION_LEFT = 'left';
const POSITION_RIGHT = 'right';

Component({
 // ... other code
 /**
 * 组件的方法列表
 */
 methods: {
 lifetimes: {
  ready() {
  this.initParams();
  }
  },
 initParams() {
  this.leftHeights = 0;
  this.rightHeights = 0;
 },
 /**
  * 设置 waterfall-item 的高度值
  * @param {Object} node waterfall-item 组件位置尺寸数据
  */
 async getWaterfallItemPostionInfo(node) {
  let top = 0;
  let position = POSITION_LEFT;
  const { height } = node;
  const { itemGap } = this;
  if (this.leftHeights <= this.rightHeights) {
  top = this.leftHeights;
  if(this.leftHeights === 0) {
   this.leftHeights += height;
  } else {
   top += itemGap;
   this.leftHeights += (height + itemGap);
  }
  } else {
  position = POSITION_RIGHT;
  top = this.rightHeights;
  if(this.rightHeights === 0) {
   this.rightHeights += height;
  } else {
   top += itemGap;
   this.rightHeights += (height + itemGap);
  }
  }
  return {
  top,
  position,
  }
 }
 // ... other code
 }
})

当所有的waterfall-item重排结束后,瀑布流渲染完成。

4.3 图片懒加载原理

微信小程序中,<image>标签本身是支持懒加载的,当lazy-load={{true}},且在即将进入一定范围(上下三屏)时才开始加载。

也就是说,当lazy-load={{true}}<image>标签初次渲染在视口上下三屏之外时,是不会请求图片资源的,当<image>即将进入三屏之内时,才会加载。

在4.2小节的图3中,<waterfall-item>的初始化位置设置成了top:0;position:left;,所以,都在视口中。如果将top的值成三屏之外的数值,例如,400vh或者更大,则<waterfall-item>重排之后,任然在三屏之外的图片即会自动懒加载。

<view
 class="waterfall-item custom-class"
 style="{{position}}:0;top:{{(top >= 0 ? top + 'px' : itemCount * 100 + 'vh')}};"
>
 <slot ></slot>
</view>
Component({
 // waterfall-item.js
 // ... other code
 lifetimes: {
 ready() {
  const { itemCount } = this.data;
  const [waterfall] = this.getRelationNodes('./waterfall');
  waterfall.childCount += 1;
  this.parent = waterfall;
  this.setData({
  itemCount: itemCount + waterfall.childCount,
  })
 },
 },
 // ... other code
})

4.4 数据翻页

因为实现了wx:for <waterfall-item>功能,和<swiper-item>组件一样,因此翻页逻辑完全由用户自己定制,<waterfall><waterfall-item>只给你提供翻页的功能,组件就可以和瀑布流数据结构完全解耦。

4.5 瀑布流Item间隔底层自动计算

将列和行中,两个<waterfall-item>组件之间的距离定义为itemGap,则:

itemGap = waterfall宽度 - (waterfall-item宽度 * 2)

<waterfall>ready钩子中,可以获取到<waterfall>组件的宽度;同理,在<waterfall-item>ready钩子中,可以获取到<waterfall-item>组件的宽度。
在调用getWaterfallItemPostionInfo之前,获取到itemGap的值即可。这样,在计算<waterfall-item>top值时,除了第一行的<waterfall-item>top值等于0之外,其他所有<waterfall-item>top值等于:

// this.leftHeights += height + itemGap;
// or
// this.rightHeights += height + itemGap;

具体代码实现请查看源码

5 总结

通过瀑布流框架抽象,使<waterfall><waterfall-item>接近原生组件使用体验,同时使组件与数据完全解耦。通过巧妙的初始化位置top设置,使瀑布流具图片有懒加载的功能。

到此这篇关于小程序瀑布流组件实现翻页与图片懒加载的文章就介绍到这了,更多相关小程序瀑布流内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2020-05-19

小程序简单两栏瀑布流效果的实现

瀑布流又称瀑布流式布局,是比较流行的一种网站页面布局方式.视觉表现为参差不齐的多栏布局,即多行等宽元素排列,后面的元素依次添加到其后,等宽不等高,根据图片原比例缩放直至宽度达到我们的要求,依次放入到高度最低的那一栏. 先上代码:https://developers.weixin.qq.com/s/Fgm5s1mz7Wdm 所谓简单,是指只考虑图片,图片之外的其他元素高度固定,不在考虑范围内. 说一下基本的实现思路: 1.加载列表数据 2.在一个隐藏的view中加载图片,通过image组件的bin

微信小程序通过js实现瀑布流布局详解

前言 瀑布流 - 小程序中数据列表或者图片展示难免高度不一,而我们一般固定宽度,让其高度自适应,并无缝对接.如下图: 瀑布流的两种做法: css: 在父元素上使用column-count: 2也可以做到两列排版.但column-count: 2是左右两列排版,如果碰巧出现左边累计高度过多大于右边累计高度的话,右边会出现大片空白区域.点击前往 js(推荐): 判断左右两边的累计高度,那边的高度小,便往那边添加(需要动态获取节点元素的高度进行计算).下面用的就是此种方法: 准备工作 我们需要用到 a

微信小程序实现瀑布流布局与无限加载的方法详解

前言 瀑布流布局是一种比较流行的页面布局方式,最典型的就是Pinterest.com,每个卡片的高度不都一样,形成一种参差不齐的美感. 在HTML5中,我们可以找到很多基于jQuery之类实现的瀑布流布局插件,轻松做出这样的布局形式.在微信小程序中,我们也可以做出这样的效果,不过由于小程序框架的一些特性,在实现思路上还是有一些差别的. 今天我们就来看一下如何在小程序中去实现这种瀑布流布局: 小程序瀑布流布局 我们要实现的是一个固定2列的布局,然后将图片数据动态加载进这两列中(而加载进来的图片,会

微信小程序 点击控件后选中其它反选实例详解

微信小程序 点击控件后选中其它反选实例详解 前言: 如果需要实现进来进行给按钮加上买一送一的样式,或者单击就选中单个按钮,只能靠css结合js进行控制了,小程序暂时没有这样的控件. 实现效果图: 微信小程序进来的时候自动进行按钮样式的初始化,这个需要一个字段做判断,加上正则表达式wxml文件: <block wx:for="{{liuliangItems}}"> <block wx:if="{{item.one2one == 1}}"> &l

微信小程序中使用Promise进行异步流程处理的实例详解

微信小程序中使用Promise进行异步流程处理的实例详解 我们知道,JavaScript是单进程执行的,同步操作会对程序的执行进行阻塞处理.比如在浏览器页面程序中,如果一段同步的代码需要执行很长时间(比如一个很大的循环操作),则页面会产生卡死的现象. 所以,在JavaScript中,提供了一些异步特性,为程序提供了性能和体验上的益处,比如可以将代码放到setTimeout()中执行:或者在网页中,我们使用Ajax的方式向服务器端做异步数据请求.这些异步的代码不会阻塞当前的界面主进程,界面还是可以

微信小程序 增、删、改、查操作实例详解

微信小程序 增.删.改.查操作实例详解 1.以收货地址的增删改查为例 2.文件目录 js文件是逻辑控制,主要是它发送请求和接收数据, json 用于此页面局部 配置并且覆盖全局app.json配置, wxss用于页面的样式设置, wxml就是页面,相当于html <form bindsubmit="addSubmit"> <view class="consignee"> <text class="consignee-tit&q

微信小程序中form 表单提交和取值实例详解

微信小程序中form 表单提交和取值实例详解 我们知道,如果我们直接给 input 添加 bindinput,比如:<input bindinput="onUsernameInput" />,那么可以在 onUsernameInput 中直接使用 e.detail.value,即: onUsernameInput : function(e) { e.detail.value; } 但是,如果有多个输入控件,我们不可能为每个控件添加 bindinput.bindchange

uni-app微信小程序登录并使用vuex存储登录状态的思路详解

微信小程序注册登录思路 (这是根据自身的项目的思路,不一定每个项目都适用) 1.制作授权登录框,引导用户点击按钮 2.uni.login获取code 3.把code传给后端接口,后端返回如下数据 openid: "ogtVM5RWdfadfasdfadfadV5s" status: 1 // 状态码:status==0(该用户未注册,需调用注册接口) status==1(该用户已注册) 4.判断用户是否注册,并调用用户信息接口 (1)若已注册则提示登录成功,并调用后台给的获取用户信息的

微信小程序(应用号)简单实例应用及实例详解

Demo 预览 演示视频(流量预警 2.64MB) GitHub Repo 地址 仓库地址:https://github.com/zce/weapp-demo 使用步骤 将仓库克隆到本地: bash $ git clone https://github.com/zce/weapp-demo.git weapp-douban --depth 1 $ cd weapp-douban 打开微信Web开放者工具(注意:必须是0.9.092300版本) 必须是0.9.092300版本,之前的版本不能保证正

微信小程序 选择器(时间,日期,地区)实例详解

微信小程序 选择器(时间,日期,地区) 微信小程序 开发由于本人最近学习微信小程序的开发,根据自己的实践结果整理了下结果,对日期选择器,时间选择器,地区选择器做的实例,有不对的地方,希望大家指正. 用微信封装好的控件感觉很好,为我们开发人员省去了很多麻烦.弊端就是不能做大量的自定义.今天试用了选择器. 上gif: 上代码: 1.index.js //index.js //获取应用实例 var app = getApp() Page({ data: { date: '2016-11-08', ti

微信小程序中换行空格(多个空格)写法详解

在小程序中HTML的网页实体无法正常使用,小程序中的写法为:  一.空格,换行 <text>你好!\t七月流火啊!\n我在下一行</text> \t 空格( 多个只会显示一个空格) \n 换行 二.连续空格 <view> <text space="ensp">你好 啊 哈哈哈(空格是中文字符一半大小)</text> </view> <view> <text space="emsp&quo

微信小程序template模板与component组件的区别和使用详解

前言: 除了component,微信小程序中还有另一种组件化你的方式template模板,这两者之间的区别是,template主要是展示,方法则需要在调用的页面中定义.而component组件则有自己的业务逻辑,可以看做一个独立的page页面.简单来说,如果只是展示,使用template就足够了,如果涉及到的业务逻辑交互比较多,那就最好使用component组件了. 一. template模板: 1. 模板创建: 建议单独创建template目录,在template目录中创建管理模板文件. 由于