blogs icon indicating copy to clipboard operation
blogs copied to clipboard

微信小程序的其他研究

Open forever-z-133 opened this issue 7 years ago • 1 comments

  • 下拉刷新
  • 瀑布流
  • 纵向轮播
  • 生成图片保存到相册
  • 伪 AR 效果

微信小程序的小型专题研究,至于 案例 可前往 我的仓库 查看


下拉刷新

在 app.json 的 window 选项中或页面配置中开启 enablePullDownRefresh; 在页面中写上 onPullDownRefresh 监听下拉刷新的触发; 当处理完数据刷新后,wx.stopPullDownRefresh 可以停止当前页面的下拉刷新。

注意事项

  • 全局的 showToast 和 showLoading 在苹果机上会严重影响下拉的回弹效果,不宜一起使用。
  • 现在各页面都有自己的 json,可以分页面设置是否下拉刷新哟(官方文档)。

简单的美化

app.wxss 里 page { background: #e5e5e5 } 让背景色偏灰 app.json 里 "backgroundTextStyle": "dark" 让三个点变成黑色

tim 20171215140905

感谢:http://www.wxappclub.com/topic/935

自写下拉刷新

除开配置中的 tabBar 的 position 为 top 这种样式外,可能你还会遇上超坑比的产品跟你说,顶部还得再加个搜索,或者顶部导航换个样式,那样我们就无法使用小程序自带的下拉刷新了。

而在小程序中,fixed 慢慢得到了比较好的支持,不会像 H5 那样特别不听话。 所以直接将顶部元素 fixed 一下,本页设为可下拉刷新,效果也是很棒的。

// page.json
{
  "enablePullDownRefresh": true
}
// page.wxml
<view class="top-bar">顶部导航</view>
<view class="section others">test</view>
// page.wxss
.top-bar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 2;
  background: #fff;
  box-shadow: 0 2rpx 5rpx rgba(0,0,0,.1);
}
// page.js
Page({
  onPullDownRefresh: function () {
    wx.stopPullDownRefresh();
  },
})

但需求往往无穷无尽,当遇到页面需要不同滑动区域时,比如 tabs 对应多个 scroll-view。 那么上面方案就会遇到一个问题,它们的滚动条是同一个呀,切换时滚动到的位置是一致的。 当然,用数组去记录切换前的已滑动位置,也是个不错的办法。

而不死心的我还是想试试有没有更直接的办法,实则是有的,只是不太妙而已。 原理:当 scroll-view 有高度时会使用 scroll-view 的滚动条,无高度时会使用全局的滚动条。 这样的话,我们只需监听 scroll 判断滑到顶了则去掉高度,那不就可以用全局的下拉刷新了吗。 方法是可行,方案也很骚,但苹果机上有问题,下拉刷新时的三个点是在 web-view 外面。

// page.wxml
<scroll-view 
  style='height:{{listHeight}}px' 
  scroll-y 
  enable-back-to-top 
  bindscrolltolower='onReachBottom' 
  bindscroll='scroll'>
  <view>列表内容</view
</scroll-view>
// page.js
let listHeight = 0;
Page({
  onLoad: function() {
    // 计算列表高度,这里的 50 为 .top-bar 高度,因为我设了 100rpx 的定值
    wx.getSystemInfo({success: res => {
      listHeight = res.windowHeight - 50;
      this.setData({ listHeight: res.windowHeight - 50 });
    }})
  },
  scroll: function(e) {
    var st = e.detail.scrollTop
    if (st < 1) this.setData({ listHeight: NaN })
  },
  onPullDownRefresh: function () {
    setTimeout(() => {
      this.setData({ listHeight: listHeight })
      wx.stopPullDownRefresh();
    }, 1000);
  },
})

真的去自己重构 scroll-view 的话,我个人觉得是不必的。 只使用 scroll-view 的 bindscrolltoupper 触顶刷新其实是最好的, 让用户不用非要进行下拉刷新的操作,无意识滑回顶部时发现数据变了可能更令人开心。

瀑布流

CSS3 的 column 众所周知,用起来非常舒爽,但有顺序问题(会先把第一栏填满再填后面的)。 而更多 jquery 插件,不能引用是一回事,其算法在小程序中也比较难实现(宽高位置获取太麻烦)。 相对而言,花瓣网 的这种分栏式瀑布流要更容易实现一些。

布局和结构不难理解,分为 cols 个数的栏目,分别有自己的数组 list[col]。

<view class="list-box">
  <block wx:for="{{cols}}" wx:key="*this" wx:for-item='col'>
    <view class='list list{{col}}'>  <!-- 单个栏目 -->
      <block wx:for="{{list[col]}}" wx:key="unique" wx:for-index='i' wx:for-item='one'>
        <view class='item'>  <!-- 栏目中的一项 -->
          <!-- 图片 -->
          <image src='{{one.img}}' style='width:{{one.width}}px;height:{{one.height}}px'></image>
          <!-- 文字,可多行 -->
          <view class='desc'>{{one.text}}</view>
        </view>
      </block>
    </view>
  </block>
</view>

// index.js
data: {
  cols: 2,
  list: [[], []],
}

然后将新数据插入到栏目中最矮的一个栏目中去,那么我们就需要知道图片高度和文本高度。 获取图片高度可通过 wx.getImageInfo,用 bindload 也可以。 获取文本高度可通过 wx.createSelectorQuery().select('.desc').boundingClientRect。

// 返回单个列表项的高度,obj 为数据,index 为数据索引
getHeight: function(obj, index, callback) {
  // 获取图片信息
  wx.getImageInfo({
    src: obj.img,
    success: img => {
      // 修改图片尺寸,宽度等于 col 宽,高度自适应
      var ratio = img.width / img.height
      obj.width = img.width = this.colWidth;  // 这个 col 宽度需要另外获取
      obj.height = img.height = obj.width / ratio;
      // 获取文字高度
      var $dom = wx.createSelectorQuery().select('#text_' + index);
      $dom.boundingClientRect(rect => {
        var height = img.height + rect.height;
        obj.itemHeight = height;
        callback && callback(height);
      }).exec();
    },
  });
},

看到网上大多实现下来,最终列表项的顺序可能是无序的,所以我们最好是所有高度获取完了再计算。

// 传入新数据,计算列表分布,更新列表
update_list: function (r) {  // r 为新数据
  var total = r.reduce(x => ++x, 0);
  r.forEach((item, i) => {
    // 获取每个列表项的高度
    this.getHeight(item, i, (height) => {
      if (--total < 1) {  // 高度全部获取完毕,开始计算
        this.data.list = this.theNewList(r, this.data.list);
        this.setData({ list: this.data.list });
      }
    })
  })
},
// 根据后加入的列表,产生新的列表
theNewList: function (temp, list) {  // temp 即 r,为新数据
  temp.forEach(item => {
    // 选出当前 col 高度最小值,_height 存储着各栏目的当前高度
    var min = Math.min.apply(null, _height);
    // 选出当前 col 高度最小值的索引
    min = _height.indexOf(min);
    // 进行赋值,这样做才是有顺序的列表
    _height[min] += item.itemHeight;
    list[min].push(item);
  })
  return this.data.list;
},

20171217222434

纵向的轮播组件

其实写轮播并不是件难事,小程序的轮播也是如此。 需处理的几个方面即可:拖拽操作,防止事件混淆,回调与自定义项。

另一方面,轮播的样式和场景处理也有不同流派 小程序的轮播是以父级高度为主,子级是 absolute 的,所以还可以完成 重叠/3d 等形式的动画切换。 而 swiper 的轮播是以 flex 布局为主,子级撑起父级高度的样式方案,只有单轴向的动画切换。

本案采用后者。当然这是由项目需求决定的,但其中大多流程不会改变。

// ... 代码整理中

生成图片并保存到相册

步骤说清楚是简单的,但不得不说,小程序的坑能把人脚给崴了。

  • canvas 绘制(wx.createCanvasContext 等)
  • wx.canvasToTempFilePath(options) 导出图片
  • wx.saveImageToPhotosAlbum 保存到相册

大致列举一下,后期会不断更新:

  • setTextAlign 和 setTextBaseline 在少量机型上会无效,所以不推荐使用,自己计算宽度比较保险。
  • drawImage 是异步的,前面画的图可能会覆盖后面的字,推荐先画图然后延时1s再重新画图画字。
  • 保存到相册少量机型不支持,所以也要做提示长按保存的兼容方案。
  • 保存到相册功能会被禁用,所以在 complete 回调里要判断是否 deny。

// ... 代码整理中

伪 AR 效果

大致介绍一下,就是打开了摄像头,进行现实场景的识别,然后给予虚拟场景下的反馈。比如扫个图形出来个三维小萝莉什么的。

真 AR 能识别到视频流的每一帧,对小萝莉还能进行三维场景下的定位处理,这必然是很烧的事情,涉及到截图效率/场景识别/三维定位计算/三维绘制几个比较头疼的事情。

所以小程序能做的还是伪 AR,定时截个图,识别成功出结果,放个三维动画结束。

// ... 代码整理中

forever-z-133 avatar Dec 15 '17 06:12 forever-z-133

不错不错

yangzhaomao2014 avatar Dec 18 '17 12:12 yangzhaomao2014