前言: 因为小程序各种交互限制, 为了实现这常用的功能, 不怎么用小程序的俺把小程序开发文档看了几遍, 挑了感觉可以用上的进行测试, 还网上搜了别人做的, 网上的基本都是死定的尺寸, 死定的数据, 死定的页面, 适用性比较差, 所以, 花了一天半时间, 终于测通了一条路.
开始的失败之路: getIntersectionObserver, 刚看到 1.9.3 + 的版本有这方法的时候, 心里一阵高兴, 感觉就是 js 的 IntersectionObserver, 结果折腾了差不多一天, 失败告终. 原因有:
1. 它只能监听一个选择器 (js 原生的可监听 N 个 dom, 小程序没 dom, 只有选择器), 比如 js 中的 querySelector, 并且不能是 querySelectorAll. 这就只能给 image 打唯一的 id 了, 一对一呗. 理论上感觉是可以了. 但是...
2. 它不能监听组件内的 image, 也就是选择器选不进去... 反正俺测试没通过. 所以只能用在 page 下的 image, 不能用在 component 的... 丢了 component, 小程序就没什么意义了.
故, 目前放弃了 getIntersectionObserver. 等抠抠那帮死人折腾到 999.999.999 版本再说吧.
getIntersectionObserver 是走不通了, image 的 lazy-load 属性在文档上就一句话 (太恶心了!), 并且只能用在 scroll-view 内, 直接不考虑了, 限制多, 用途窄, 没鬼用!
先上个效果图, 图中是组件效果, 共 7 张图. 右边是 selectAll() 的数组 (数量)
--------------------- 进入主题 -----------------------------
最后想到了, wx.createSelectorQuery().selectAll(), 它可以选择多个节点, 主要是, 它能选组件内的节点. 虽然它是有限制条件的.
限制条件 (个人测, 不完整)
只能是 #id, 或. class, 不能是标签名, 也不能是属性. 但 id 和类名已经够用了, 不行我们就加个类名给标签呗.
选组件内节点示例
componentA.wxml 文件示范
- <view>
- <image class=".loading" />
- <image class=".loaing" />
- </view>
list.wxml 文件示范
- <view class="con"></view>
- <component-a class="a" data="{{data}}" />
选择器
wx.createSelectorQuery().selectAll(".a>>>.loading"); // 可选中组件 A 中的 image
如果想连. con 也一起选, 可这样写
wx.createSelectorQuery().selectAll(".a>>>.loading,.con"); // 这跟 js 的差不多一样.
节点选出来了. 接下来是获得节点信息.
获取节点位置信息语法
- wx.createSelectorQuery()
- .selectAll(".a>>>.loading,.con")
- .boundingClientRect(function(rects){
- //rects 是一个数组对象, 其中有所选中节点的当前位置信息
- })
- .exec();
节点位置信息出来了, 接下来得判断是否进入可视区域, 也就是屏幕.
获取屏幕信息
因为我们是上拉加载, 所以要高度信息就行了.
var windowHeight=wx.getSystemInfoSync().windowHeight // 系统接口, 可用高度
有了节点位置信息, 也有了屏高. 作进入可视区判断
可视判断
- wx.createSelectorQuery()
- .selectAll(".a>>>.loading,.con")
- .boundingClientRect(function(rects){
- //rects 是一个数组对象, 其中有所选中节点的当前位置信息
- var i=rects.length;
- while(i--){
- if(rects[i].top<windowHeight && rects[i].bottom>0){
- // 触发图片加载 (下补)
- }
- }
- })
- .exec();
想触发图片加载, 小程序只能改变数据, 也就是 this.setData(data).
因为节点跟 js 交互基本就 id 和 dataset, 所以, 在这里, 我考虑的是用 dataset 记下数据地址.
记录数据地址
假如 list.js 代码如下 (部分)
- Page({
- data:{
- list:[
- {"pic":"a.jpg"},
- {"pic":"b.jpg"}
- ]
- }
- });
- (本思路不支持 list:["a.jpg","b.jpg"] 这种字符串记录的方式)
list.wxml 为:
- <block wx:for="{{list}}" wx:for-item="item" wx:key="index">
- <image data-addr="list[{{index}}]" class="{{item._lazyload_complete?'':'loading'}}"src="{{item._lazyload?item.pic:''}}" />
- </block>
其中 data-addr 记录的是当前图片的数据地址 (组件同理, 传值就好), 同时给记录假设了两个属性:
1._lazyload_complete 是否完成, 默认肯定是没完成, 等图片 load 后设为 true. 其还有一作用, 就是去掉 loading 状态, 同时也去掉了 selectAll(".loading") 里的匹配.
2._lazyload 是否已经加载, 默认是未加载. 等进入可视区域就设为 true.
进入可视区哉, 改变_lozyload 值
补完执行位置代码:
- Page({
- lazyloading:function(dataset){
- var data={};
- data[dataset.addr+"._lazyload"]=true; // 相当于 data.list[0]._lazyload=true;
- this.setData(data); // 根据数据地址改变了 image.src 的显示
- },
- onLoad:function(){
- var page=this;
- wx.createSelectorQuery()
- .selectAll(".a>>>.loading,.con")
- .boundingClientRect(function(rects){
- //rects 是一个数组对象, 其中有所选中节点的当前位置信息
- var i=rects.length;
- while(i--){
- if(rects[i].top<windowHeight && rects[i].bottom>0){
- // 触发图片加载
- page.lazyloading(rects[i].dataset);
- }
- }
- })
- .exec();
- }
- });
改变 image.src 的显示后, 等图片加载完后, 去掉 loading 状态,
小程序的 image 也只有一个 load 事件可用, 这就绑上事件.
绑上懒加载完成事件
- <block wx:for="{{list}}" wx:for-item="item" wx:key="index">
- <image data-addr="list[{{index}}]" class="{{item._lazyload_complete?'':'loading'}}"src="{{item._lazyload?item.pic:''}}"
- bindload="lazyloadComplete"
- />
- </block>
- list.js
- Page({
- lazyloadComplete:function(e){
- // 这地方要考虑到组件的回调事件
- if(e.type!=="load"){
- e=e.detail;
- }
- var data={},
- dataset=e.target.dataset;
- data[dataset.addr+"._lazyload_complete"]=true; // 类拟_lazyload
- this.setData(data); // 懒加载完成
- }
- });
完整的 list.js 代码:
- Page({
- data:{
- list:[
- {"pic":"a.jpg"},
- {"pic":"b.jpg"}
- ]
- },
- lazyloadComplete:function(e){
- // 这地方要考虑到组件的回调事件
- if(e.type!=="load"){
- e=e.detail;
- }
- var data={},
- dataset=e.target.dataset;
- data[dataset.addr+"._lazyload_complete"]=true; // 类拟_lazyload
- this.setData(data); // 懒加载完成
- },
- lazyloading:function(dataset){
- var data={};
- data[dataset.addr+"._lazyload"]=true; // 相当于 data.list[0]._lazyload=true;
- this.setData(data); // 根据数据地址改变了 image.src 的显示
- },
- onLoad:function(){
- var page=this;
- var query=wx.createSelectorQuery()
- .selectAll(".a>>>.loading,.loading")
- .boundingClientRect(function(rects){
- //rects 是一个数组对象, 其中有所选中节点的当前位置信息
- var i=rects.length;
- while(i--){
- if(rects[i].top<windowHeight && rects[i].bottom>0){
- // 触发图片加载
- page.lazyloading(rects[i].dataset);
- }
- }
- });
- this.checkLazyload=function(){
- query.exec();
- };
- // 页面加载完后处理第一遍, 然后就是 scroll 事件内处理.
- this.checkLazyload();
- },
- onPageScroll:function(){
- this.checkLazyload(); // 加个节流或防抖函数就更完美了, 在此不多写了.
- }
- });
最后, 由于小程序的 Page 无 prototype 属性, 故扩展封装时, 只能自己另起一工具包, 给 page 赋于 lazyload 的能力即可.
具体封装也不在这写了, js 好点的人都会的.
完整源码迟点有时间再上传
over!
来源: http://www.qdfuns.com/article/11445/9972bffbdb8ef476fad7259dafa61325.html