- 小程序基础库 3.0.0 更新
各位微信开发者: 小程序基础库 3.0.0 已经开始灰度开发者,请大家基于业务情况关注相关变更。如遇问题请及时在该帖下方留言或在小程序交流专区发表标题包含「基础库3.0.0」的帖子反馈。 本次更新正式发布以下三大特性: 1、Skyline 渲染引擎发布正式版 为了进一步优化小程序性能,提供更为接近原生的用户体验,我们推出了一套新渲染引擎 Skyline。在经过近一年的 beta 版测试后,Skyline 已经趋于稳定。我们修复了大量问题并进行了诸多性能优化,使线上的小程序能够稳定运行且性能表现更优。此外,为了让开发者能更快迁移,我们支持了大多数常用的 CSS 特性,同时还添加了许多高级特性,以帮助开发者构建类原生体验的小程序。更多详细的信息请查阅 介绍文章。 2、XR-FRAME 发布正式版 经过迭代,我们补齐了XR-FRAME的许多基础能力,并针对稳定性等问题做了针对性的优化。XR-FRAME是一个基于小程序开发方案、高性能、渐进式的3D/XR开发框架。开发者可以非常简单得使用WXML便可构造出一个酷炫的3D小程序,并且还广泛支持了AR、物理、交互、粒子、后处理、视频等等能力,同时也能够满足服务商等高级用户各种进阶的定制需求。我们仍在不断迭代新功能,跟着这篇文档可以了解XR-FRAME框架并开始你的开发:开始入门。 3、推出新版组件框架 glass-easel glass-easel 是新一代的小程序页面和自定义组件框架,旨在替代老旧的组件框架,提供更好的性能和更多的特性。现在,我们率先在 Skyline 环境下引入,成为默认的组件框架。glass-easel 几乎完整兼容了旧版框架,仅有极个别的接口被废弃移除,在提升性能的同时,添加了诸多特性,如 Chaining API、动态 slot、在模板中调用 data 里的函数等,更多详细的信息请 查阅文档。 更多更新内容: 新增 框架 新增 wx.getCommonConfig 接口 详情新增 组件 scroll-view 支持下拉二楼交互 详情更新 框架 glass-easel 在兼容模式下运行时使用 wxs 事件响应函数的 ComponentDescriptor#getState 方法 更新 框架 scroll-view 支持 min-drag-distance 属性 详情更新 框架 video 组件遮罩逻辑导致全屏投屏按钮无法点击 详情更新 框架 sticky-header 支持 top 偏移 详情更新 框架 skyline 支持 css animation 事件 更新 框架 启动页无法绑定自定义路由 详情更新 框架 更新 scroll-view / grid-view / list-view / sticky-header / sticky-section 组件支持 padding 属性,设置组件内部的内边距 详情 更新 框架 XR-FRAME VideoTexture 发布正式版 详情更新 框架 Skyline 渲染引擎下,组件框架切换为 glass-easel 详情更新 组件 skyline button 组件 loading 属性添加动画 更新 API 基础库支持 visionkit depth 功能 详情修复 框架 scroll-view 封装成组件时 scroll-into-view 无法跳转 修复 框架 skyline 内存泄漏问题修复 框架 skyline input/textarea 组件获取焦点相关问题 修复 框架 skyline 下部分组件事件无法使用 wxs 函数响应的问题 修复 组件 video 视频遮罩报错修复
2023-07-19 - 【物流-查询组件】1000+快递公司物流信息免费接入
快递100“快递跟踪”微信小程序插件上线啦! 帮助所有小程序解决快递物流查询问题,现面向所有第三方小程序、小程序开发服务商(包括个体小程序)免费开放。 目前已经有1000家小程序申请接入了快递100【快递跟踪】插件, 点链接→https://fuwu.weixin.qq.com/service/detail/00008caeab84c07c17dcdabf55b815,立即添加插件(添加时请从电脑端打开链接) 公开数据显示,今年上半年微信小程序数量已超过430万。 随着小程序生态不断发展,越来越多商家和开发者在小程序上建立自有商城。大到京东这样的巨型平台,小到一个公众号、博主自己开的店铺,用户都可以在小程序上下单。业务逐渐壮大后,物流却成为困扰不少商家和开发者的一大难题。 大平台有大量的资金和人力来调配资源,自主开发接入物流公司系统,给顾客及时物流反馈;而对于那些中小店铺的小程序商家们来说,没有足够人力、财力支撑,无法自主开发接入。 近日,中国领先的快递物流信息服务商快递100宣布,正式上线“快递跟踪”小程序功能插件,开放快递物流信息查询模块,允许第三方小程序 免费 接入。 “快递跟踪”小程序插件整合了快递100快递查询能力,支持全球1000+快递物流公司信息查询,对全行业的小程序免费开放接入,包括电商平台、商家、医药寄送、信息查询或本地生活服务平台等 任何有物流查询需求的小程序开发者,为企业、商家、个体小程序赋能。 点链接→https://fuwu.weixin.qq.com/service/detail/00008caeab84c07c17dcdabf55b815,立即添加插件 01 无门槛免费接入 无论是电商商城,还是社群团购、回收类等任何有涉及快递物流环节的小程序,物流信息查询是必须重视的一项服务。卖家是否能提供及时的物流信息更新服务,会影响到用户的二次购买决策。 “快递跟踪”小程序插件,是免费接入。接入插件后,用户只要在小程序内点击快递单号,就可以查看最新物流信息,有效提升用户的购物体验,提高小程序的回访率和复购转化率。 02 原生体验,无第三方跳转 快递100“快递跟踪”插件依托微信小程序生态,第三方小程序接入后无需任何跳转,在自己的小程序内即可直接查看物流信息,简化用户操作流程。 [图片] 接入方式也非常简单快捷,模板化快速接入,无需再次开发,几个小时即可完成接入,大大降低开发和运营成本。 快递100“快递跟踪”插件开放接入,不仅能够帮助小程序开发者降低物流服务的开发门槛和成本,同时也为小程序商家提供了更好服务用户的方式。 03 支持国内外1000+家快递公司物流查询 通过快递100“快递跟踪”小程序插件,支持全网快递物流查询,可查看国内、国际1000+快递物流公司的信息,同时还提供官方客服热线。 “快递跟踪”插件服务稳定,让商家、开发者管理更加方便。 除了常见的电商场景,“快递跟踪”插件同样非常适合有特定物品物流信息查询需求的机构和企业接入 —— 例如医院类公众号,病历档案预约寄出后的进度查询;驾校机构,寄出驾照后的进度查询;校园机构的报到证、档案等资料的快递查询等。 点链接→https://fuwu.weixin.qq.com/service/detail/00008caeab84c07c17dcdabf55b815,立即添加插件 另外,快递100也可提供快递信息推送、实时快递、地图轨迹API等服务,点这里→https://api.kuaidi100.com/ 了解详情 快递100是中国领先的快递物流信息服务商,国家高新技术企业、新基建代表企业。 快递100目前拥有个人注册用户1.6亿,企业客户60万+,日均查询量3亿次,是国内查询量最大的快递物流信息查询平台;年寄件量超8亿单,寄件功能官方合作京东、邮政、德邦、圆通、韵达、DHL、TNT、UPS等多家国内外快递公司。 快递100致力构建中国最大的物流信息服务枢纽,始终秉承开放态度与快递行业共创共赢,为用户、商家、企业提供专业、可靠的服务,实现互联互通互动。 点链接→https://fuwu.weixin.qq.com/service/detail/00008caeab84c07c17dcdabf55b815,立即添加插件
2021-11-26 - voip-room 使用参考
voip-room给我调到头大, 于是放出代码供各位参考. 示例为uniapp, 语法为vue, 原生小程序请自行替换. 首先是voip-room组件的使用, 要注意mode的设置, 如果渲染的 [代码]voip-room[代码] 组件的openid属性对应自己的openid则mode属性要设为camera, 如果不是要设置为video. [代码]<!-- wxml --> <voip-room class="video" v-for="item in openid_list" :key="item" :openid="item" :mode="my_openid == item ? 'camera': 'video'"/> [代码] 相应的js代码如下 [代码]getOpenId()[代码] 函数用于获取自己的openid 很简单自己去写 [代码]getOpenIdList()[代码] 封装了[代码]wx.joinVoIPChat[代码]返回值为房间中的人的openid列表 很简单, 同样自己去写 最后别忘记退出房间时调一下[代码]wx.exitVoIPChat[代码] [代码]// js async joinRoom(){ // 获取自己的openid; const my_openid = await getOpenId(); this.my_openid = my_openid.openid; // 加入房间并获取openid列表 const result = await getOpenIdList(cache.join_param); this.openid_list = result.openIdList; // 订阅视频成员, 避免人超过两个显示不了视频. wx.subscribeVoIPVideoMembers({ openIdList: result.openIdList, success(res){ console.log('subscribeVoIPVideoMembers ok', res); }, fail(err){ console.log('subscribeVoIPVideoMembers fail', err); } }) // 成员变化时修改openid列表; wx.onVoIPChatMembersChanged((result) => { console.log('member change', result); this.openid_list = result.openIdList; }) // 视频成员变化时重新订阅视频成员 wx.onVoIPVideoMembersChanged((result)=>{ wx.subscribeVoIPVideoMembers({ openIdList: result.openIdList, success(res){ console.log('subscribeVoIPVideoMembers ok', res); }, fail(err){ console.log('subscribeVoIPVideoMembers fail', err); } }) }) } [代码]
2021-07-31 - 一个标准的隐私协议模板如何填写
一个标准的隐私协议模板如何填写 ~ 近期审核都需要填写隐私协议,很多人都不知道从何处填写,现提供一个标准模板,可供大家参考 ~ [图片] ~ [图片] ~ 值得注意的是: 即使没有收集用户信息也建议如此操作,方可审核通过,这是经验贴,血淋淋的教训每天都还在发生 [图片] ~
2021-11-08 - [打怪升级]小程序自定义头部导航栏“完美”解决方案
[图片] 为什么要做这个? 主要是在项目中,智酷君发现的一些问题 一些页面是通过扫码和订阅消息访问后,没有直接可以点击去首页的,需要添加一个home链接 需要添加自定义搜索功能 需要自定义一些功能按钮 [图片] 其实,第一个问题,在最近的微信版本更新中已经优化了,通过 小程序模板消息 过来的,系统会自动加上home按钮,但对于其他的访问方式则没有支持~ 一个不大不小的问题:两边ICON不对齐问题 [图片] 智酷君之前尝试了各种解决方法,发现有一个问题,就是现在手机屏幕太多种多样,有 传统头部、宽/窄刘海屏、水滴屏等等,无法八门,很多解决方案都无法解决特殊头部,系统**“胶囊按钮”** 和 自定义按钮在Android屏幕可能有 几像素不对齐 的问题(强迫症的噩梦)。 下面分享下一个相对比较完善的解决方案: [图片] 小程序代码段DEMO Link: https://developers.weixin.qq.com/s/cuUaCimT72cH ID: cuUaCimT72cH 智酷君做了一个demo代码段,方便大家直接用IDE工具查看源码~ [图片] 页面配置 1、页面JSON配置 [代码]{ "usingComponents": { "NavComponent": "/components/nav/common" //以插件的方式引入 }, "navigationStyle": "custom" //自定义头部需要设置 } [代码] 如果需要自定义头部,需要设置navigationStyle为 “custom” 2、页面代码 [代码]<!-- home 类型的菜单 --> <NavComponent v-title="自定义头部" bind:commonNavAttr="commonNavAttr"></NavComponent> <!-- 搜索菜单 --> <NavComponent is-search="true" bind:commonNavAttr="commonNavAttr"></NavComponent> [代码] 可以在自定义导航标签上添加属性配置来设置功能,具体按照实际需要来 3、目录结构 [代码]│ ├─components │ └─nav │ common.js │ common.json │ common.wxml │ common.wxss │ ├─images │ back.png │ home.png │ └─index index.js index.json index.wxml index.wxss search.js search.json search.wxml search.wxss [代码] 仅供参考 插件对应的JS部分 components/nav/common.js部分 [代码]const app = getApp(); Component({ properties: { vTitle: { type: String, value: "" }, isSearch:{ type: Boolean, value: false } }, data: { haveBack: true, // 是否有返回按钮,true 有 false 没有 若从分享页进入则没有返回按钮 statusBarHeight: 0, // 状态栏高度 navbarHeight: 0, // 顶部导航栏高度 navbarBtn: { // 胶囊位置信息 height: 0, width: 0, top: 0, bottom: 0, right: 0 }, cusnavH: 0, //title高度 }, // 微信7.0.0支持wx.getMenuButtonBoundingClientRect()获得胶囊按钮高度 attached: function () { if (!app.globalData.systeminfo) { app.globalData.systeminfo = wx.getSystemInfoSync(); } if (!app.globalData.headerBtnPosi) app.globalData.headerBtnPosi = wx.getMenuButtonBoundingClientRect(); console.log(app.globalData) let statusBarHeight = app.globalData.systeminfo.statusBarHeight // 状态栏高度 let headerPosi = app.globalData.headerBtnPosi // 胶囊位置信息 console.log(statusBarHeight) console.log(headerPosi) let btnPosi = { // 胶囊实际位置,坐标信息不是左上角原点 height: headerPosi.height, width: headerPosi.width, top: headerPosi.top - statusBarHeight, // 胶囊top - 状态栏高度 bottom: headerPosi.bottom - headerPosi.height - statusBarHeight, // 胶囊bottom - 胶囊height - 状态栏height (胶囊实际bottom 为距离导航栏底部的长度) right: app.globalData.systeminfo.windowWidth - headerPosi.right // 这里不能获取 屏幕宽度,PC端打开小程序会有BUG,要获取窗口高度 - 胶囊right } let haveBack; if (getCurrentPages().length != 1) { // 当只有一个页面时,并且是从分享页进入 haveBack = false; } else { haveBack = true; } var cusnavH = btnPosi.height + btnPosi.top + btnPosi.bottom // 导航高度 console.log( app.globalData.systeminfo.windowWidth, headerPosi.width) this.setData({ haveBack: haveBack, // 获取是否是通过分享进入的小程序 statusBarHeight: statusBarHeight, navbarHeight: headerPosi.bottom + btnPosi.bottom, // 胶囊bottom + 胶囊实际bottom navbarBtn: btnPosi, cusnavH: cusnavH }); //将实际nav高度传给父类页面 this.triggerEvent('commonNavAttr',{ height: headerPosi.bottom + btnPosi.bottom }); }, methods: { _goBack: function () { wx.navigateBack({ delta: 1 }); }, bindKeyInput:function(e){ console.log(e.detail.value); } } }) [代码] 解决不同屏幕头部不对齐问题的终极办法是 wx.getMenuButtonBoundingClientRect() 这个方法从微信7.0.0开始支持,通过这个方法我们可以获取到右边系统胶囊的top、height、right等属性,这样无论是水滴屏、刘海屏、异形屏,都能完美对齐右边系统默认的胶囊bar,完美治愈强迫症~ APP.js 部分 [代码]//app.js App({ /** * 加载页面 * @param {*} options */ onShow: function (options) { }, onLaunch: async function () { let self = this; //设置默认分享 this.globalData.shareData = { title: "智酷方程式" } // this.getSysInfo(); }, globalData: { //默认分享文案 shareData: {}, qrCodeScene: false, //二维码扫码进入传参 systeminfo: false, //系统信息 headerBtnPosi: false, //头部菜单高度 } }); [代码] 将获取的参数存储在一个全局变量globalData中,可以减少反复调用的性能消耗。 插件HTML部分 [代码]<view class="custom_nav" style="height:{{navbarHeight}}px;"> <view class="custom_nav_box" style="height:{{navbarHeight}}px;"> <view class="custom_nav_bar" style="top:{{statusBarHeight}}px; height:{{cusnavH}}px;"> <!-- 搜索部分--> <block wx:if="{{isSearch}}"> <input class="navSearch" style="height:{{navbarBtn.height-2}}px;line-height:{{navbarBtn.height-4}}px; top:{{navbarBtn.top+1}}px; left:{{navbarBtn.right}}px; border-radius:{{navbarBtn.height/2}}px;" maxlength="10" bindinput="bindKeyInput" placeholder="输入文字搜索" /> </block> <!-- HOME 部分--> <block wx:else> <view class="custom_nav_icon {{!haveBack||'borderLine'}}" style="height:{{navbarBtn.height}}px;line-height:{{navbarBtn.height-2}}px; top:{{navbarBtn.top}}px; left:{{navbarBtn.right}}px; border-radius:{{navbarBtn.height/2}}px;"> <view wx:if="{{haveBack}}" class="icon-back" bindtap='_goBack'> <image src='/images/back.png' mode='aspectFill' class='back-pre'></image> </view> <view wx:if="{{haveBack}}" class='navbar-v-line'></view> <view class="icon-home"> <navigator class="home_a" url="/pages/home/index" open-type="switchTab"> <image src='/images/home.png' mode='aspectFill' class='back-home'></image> </navigator> </view> </view> <view class="nav_title" style="height:{{cusnavH}}px; line-height:{{cusnavH}}px;"> {{vTitle}} </view> </block> </view> </view> </view> [代码] 主要是对几种状态的判断和定位的计算。 插件CSS部分 [代码]/* components/nav/test.wxss */ .custom_nav { width: 100%; background: #3a7dd7; position: relative; z-index: 99999; } .custom_nav_box { position: fixed; width: 100%; background: #3a7dd7; z-index: 99999; border-bottom: 1rpx solid rgba(255, 255, 255, 0.3); } .custom_nav_bar { position: relative; z-index: 9; } .custom_nav_box .nav_title { font-size: 28rpx; color: #fff; text-align: center; position: absolute; max-width: 360rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; top: 0; left: 0; right: 0; bottom: 0; margin: auto; z-index: 1; } .custom_nav_box .custom_nav_icon { position:absolute; z-index: 2; display: inline-block; border-radius: 50%; vertical-align: top; font-size:0; box-sizing: border-box; } .custom_nav_box .custom_nav_icon.borderLine { border: 1rpx solid rgba(255, 255, 255, 0.3); background: rgba(0, 0, 0, 0.1); } .navbar-v-line { width: 1px; margin-top: 14rpx; height: 32rpx; background-color: rgba(255, 255, 255, 0.3); display: inline-block; vertical-align: top; } .icon-back { display: inline-block; width: 74rpx; padding-left: 20rpx; vertical-align: top; /* margin-top: 12rpx; vertical-align: top; */ height: 100%; } .icon-home { /* margin-top: 8rpx; vertical-align: top; */ display: inline-block; width: 80rpx; text-align: center; vertical-align: top; height: 100%; } .icon-home .home_a { height: 100%; display: inline-block; vertical-align: top; width: 35rpx; } .custom_nav_box .back-pre, .custom_nav_box .back-home { width: 35rpx; height: 35rpx; vertical-align: middle; } .navSearch { width: 200px; background: #fff; font-size: 14px; position: absolute; padding: 0 20rpx; z-index: 9; } [代码] 总结: 通过微信API: getMenuButtonBoundingClientRect(),结果各类手机屏幕的适配问题 将算好的参数存储在全局变量中,一次计算全局使用,爽YY~ 往期回顾: [填坑手册]小程序PC版来了,如何做PC端的兼容?! [填坑手册]小程序Canvas生成海报(一) [拆弹时刻]小程序Canvas生成海报(二)
2021-09-13 - 小程序仿instagram交互效果实现(附长列表优化处理)
需求 最近几天在忙着搞公司项目的一个新的需求,原因是这样的:公司准备开发一个偏向于社交娱乐项的小程序,其中首页是可以看到用户发的话题帖子之类的,每个帖子都至少包含一张图片或者一个视频, 然后产品那边希望首页可以实现instagram的交互效果,效果图如下(本来应该是显示图片,奈何我的gif图片大小超过两兆,不能上传,所以我就用表情包替换了): [图片] 嗯,大致上这个就是需求的背景,然后就是每个帖子的高度是不确定的,高度大概在500~600px之间。 实现思路 一开始接到这个需求,其实我心里还是有点慌的,毕竟有一段时间不怎么接触小程序,也不知道小程序更新到什么程度,文档更新到什么程度。仔细分析一下项目需求,大致上可以归类为两个:交互 和 性能优化。 性能优化 因为首页是一个长列表,众所周知,页面一旦渲染的节点过多,就会卡顿,更何况是小程序,并且小程序是分为逻辑层和渲染层,两者通过setData链接,所以处理的时候需要注意两点: setData的数据量不能太大,记得好像是有个大小限制,,忘了是多少,也懒得找:clown_face:,你们可以自己在官方文档上找一下; 页面能够渲染的帖子数量是有限的,在这里,我是控制为最多渲染25个帖子。 处理 针对于长列表的优化,官方也有相应的组件-recycle-view,但是貌似并不符合项目需求,所以被我pass掉了。 虽然没用官方的组件,但是在组件的文档里面把对于长列表得性能优化解释一遍,这里摘抄一下重点: 核心的思路就是只渲染显示在屏幕的数据,基本实现就是监听 scroll 事件,并且重新计算需要渲染的数据,不需要渲染的数据留一个空的 div 占位元素。 其实也就是设置一个变量控制该数据是否可以渲染,如果是不能够渲染得话,那我们就用一个空的view取代它,需要注意的失败的是:空的框架高度需要设置为帖子的高度,这样子才不会闪屏。 针对这种思路,我们就可以确定其中一种长列表的性能优化的解决思路: 将数据分为二维数组,这样子就可以限制每次setData的数据量,等待数据渲染完成之后,获取每组数据所占用的总高度,这里的高度是为了在改组数据不渲染时设置占位框的高度。 [代码]/** * 获取 有渲染,但是高度还没获取到的分组 的高度 */ _getGroupListHeight() { this.data.list.forEach((item, index) => { if (item.show && !item.height) { const id = 'XXXXXXXX' // 组的id let query = wx.createSelectorQuery() query.select(id).boundingClientRect(rect => { this.data.list[index].height = rect.height }).exec(); this.getTopicHeight(item.data, index) // 获取列表中每个话题的高度,用于计算滑动时要滚动的距离 } }) } [代码] 上面就是一个简单获取每组的高度的代码实例,当该组数据有被渲染但是高度不明的情况下,就会去获取,加一步判断是为了防止重获获取组的数据,造成不必要的浪费。在获取每组数据的高度时,还会对应去 获取该组的每个帖子的高度,这样子是为了后面实现 仿instagram 交互做准备。 获取到了每组数据的高度,接下来,我们就可以监听页面滚动的高度,从而控制需要渲染的数据,需要注意的一点是,我们需要在该组的数据基础上,多渲染上两组和下两组数据,目的是防止用户快速滑动的时候出现白屏的不友好体验。当然也可以根据自己的需要多渲染几组。 [代码]/** * 页面滚动 * @param e */ onPageScroll(e) { // android页面滑动处理(非仿instagram版本) if (!this.data.isIos && !this.data.scrollBoxInfo.canUseScrollBox) { // 1. 处理当前页面正在播放的视频 if (this.data.currentPlayingId && Math.abs(e.scrollTop - this.data.scrollTop) > 100) { this.selectComponent(this.data.currentPlayingId).pauseVideo() this.data.currentPlayingId = '' } // 2. 处理 Andorid 渲染的分组数据 this._dealAndroidScroll(e) // 3. 处理视频自动播放 if (this.timer) { clearTimeout(this.timer) } this.timer = setTimeout(() => { this.data.scrollTop = e.scrollTop // 记录下当前的滚动距离,滑动暂停视频播放的时候需要用到 this.handleAutoPlay(e) // 视频自动播放 }, 300) } } /** * Android 监听滚动,动态设置分组 * @param {Object} e */ _dealAndroidScroll(e) { let max_height = 0 // 最大高度 for (let i = 0; i <= this.data.topicScroll.show_index; i++) { max_height += this.data.list[i].height } let min_height = max_height - this.data.list[this.data.topicScroll.show_index].height // 最小高度 // 超过,+1 if (e.scrollTop > max_height && this.data.topicScroll.show_index < this.data.list.length - 1) { ++this.data.topicScroll.show_index this._dealListShow(this.data.topicScroll.show_index) } // 小于,-1 if (e.scrollTop < min_height) { --this.data.topicScroll.show_index this._dealListShow(this.data.topicScroll.show_index) } } [代码] 这里没有对代码进行过滤,代码实现的效果就是监听滚动到的位置,并且设置渲染分组数据,因为有些是视频帖子,所以需要在滚动完成之后实现自动播放视频的功能。这段代码只是处理anroid平滑滚动的情况,因为代码涉及到 instagram 交互的实现。 对于长列表性能优化的思路大致上就这样:数据分为二位数组,渲染指定分组的数据,减少渲染的数据量和需要渲染的节点数量, 不需要渲染的数据就用指定高度的空的view替代,指定高度是为了防止闪屏。 仿 instagram 交互实现 从上面的 instagram 交互视频可以看出来,我们需要监听用户的手势滑动从而控制帖子的切换,并且每次切换只是切换一个帖子。知道了交互的详情,我们就可以展开想象了,大致上可以给个基本的实现思路: 监听手指点击和手指离开的事件,记录下手指点击的高度 && 手指离开的高度,用来判断用户滑动的距离和方向;记录下手指点击 和 手指离开的时间,可以粗略用来判断用户当前的滑动行为是快滑还是慢滑。根据上面的得到的信息,我们基本上实现滑动切换帖子的操作: [代码]/** * 监听手指点击操作 * @param e */ touchStart(e) { this.data.topicScroll.startTimeStamp = new Date().getTime() // 记录下当前手指点击事件 this.data.topicScroll.startPosition = e.changedTouches[0].clientY // 记录下手指开始点击的位置 }, /** * 手指离开屏幕 * @param e */ touchEnd(e) { const diffTime = new Date().getTime() - this.data.topicScroll.startTimeStamp // 手指离开的时候的时间戳 const clientY = e.changedTouches[0].clientY // 手指离开屏幕的位置 const diffY = Math.abs(clientY - this.data.topicScroll.startPosition) // 手指滑动的距离 const direction = this.data.topicScroll.startPosition - clientY > 0 // 手指滑动的方向,true为向上滑,false为向下滑 const scrollInfo = this.data.topicScroll // 1. 第一个节点手指向下滑动 && 最后一个节点手指向上滑动 不做操作 if (!scrollInfo.parent_index && !scrollInfo.child_index && !direction) { return } // 第一个节点向下滑动不做操作 if (scrollInfo.parent_index === (this.data.list.length - 1) && scrollInfo.child_index === (this.data.list[scrollInfo.parent_index].data.length - 1) && direction) { return } // 最后一个节点向上滑动不做操作 // 2. 根据滑动的方向,判断需要滚动到哪个节点下 const can_move = (diffTime < 100 && diffY > 50) || (diffTime >= 100 && diffY > 80) // 是否可以滑动,手势滑动判断依据 if (can_move) { if (direction) { if (scrollInfo.child_index === 4) { ++scrollInfo.parent_index scrollInfo.child_index = 0 } else { ++scrollInfo.child_index } } else { if (scrollInfo.child_index === 0) { --scrollInfo.parent_index scrollInfo.child_index = 4 } else { --scrollInfo.child_index } } } // 3. 处理滚动 this._dealScroll(can_move ? pausePlayId : '', can_move) } [代码] 根据监听到信息,可以判断是否切换以及切换到那个帖子的操作,所以我们只需要根据面已经获取到的帖子高度就可以计算出来要滚动的高度。 2. 根据第一点的操作,我们就可以实现 仿Instagram 交互效果,但是忽略了致命的一点:页面的惯性滚动。也正是因为这个所谓的“惯性滚动”,我多花了几天的时间去研究交互的实现💥 众所周知,为了使得页面的滑动更加流畅,当我们滑动停止的时候,页面就像会产生惯性一般,自动的滑动一定距离才停下。 安卓下默认有惯性滚动,而在 iOS 下需要额外设置-webkit-overflow-scrolling: touch的样式 而第一点方案实现的大前提是页面不能拥有惯性滚动,否则的话页面无法准确滚动到指定位置。 解决方案 ios的解决方案比较简单,我们只需要设置 -webkit-overflow-scrolling: auto 的样式即可。 比较麻烦的是android的实现,一开始我是上网找了不少的资料去实现取消页面的惯性滚动,毕竟页面滚动的性能是最好的。不过很可惜,我没有找到可行的方案,所以我选择退而求其次,模拟手指的滑动。经过对小程序文档的浏览,我选择可两种比较可能实现的方案:①使用wxs实现手指滑动的效果;②使用scroll-view的fast-deceleration 属性。显而易见,使用wxs方案实现比较复杂,所以我一开始选择scroll-view这个方案。 android实现思路 scroll-view方案实现的思路与ios类似,比较不同的是页面的滚动,因为ios是可以直接使用页面滚动的,即wx.pageScrollTo,而scroll-view则是使用scroll-into-view 或者 scroll-top,为了减少操作,我是直接使用了scroll-top这个属性。正当我信心满满的时候,做出来的效果却是打了我的脸:每次滚动到指定位置的时候都会抖动一下,虽然没有仔细去查明原因,不过 我猜想是页面滚动依然存在一定的惯性滚动,当我们设置了scroll-top的时候,惯性滚动的存在会使得列表偏移位置,最后在偏移回来,这样子看起来就像是抖动了一下。 正当我准备放弃scroll-view这个方案的时候,无意中让我发现了scroll-view的api接口,本着想试一下新接口的心态,我尝试一下使用官方的api接口控制scroll-view滚动,神奇的是,成了!!!! (此处为效果图) 既然scroll-view做出来的效果勉强还可以,我就直接pass后面的wxs方案,毕竟那一块的逻辑应该会比较复杂。 [代码] 因为时使用了scroll-view的api接口,支持的版本库比较高,是2.14.4,虽然大部分的用户可以支持,但还是要兼容少部分的用户,所以android部分我是搞了一个scroll-view版本和平滑滚动版本 fast-deceleration这个特性在开发者工具那里貌似不生效,但是手机预览却是可以,不知道是开发工具的问题还是说只是兼容了部分手机(我这边测试了小米和华为这两款,没更多的手机测试了:clown_face:) [代码] demo 说了这么多,也不一定有人看得下去,demo呈上 结尾 在调试这个效果的时候,遇到不少的坑,其中有一个坑印象巨深,在此记录一下: touch 期间 touchstart 的目标节点被移除,则对应的 touchend 事件会因为没有目标节点而缺失。 遇到这个坑是因为小程序同个页面不允许存在多个视频,所以需要将没有播放的视频使用图片替换,需要播放的视频的时候就替换回来,这样子,就会出现上面的情况,导致无法监听到touchend事件,整个列表停在原地,没有滚动到指定位置。 原文链接 传送门
2021-11-14 - 小程序RSA加密
您好!小程序上是否支持RSA加密算法,能帮忙给个例子出来吗?
2017-04-01 - 用小程序Canvas 2D自定义生成转发图片
前言 小程序原先转发给别人,要不是一张图片,要不是就是截屏,不是很自定义化。 我们要每个用户在不同的页面,转发的内容都不一样,这当然可以直接从服务端实时生成这样 5:4 的图片做转发,毕竟好处是避免了兼容性问题 本示例将会用另外一种思路,从客户端实时生成分享图片,并进行转发。 技术调查 微信小程序中 以 [代码]wx.createCanvasContext[代码] 为代表的 [代码]CanvasContext[代码] (v1) 都从基础库 [代码]2.9.0[代码] 开始,停止维护了 所以我们就使用更加贴近 mdn 上 [代码]Canvas[代码] 来代替 (v2) 这里简称停止维护的版本为 v1, [代码]Canvas[代码] 版本为 v2 值得注意的是 v2 版本中还有一个 [代码]wx.createOffscreenCanvas[代码] 它也可以使用 [代码]OffscreenCanvas.getContext('2d')[代码] 这样的 api 不过它的最低版本为 [代码]2.16.1[代码] 到今天 (2021/05/05) 大部分人用的版本还是 [代码]2.16.1[代码] ([代码]2.17.0[代码] 还在灰度) 所以说,从兼容性的角度出发, 使用 [代码]OffscreenCanvas[代码] 是有很大风险的 而且我出于好奇实验使用了一下,bug 也是一大堆 issue 开始吧 ps: canvas api 不熟的 [代码]mdn[代码] 上看看 绘图前言 rpx 这种单位换算问题 , [代码]wx.getSystemInfoSync[代码] 获取一下屏幕宽,做个换算就行 小程序中的 canvas 2d 不能把它设置 style:display:none ,不然转化成图片时会空白 , 我们可以把它移出可视界面。 层级问题,canvas 和 svg 一样都是后面的覆盖前面的,所以我们可以使用 对[代码]zIndex[代码] 进行排序,来决定每个 [代码]function[代码] 的执行顺序 绘图 ing 圆角矩形可以使用 [代码]arc[代码] api 来解决 圆角图片可以使用 [代码]clip[代码] + [代码]drawImage[代码] 比如在这里因为我们需要使用微信的头像 所以需要把 图片的域名 在后台配置一下 这里贴一段 uni 下,在 ctx 中绘制圆角头像的代码 [代码]async addAvatar() { // 下载到本地 const [err, res] = await uni.getImageInfo({ src: avatarUrl }) const { path, type } = res const img = canvas.createImage() img.src = path // 直接设置 width height 是不生效 const offsetX = 20 * dpr const offsetY = 20 * dpr const r = (50 * dpr) / 2 const circle = { x: offsetX + r, y: offsetY + r, r: r } // 这样写是因为最后绘制的时候是执行一个 ()=> Promise<any> // 这样可以确保生成的时候,图片已经 onload了 await new Promise((resolve, reject) => { img.onload = () => { ctx.save() // 切圆角 ctx.beginPath() ctx.arc(circle.x, circle.y, circle.r, Math.PI * 2, false) ctx.clip() // 画成指定的长和高 ctx.drawImage(img, offsetX, offsetY, 50 * dpr, 50 * dpr) ctx.restore() resolve() } img.onerror = event => { reject(event) } }) } [代码] 图片的丰富度 -> svg 的引入 在上面,我们已经把图片的,渐变,排版,文字,依赖的图片全部绘制上去了 然而这时候我们想加一些丰富度,但是不想使用本地图片去那种,缝合的事情,毕竟这样也有失水准。 这时候我们自然而然的想到了 [代码]SVG-to-canvas parser[代码] 目前在 [代码]npm[代码] 主要有 [代码]fabric[代码] / [代码]canvg[代码] 这些 这里我选用的是 基于 [代码]canvg[代码] 的 [代码]svg2canvas[代码] 这样我们在 [代码]iconfont[代码] 就可以挑选一些 [代码]svg[代码] 下载下来,然后通过 [代码]converter[代码] 就可以直接转换成 [代码]canvas js code[代码] 使用了。 当然,转化实际上是不完美的,定位和大小,我们可以改造绘制的 [代码]draw function[代码] 在其中加入 [代码]ctx.translate(x, y) ctx.scale(dpr / 2, dpr / 2) [代码] 颜色什么当然都可以作为参数 这样,我们的 [代码]svg[代码] 就顺利的加了上去 onShareAppMessage 转发 [代码]onShareAppMessage[代码] 是支持 [代码]promise[代码] 的,不过 [代码]promise[代码] 三秒内不 resolve , 会自动 [代码]fallback[代码] 见 onShareAppMessage 文档 这时候,我们就可以写出下列代码 [代码]onShareAppMessage() { const createPromise = async () => { try { this.shareBtnLoading = true // 获得图片的临时路径 const loaclPath = await aWayToGetCanvasTempFilePath() return { title: 'hello, i am icebreaker', path: 'resolve path', imageUrl: loaclPath } } catch (e) { console.warn(e) } finally { this.shareBtnLoading = false } } return { title: 'fallback title', promise: createPromise(), path: 'fallback path', imageUrl: 'fallback imageUrl' } } [代码] 获得图片的临时路径,可以使用 [代码]wx.canvasToTempFilePath[代码] api 这个api是 画布 v1 版本 和 v2版本通用的 不过 v1 版本传入的是 [代码]canvas-id[代码] v2 则是 [代码]canvas[代码] 组件实例 生成速度 在不同的 dpr 下,时间都很快 真机在 [代码]300ms[代码] 左右 远小于 [代码]3000ms[代码] 自动 fallback 的限制 效果视频 在知乎上, 链接地址
2021-05-05 - 小程序原生实现左滑抽屉菜单
最近研究了一下如何在原生框架中实现滑动抽屉菜单效果,本来以为很麻烦,结果发现其实几十行代码就可以解决,不需要任何框架或插件,而且可以类比实现很多灵活的效果。感觉现在网上相关资料较少,因此在此分享一下。 这里实现了三种常见效果,可以点击链接在小程序开发工具中预览效果、查看代码片段。 A 菜单在上层 A2 菜单在上层,下层遮罩 B 菜单在下层 [图片] [图片] [图片] 如果需要更细致的讲解可以移步我的博客查看,希望对你有所帮助。
2021-07-29 - 使用分页和空白view支撑解决Dom limit exceeded 问题
列表页数据量过大页面渲染节点过多会触发Dom limit exceeded . 解决方案如下。 欢迎讨论交流 [代码]// index.js[代码] [代码]Page({[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]currentPage: 0,[代码][代码] [代码][代码]pageFrame:[][代码][代码] [代码][代码]},[代码][代码] [代码][代码]pageSize: 10,[代码][代码] [代码][代码]currentPage:0,[代码][代码] [代码][代码]onLoad: [代码][代码]function[代码] [代码]() {[代码] [代码] //mock 一些数据[代码] [代码] [代码][代码]var[代码] [代码]list = [];[代码][代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]i = 0; i< 10000 ; i++) {[代码][代码] [代码][代码]list.push({[代码][代码] [代码][代码]name: [代码][代码]'ssss----'[代码] [代码]+ i,[代码][代码] [代码][代码]desc: [代码][代码]'aaaaa----'[代码] [代码]+ i,[代码][代码] [代码][代码]content: [代码][代码]'adasdadasdadadasdadadadadasdadad ----'[代码] [代码]+ i[代码][代码] [代码][代码]});[代码][代码] [代码][代码]}[代码] [代码] //数据分页[代码] [代码] [代码][代码]this[代码][代码].pagedList = [代码][代码]this[代码][代码].pageList(list);[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]listLength: list.length,[代码][代码] [代码][代码]list: [[代码][代码]this[代码][代码].pagedList[0]][代码][代码] [代码][代码]})[代码][代码] [代码][代码]},[代码] [代码] //滚动监听[代码] [代码] [代码][代码]onListScroll(e) {[代码][代码] [代码][代码]if[代码] [代码]([代码][代码]this[代码][代码].inPageUpdate) {[代码][代码] [代码][代码]return[代码][代码];[代码][代码] [代码][代码]}[代码][代码] [代码][代码]var[代码] [代码]{ scrollTop } = e.detail;[代码][代码] [代码][代码]if[代码] [代码]([代码][代码]this[代码][代码].currentPage > 0) {[代码][代码] [代码][代码]var[代码] [代码]pageFrame = [代码][代码]this[代码][代码].data.pageFrame[[代码][代码]this[代码][代码].currentPage - 1];[代码][代码] [代码][代码]var[代码] [代码]screenHeight = wx.getSystemInfoSync().screenHeight;[代码][代码] [代码][代码]if[代码] [代码]((scrollTop + screenHeight) - (pageFrame.lastBottom + pageFrame.height) < -200) {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]true[代码][代码];[代码][代码] [代码][代码]this[代码][代码].currentPage -= 1;[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]currentPage: [代码][代码]this[代码][代码].currentPage,[代码][代码] [代码][代码]}, () => {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]false[代码][代码];[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码]var[代码] [代码]currentPageFrame = [代码][代码]this[代码][代码].data.pageFrame[[代码][代码]this[代码][代码].currentPage];[代码][代码] [代码][代码]if[代码] [代码](currentPageFrame) {[代码][代码] [代码][代码]if[代码] [代码](scrollTop - (currentPageFrame.lastBottom + currentPageFrame.height) > 200) {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]true[代码][代码];[代码][代码] [代码][代码]this[代码][代码].currentPage += 1;[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]currentPage: [代码][代码]this[代码][代码].currentPage,[代码][代码] [代码][代码]}, () => {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]false[代码][代码];[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码]},[代码] [代码] //计算分页高度[代码] [代码] [代码][代码]reachPageBottom() {[代码][代码] [代码][代码]if[代码] [代码]([代码][代码]this[代码][代码].inPageUpdate) {[代码][代码] [代码][代码]return[代码][代码];[代码][代码] [代码][代码]}[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]true[代码][代码];[代码][代码] [代码][代码]if[代码] [代码]([代码][代码]this[代码][代码].currentPage < [代码][代码]this[代码][代码].pagedList.length - 1) {[代码][代码] [代码][代码]var[代码] [代码]self = [代码][代码]this[代码][代码];[代码][代码] [代码][代码]var[代码] [代码]currentPage = [代码][代码]this[代码][代码].currentPage;[代码][代码] [代码][代码]wx.createSelectorQuery().select([代码][代码]'#listpage-'[代码] [代码]+ [代码][代码]this[代码][代码].currentPage).boundingClientRect([代码][代码]function[代码] [代码](rect) {[代码][代码] [代码][代码]if[代码] [代码](currentPage > 0) {[代码][代码] [代码][代码]rect.lastBottom = self.data.pageFrame[currentPage - 1].height + self.data.pageFrame[currentPage - 1].lastBottom[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]rect.lastBottom = 0;[代码][代码] [代码][代码]}[代码][代码] [代码][代码]self.setData({[代码][代码] [代码][代码][`pageFrame[${currentPage}]`]: rect[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}).exec();[代码] [代码] [代码][代码]this[代码][代码].currentPage = [代码][代码]this[代码][代码].currentPage + 1;[代码][代码] [代码][代码]var[代码] [代码]nextPage = [代码][代码]this[代码][代码].pagedList[[代码][代码]this[代码][代码].currentPage];[代码][代码] [代码][代码]var[代码] [代码]key = `list[${[代码][代码]this[代码][代码].currentPage}]`[代码][代码] [代码][代码]var[代码] [代码]data = {};[代码][代码] [代码][代码]data[key] = nextPage;[代码][代码] [代码][代码]data.currentPage = [代码][代码]this[代码][代码].currentPage;[代码][代码] [代码][代码]console.log(data);[代码][代码] [代码][代码]this[代码][代码].setData(data, () => {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]false[代码][代码];[代码][代码] [代码][代码]});[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]pageEnd: [代码][代码]true[代码][代码],[代码][代码] [代码][代码]}, () => {[代码][代码] [代码][代码]this[代码][代码].inPageUpdate = [代码][代码]false[代码][代码];[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]},[代码][代码] [代码][代码]listItemTap(e) {[代码] [代码] //响应点击事件[代码] [代码] [代码] [代码] [代码][代码]console.log(e.currentTarget.dataset);[代码][代码] [代码][代码]},[代码] [代码] //分页函数[代码] [代码] [代码][代码]pageList(list) {[代码][代码] [代码][代码]var[代码] [代码]splitArray = (arr, len) => {[代码][代码] [代码][代码]var[代码] [代码]a_len = arr.length;[代码][代码] [代码][代码]var[代码] [代码]result = [];[代码][代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]i = 0; i < a_len; i += len) {[代码][代码] [代码][代码]result.push(arr.slice(i, i + len));[代码][代码] [代码][代码]}[代码][代码] [代码][代码]return[代码] [代码]result;[代码][代码] [代码][代码]}[代码][代码] [代码][代码]var[代码] [代码]pagedList = splitArray(list, [代码][代码]this[代码][代码].pageSize);[代码][代码] [代码][代码]return[代码] [代码]pagedList;[代码][代码] [代码][代码]}[代码] [代码]})[代码] [代码]/*index.wxss*/[代码][代码]page {[代码][代码] [代码][代码]height[代码][代码]: [代码][代码]100%[代码][代码];[代码][代码]}[代码] [代码].list {[代码][代码] [代码][代码]height[代码][代码]: [代码][代码]100%[代码][代码];[代码][代码] [代码][代码]width[代码][代码]: [代码][代码]100%[代码][代码];[代码][代码]}[代码] [代码].listitem {[代码][代码] [代码][代码]line-height[代码][代码]: [代码][代码]50[代码][代码]rpx;[代码][代码] [代码][代码]padding[代码][代码]: [代码][代码]20[代码][代码]rpx;[代码][代码] [代码][代码]font-size[代码][代码]: [代码][代码]32[代码][代码]rpx;[代码][代码] [代码][代码]color[代码][代码]: [代码][代码]#ff0000[代码][代码];[代码][代码] [代码][代码]border-bottom[代码][代码]: [代码][代码]1[代码][代码]rpx [代码][代码]solid[代码] [代码]#eeeeee[代码][代码];[代码][代码]}[代码] [代码].list-end{[代码][代码] [代码][代码]text-align[代码][代码]: [代码][代码]center[代码][代码];[代码][代码] [代码][代码]font-size[代码][代码]: [代码][代码]18[代码][代码]rpx;[代码][代码] [代码][代码]color[代码][代码]: [代码][代码]#b2b2b2[代码][代码];[代码][代码] [代码][代码]margin[代码][代码]: [代码][代码]15[代码][代码]rpx [代码][代码]0[代码] [代码]7.5[代码][代码]rpx;[代码][代码] [代码][代码]display[代码][代码]: flex;[代码][代码] [代码][代码]flex-[代码][代码]direction[代码][代码]: row;[代码][代码] [代码][代码]justify-[代码][代码]content[代码][代码]: [代码][代码]center[代码][代码];[代码][代码] [代码][代码]align-items: [代码][代码]center[代码][代码];[代码][代码]}[代码] [代码].list-end .line {[代码][代码] [代码][代码]width[代码][代码]: [代码][代码]20%[代码][代码];[代码][代码] [代码][代码]height[代码][代码]: [代码][代码]1px[代码][代码];[代码][代码] [代码][代码]background-color[代码][代码]: [代码][代码]#dddddd[代码][代码];[代码][代码] [代码][代码]margin[代码][代码]: [代码][代码]0[代码] [代码]30[代码][代码]rpx;[代码][代码]}[代码] [代码]<!-- index.wxml -->[代码][代码] [代码][代码]<[代码][代码]scroll-view[代码] [代码]class[代码][代码]=[代码][代码]"list"[代码] [代码]style[代码][代码]=[代码][代码]''[代码] [代码]scroll-y[代码][代码]=[代码][代码]"true"[代码] [代码]bindscrolltolower[代码][代码]=[代码][代码]"reachPageBottom"[代码] [代码]bindscrolltoupper[代码][代码]=[代码][代码]"reachPageTop"[代码] [代码]bindscroll[代码][代码]=[代码][代码]"onListScroll"[代码] [代码]enable-back-to-top[代码][代码]=[代码][代码]"true"[代码] [代码]upper-threshold[代码][代码]=[代码][代码]'100'[代码] [代码]lower-threshold[代码][代码]=[代码][代码]"100"[代码] [代码]scroll-top[代码][代码]=[代码][代码]"{{scrollTo}}"[代码][代码]>[代码] [代码] <!-- 循环分页 -->[代码] [代码] [代码][代码]<[代码][代码]view[代码] [代码]wx:for[代码][代码]=[代码][代码]"{{list}}"[代码] [代码]wx:for-item[代码][代码]=[代码][代码]"subList"[代码] [代码]wx:for-index[代码][代码]=[代码][代码]"page"[代码] [代码]id[代码][代码]=[代码][代码]"listpage-{{page}}"[代码] [代码]wx:key[代码][代码]=[代码][代码]"listpage"[代码] [代码]>[代码] [代码] [代码] [代码] <!-- 判断页面状态 -->[代码] [代码] [代码][代码]<[代码][代码]block[代码] [代码]wx:if="{{page - currentPage >= -1 && page - currentPage <= 1}}" >[代码] [代码] <!-- item渲染 -->[代码] [代码] [代码][代码]<[代码][代码]template[代码] [代码]is[代码][代码]=[代码][代码]"listitem"[代码] [代码]wx:for[代码][代码]=[代码][代码]"{{subList}}"[代码] [代码]data[代码][代码]=[代码][代码]"{{item:item, page,index}}"[代码] [代码]/>[代码][代码] [代码][代码]</[代码][代码]block[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]wx:else [代码][代码]style[代码][代码]=[代码][代码]'height:{{pageFrame[page].height}}px;'[代码] [代码]></[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]"list-end"[代码] [代码]wx:if="{{listLength > 0}}">[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]'line'[代码][代码]></[代码][代码]view[代码][代码]>[代码][代码] [代码] [代码] [代码][代码]<[代码][代码]text[代码] [代码]wx:if[代码][代码]=[代码][代码]"{{pageEnd}}"[代码][代码]>以上共{{listLength || 0}}个数据</[代码][代码]text[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]button[代码] [代码]wx:else [代码][代码]loading[代码][代码]=[代码][代码]"true"[代码][代码] [代码][代码]disabled[代码][代码]=[代码][代码]"true"[代码] [代码]bindtap[代码][代码]=[代码][代码]"empty"[代码] [代码]class[代码][代码]=[代码][代码]"button-noborder"[代码] [代码]style[代码][代码]=[代码][代码]'font-size: 26rpx; background-color:transparent'[代码] [代码]>正在加载更多...</[代码][代码]button[代码][代码]> [代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]'line'[代码][代码]></[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]scroll-view[代码][代码]>[代码] [代码] [代码][代码]<[代码][代码]template[代码] [代码]name[代码][代码]=[代码][代码]"listitem"[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]'listitem'[代码] [代码]bindtap[代码][代码]=[代码][代码]'listItemTap'[代码] [代码]data-item[代码][代码]=[代码][代码]'{{item}}'[代码] [代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码][代码]>{{item.name}}</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码][代码]>{{item.desc}}</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码][代码]>{{item.content}}</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]<!-- 循环100个节点 -->[代码][代码] [代码][代码]<[代码][代码]text[代码][代码]>{{page}}--</[代码][代码]text[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]text[代码] [代码]wx:for[代码][代码]=[代码][代码]"{{100}}"[代码] [代码]wx:for-index[代码][代码]=[代码][代码]'i'[代码] [代码]>{{index}}</[代码][代码]text[代码][代码]>[代码] [代码] [代码][代码]</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]template[代码][代码]>[代码] demo见代码片段 [代码]https:[代码][代码]//developers.weixin.qq.com/s/ItRHXsmg7L5r[代码]使用小程序开发工具导入片段可直接运行
2019-01-16