- 基于wxs实现拖拽排序,封装成了组件,拷贝组件即可使用
基于wxs封装的拖拽排序组件,开箱即用。 目前拖拽到屏幕边缘的时候未触发滚动(即当排序列表超过一个屏幕的话会比较麻烦,如果有需要实现该功能的小伙伴,请留言,作者会抽空加上该功能) 仓库地址:https://gitee.com/ayao1010/miniapp-drag-to-sort 如果喜欢的话给我一个star,我也很开心的😄😄 [图片] 更新: 增加了poschange钩子(在demo里,在该回调里调用了振动反馈)
2021-10-22 - 小程序实现高性能虚拟列表优化+节流+分页请求(固定高度)
场景引入 为什么需要用到高性能虚拟列表+节流+分页请求的优化? 当有场景需求为需要将大量数据(10000条)呈现在一页上,我们不断下拉访问,页面中有大量的数据列表的时候,用户会不会有不好的体验?会不会出现滚动不流畅而卡顿的情况?会不会因卡顿而出现短暂的白屏现象(数据渲染不成功)? 通过微信开发者工具自带的调试器->Network页面,我们可以观察到当有长列表时如果不使用高性能虚拟列表+节流+分页请求的优化,会出现以下问题: FPS:每秒帧数,图表上的红色块表示长时间帧,很可能会出现卡顿。 CPU:CPU消耗占用,实体图越多消耗越高。 NET:网络请求效率低,一次性请求10000条的渲染效率远远低于分1000次,每次请求10条数据 内存:滑动该列表时明显能看到内存消耗大。 总结:如果需要将大量数据(10000条)呈现在一页上,可以通过高性能虚拟列表+节流+按需请求分页数据并追加显示。 优化的具体实现可拆分为以下需求(将一个大问题拆分为一个个小问题并逐个去解决): 不把长列表数据一次性全部直接显示在页面上。 截取长列表一部分数据用来填充屏幕容器区域。 长列表数据不可视部分使用使用空白占位填充。 监听滚动事件根据滚动位置动态改变可视列表。 监听滚动事件根据滚动位置动态改变空白填充。 分页从服务器请求数据,将一次性请求所有数据变为滚动到底部才再次向服务器发送获取数据的请求 开始实战 此次实例我设定每个元素的固定高度为210rpx [图片] (1)首先计算屏幕内的容积最大容量(即屏幕一次性可以容纳多少个高度为210rpx的元素) 调用wx.createSelectorQuery()的api [图片] 上图是id为scrollContainer的组件,滚动时触发函数handleScroll() [代码]// 计算容器的最大容积,onReady中触发,即初次渲染时触发 getContainSize() { wx.createSelectorQuery().select('#scrollContainer').boundingClientRect(function (rect) { rect.id // 节点的ID rect.dataset // 节点的dataset rect.left // 节点的左边界坐标 rect.right // 节点的右边界坐标 rect.top // 节点的上边界坐标 rect.bottom // 节点的下边界坐标 rect.width // 节点的宽度 rect.height // 节点的高度 }).exec((option) => { // console.log(~~(option[0].height / this.data.oneHeight) + 2); this.data.containSize = ~~(option[0].height / this.data.oneHeight) + 2; }) }, [代码] 调用api后返回的option中的height就是小程序页面的视口高度,除以oneHeight(210rpx)就是能容纳的个数,用[代码]~~[代码]来对结果进行向下取整(实际能容纳的应+1),由于在滚动时会出现上一个元素在上边界还没完全消失,第四个元素就从下边界进入视口了,因此最大容纳量应再+1。即实际容纳量应该如图最后一行代码所示+2. [图片] (2)监听滚动事件动态截取数据 监听用户滚动、滑动事件,根据滚动位置,动态计算当前可视区域起始数据的索引位置 startIndex,再根据containsize,计算结束数据的索引位置 endIndex,最后根据 startIndex与endIndex截取长列表所有数据a11Datalist 中需显示的数据列表 showDatalist。 PS:下列代码中函数handleScroll()最下面的[代码]this.setDataStartIndex(data);[代码]才是滚动时真正进行的滚动事件动态截取数据,上面那些代码用途在文章后面部分再详细介绍(节流)。 [代码]// 定义滚动行为事件方法 handleScroll(data) { if (this.data.isScrollStatus) { this.data.isScrollStatus = false; // 节流,设置一个定时器,1秒以后,才允许进行下一次scroll滚动行为 let mytimer = setTimeout(() => { this.data.isScrollStatus = true; clearTimeout(mytimer); }, 17) this.setDataStartIndex(data); } }, [代码] [代码]// 执行数据设置的相关任务, 滚动事件的具体行为 setDataStartIndex(data) { // console.log("scroll active") this.data.startIndex = ~~(data.detail.scrollTop / this.data.oneHeight); // 通过scrollTop滑动后距离顶部的高度除以每个元素的高度,即可知道目前到第几个元素了 this.setData({ showDataList: this.data.allDatalist.slice(this.data.startIndex, this.data.endIndex) }) // 动态截取实际拥有10000条数据的数组中下标为startIndex到endIndex的数组出来呈现在前端页面上 // 容器最后一个元素的索引 if (this.data.startIndex + this.data.containSize <= this.data.allDatalist.length - 1) { this.setData({ endIndex: (!this.data.allDatalist[this.data.endIndex]) ? this.data.allDatalist.length - 1 : this.data.startIndex + this.data.containSize // 滚动到底部了吗,是的话那就将endIndex设置为9999,不然的话设置为startIndex+视口最大容量 }) } else { console.log("滚动到了底部"); this.data.pageNumNow++; // 例如一次性从数据库拿10条数据赋值到allDataList,如果滚动到底部(即allDataList所有数据都已经呈现了),那就再次向服务器发送请求获取数据库中的下10条数据 this.addMes(); // 该函数内就写你实际向数据库请求时的代码,请求成功后拼接到allDataList即可 console.log(this.data.allDatalist.length) } }, [代码] (3)使用计算属性动态设置上下空白占位 我们设置了根据容器滚动位移动态截取ShowDatalist 数据,现在我们滚动一下发现滚动2条列表数据后,就无法滚动了,这个原因是什么呢? 在容器滚动过程中,因为动态移除、添加数据节点丢失,进而强制清除了顶部列表元素DOM节点,导致滚动条定位向上移位一个列表元素高度,进而出现了死循环根据 startIndex和endIndex的位置,使用计算属性,动态的计算并设置,上下空白填充的高度样式blankFi11Sty1e,使用padding或者margin 进行空白占位都是可以的 PS:由于小程序没有computed,所以为了使用计算属性,得另外引入封装好computed的包,引入computed组件的教程我放在最后,我使用的是官方推荐的computed,且注意使用该插件时,不要加[代码]this -> this.data[代码],直接data即可 [代码]computed: { // 定义上空白高度 topBlankFill(data) { // console.log("change") return data.startIndex * data.oneHeight; }, // 定义下空白高度 BottomBlankFill(data) { return (data.allDatalist.length - data.endIndex) * data.oneHeight; }, // 定义一个 待显示的数组列表元素 showDataList(data) { // console.log(data.allDatalist.slice(data.startIndex, data.endIndex)) return data.allDatalist.slice(data.startIndex, data.endIndex) }, }, [代码] (4)下拉置底自动请求加载数据 下方代码中[代码]setDataStartIndex()[代码]函数末尾的if-else便是判断是否已经滚动到现有全部数据的allDataList数组是否已经滚动到底部,全部呈现完了。 如果是那就执行else部分,请求的页数pageNumNow+1,然后调用addMes()请求数据,然后将新请求到的数据进行拼接到allDataList上 [代码]// 执行数据设置的相关任务, 滚动事件的具体行为 setDataStartIndex(data) { // console.log("scroll active") this.data.startIndex = ~~(data.detail.scrollTop / this.data.oneHeight); this.setData({ showDataList: this.data.allDatalist.slice(this.data.startIndex, this.data.endIndex) }) // 容器最后一个元素的索引 if (this.data.startIndex + this.data.containSize <= this.data.allDatalist.length - 1) { this.setData({ endIndex: (!this.data.allDatalist[this.data.endIndex]) ? this.data.allDatalist.length - 1 : this.data.startIndex + this.data.containSize }) } else { console.log("滚动到了底部"); this.data.pageNumNow++; this.addMes(); console.log(this.data.allDatalist.length) } }, // 根据接口数据来给数组添加真实数据 addMes: function () { this.list() .then(res => { // console.log(res.result.data); // 将接口获取到得所有数据存储起来 this.data.allDatalist = this.data.allDatalist.concat(res.result.data); // 设置初始显示列表 this.setData({ showDataList: this.data.allDatalist.slice(0, 5) }) }) }, // 以下为获取数据 list: function () { let pageNum = this.data.pageNumNow; let pageSize = 20; // console.log('当前请求的页码为:' + pageNum); return new Promise((resolve, reject) => { wx.cloud.callFunction({ name: 'teacher', data: { action: 'list', pageNum, pageSize } }).then(res => { resolve(res) }).catch(err => { reject(err) }) }) }, [代码] 到此处,高性能虚拟列表+分页请求的优化已经搞定了,下面开始节流的优化 (5)滚动事件节流定时器优化 由于监听滚动事件触发对应函数方法的频率是极高的,因此页面节流优化是必须的。 方法:在data中声明一个属性scro11State用来记录滚动状态,只有scro11State值为true的时候才会具体执行 PS:下面代码中定时器设置为17ms的原因是,由于小程序没有web的requestAnimationFrame的api,无法作滚动事件节流请求动画帧优化,因此可以手动计算显示器的帧率,大概一帧在17ms,因此定时器设置为17ms [代码]// 定义滚动行为事件方法 handleScroll(data) { //节流部分代码 if (this.data.isScrollStatus) { this.data.isScrollStatus = false; // 节流,设置一个定时器,1秒以后,才允许进行下一次scroll滚动行为 let mytimer = setTimeout(() => { this.data.isScrollStatus = true; clearTimeout(mytimer); }, 17) //节流部分代码 this.setDataStartIndex(data); } }, [代码] 到这为止,节流也搞定啦,觉得从中学到了许多的小伙伴不妨点个赞。 小程序如何引入computed计算属性请参考我的这篇文章:https://developers.weixin.qq.com/community/develop/article/doc/000a4442bd44c84e740d6b6b051413 在后续我也会将高性能虚拟列表+节流的优化封装成一个插件,给小伙伴们直接使用,欢迎关注我以便及时获取到我文章的更新~ 在这里也推荐一篇防抖和节流的性能优化知识介绍的文章:https://segmentfault.com/a/1190000018428170 觉得有帮助的小伙伴欢迎点赞,有其他问题也欢迎在评论区提出
2021-11-11 - 微信小程序swiper高度自适应,swiper的子元素高度不固定
微信小程序swiper高度自适应,swiper的子元素高度不固定小程序 swiper 组件默认高度150px,并且如果子元素过高,swiper不会自适应高度 解决方案一: (总体来说不够完美,适合满屏滑动)如果不是满屏的状态,用scroll-view IOS滑动兼容性不好,在IOS会有无法滑动的情况 <swiper class="content" style="height:{{height}}px" bindchange="change" current-item-id="{{docid}}" duration="100" > <swiper-item data-key="{{item.id}}" wx:for="{{title}}" wx:key="index" item-id="{{item.id}}" > <scroll-view data-id="{{item.id}}" style='height:100%;' scroll-y bindscrolltolower="scrolltolower" data-left="51" scroll-top="{{top}}" bindscroll="scroll" > <!--单条新闻start --> <navigator url="/pages/detail/detail?id={{item.docid}}" class="item" wx:for="{{item.id==docid?news:''}}" wx:key="index"> </navigator> <!--单条新闻end --> <view class='loading'>加载中...</view> </scroll-view> </swiper-item> </swiper>适应场景: [图片] 适合这种满屏滑动的,嵌套 scroll-view 注意: <scroll-view> 需要有条件,写固定的高,纵向滑动scroll-y 横向滑动 scroll-x white-space:nowrap; 解决方案二: (适应子元素高度也不一致) 效果图是这样的:然后在上滑过程中,导航栏还需要吸顶,然后滑动下方tab栏的内容 [图片][图片] 其实如果不是基于小程序,我们可以很直接用swiper插件,操作起来简直方便,小程序由于 swiper 高的限制,真是走了不少弯路,如果下面的列表高度一样,我们便可以算出一个的高度,然后乘以个数即可,但是这样只能求出每一个个数 index .wxml <swiper current="{{current}}" bindchange="change" duration="300" style="height:{{swiper_height + 80}}px;min-height:50%vh;"> <swiper-item class="swiper-item" wx:for="{{channel_list}}" wx:key="{{item.id}}"> <!-- navigator 的类名很重要,虽然一个循环用统一样式,但是我们基于不同的tab 取了不用的类名 ,因为小程序无法操作DOM元素,虽然封装的API 可以获取,但是只能获取第一个和所有,我们每个tab的内容个数不一样,所以需要基于每个tab栏求,item.channelId 是图2标注吸顶效果的channelId, --> <navigator class="column-list column-list{{item.channelId}}" url="" wx:for="{{item.viewLessonList}}" wx:for-item="lesson" wx:key="{{index}}" wx:key-item="lesson-item"> 这里面便是一个一个不同高度的列表 </navigator> <!-- 这下面就是weui的一个加载样式,基于分页加载做的不同样式 --> <view class="weui-loadmore" hidden="{{is_loadmore}}"> <view class="weui-loading"></view> <view class="weui-loadmore__tips">正在加载</view> </view> <view class="weui-loadmore weui-loadmore_line" hidden="{{!is_loadmore}}"> <text class="weui-loadmore__tips">左右滑动,查看更多</text> </view> </swiper-item> </swiper> [图片] index.js // 获取wxml的节点信息 function get_wxml(className, callback) { wx.createSelectorQuery().selectAll(className).boundingClientRect(callback).exec() } onReady: function() { let column_all = that.data.column_list[that.data.current].viewLessonList, // 这个是基于java端返回的tab栏的接口,大致样式如上图,也就是每个列表, channel_id = that.data.column_list[that.data.current].channelId // 我们这个就是求出目前的channelId,好区分不同的类名 that.setData({ swiper_length: column_all.length // 算出当前tab栏有多少个列表 }) get_wxml(`.column-list${channel_id}`, (rects) => { let sum_heigth = 0 for (let i = 0; i < that.data.swiper_length; i++) { sum_heigth += rects[i].height } that.setData({ swiper_height: sum_heigth }) // 就是循环相加每个列表的高度,然后赋值给swiper_height,便可以求出当前tab栏的高度,赋值给swiper 便可以swiper高度自适应 }) }
2018-08-08 - 【能力体验】“后台持续定位”接口的使用与踩坑
在小程序基础库 v2.8.0 版本中,新增了小程序后台持续定位功能,没错就是这个接口 wx.startLocationUpdateBackground(Object object)。对于一些有这方面需求的项目来说,这无疑是个好消息!刚好我有个项目刚好需要用到这个功能,这里就分享一下使用的体验吧。 [图片] 一、使用前提醒 要使用这个能力,官方文档有以下提醒: 1、基础库 2.8.0 开始支持,低版本需做兼容处理。 2、调用前需要 用户授权 scope.userLocationBackground (需要写一个button供用户点击) 除此之外,你还需要知道: 这个接口仅支持在真机上调试! 因此写代码时可以先用wx.startLocationUpdate(前台时接收位置消息)替代,在运行没问题后再换回并在真机测试。 二、使用时感受 微信团队在公众号里说“满足线路导航、路线记录等服务场景下,小程序需要长时间持续定位来提供服务”,但在实际情况中是,小程序很难实现所谓的“长时间”(有时甚至不到5分钟)。这样就会导致我们记录的数据还没来得及上传就丢失了。 当我发现这个问题,第一时间是抱怨为什么小程序销毁前不能给我们返回一个提醒。但思考后才理解:微信APP自身都不能确保“长时间”在后台运行,还怎么样保证小程序的“长时间”运行呢?(如被一些软件清理了) 三、使用后经验 既然无法避免突发性关闭,又不宜频繁上传地点数据。那么,我们就只能用到缓存了!以实现类似断线重连的功能。 [图片] 实现效果: [图片] 代码:(仅供参考) [代码] var points_map = [ ] // 实时绘制地图 var points_yun = [ ] // 云开发需要的格式 var point Page({ data: { polyline: [{ points: points_map, color: '#FFA500', width: 3 }], }, onLoad: function (options) { var that = this wx.getStorage({ key: 'TimeStamp', success(res) { console.log('有缓存') wx.getStorage({ key: 'points_yun', success(res) { points_yun = res.data } }) wx.showModal({ content: '检测到您有一个未完成的巡护记录!', cancelText: '不保存', cancelColor: '#DC143C', confirmText: '继续巡护', confirmColor: '#228B22', success(k) { if (k.confirm) { console.log('用户点击确定-继续巡护') that.setData({ TimeStamp: res.data }) that.GoContinue() } else if (k.cancel) { console.log('用户点击取消-不保存') wx.removeStorage({ key: 'TimeStamp' }) //删缓存 wx.removeStorage({ key: 'points_yun' }) points_yun = [] } } }) }, fail(res) { console.log('无缓存') points_yun = [] } }) }, Go: function () { // 开始巡护(首次) var that = this wx.startLocationUpdateBackground({ success(res) { var TimeStamp = (new Date()).valueOf() that.GetNowGeo() wx.setStorage({ //设置缓存(TimeStamp) key: "TimeStamp", data: TimeStamp }) }, fail() { // 这里弹窗引导用户授权使用地理位置 } }) }, GoContinue: function () { // 开始巡护(再续) var that = this wx.startLocationUpdateBackground({ success(res) { that.GetNowGeo() }, fail() { // 这里弹窗引导用户授权使用地理位置 } }) }, End: function () { var that = this wx.stopLocationUpdate() clearInterval(this.data.setInter) wx.showModal({ title: '', content: '是否要上传数据?', success(res) { if (res.confirm) { that.updateGeo() } } }) }, GetNowGeo: function () { var that = this wx.onLocationChange(function (res) { point = { latitude: res.latitude, longitude: res.longitude } }) this.data.setInter = setInterval(function () { if (points_map.length == 0) { points_yun.push([]) } points_map.push(point); //画地图 that.setData({ 'polyline[0].points': points_map }) var n = [point.longitude, point.latitude] // 云开发数据库需要的格式 var r = points_yun.length - 1 points_yun[r].push(n) wx.setStorage({ // 设置缓存(路程数据) key: "points_yun", data: points_yun }) }, 6000); }, updateGeo: function () { var that = this wx.showLoading({ title: '数据上传中', }) db.collection('patrol_geo').add({ // 云开发上传 data: { location: { type: 'MultiLineString', coordinates: points_yun } }, success: res => { wx.showToast({ title: '上传成功' }) wx.removeStorage({ key: 'TimeStamp' }) // 清理缓存 wx.removeStorage({ key: 'points_yun' }) }, fail: res => { wx.showToast({ title: '上传失败,请重新操作!' }) console.log(res) } }) }, }) [代码]
2019-09-26 - 一个企业营业执照可以注册多少公众号?
公司业务需求,需要不同的团队,维护公众号,一个团队一个公众号,请问企业营业执照可以申请认证多少个?
2019-06-28