- Skyline|小程序手势:让半屏弹窗更顺滑
在小程序页面开发中,我们经常用半屏弹窗来进来内容展示,例如:微信开放社区切换主页、加入购物车的选项页、文章留言区等等。 [图片] 常见的半屏弹窗展示逻辑是这样的: 打开弹窗:点击 “打开弹窗” 按钮展示弹窗关闭弹窗:点击“关闭按钮” or 遮罩层 关闭弹窗当我们想在半屏弹窗加一些交互动画时,可以监听节点的 touch 事件来做一些手势判断,进而处理拖拽事件。但是这种方式实现的滚动动画容易卡顿,出现延迟的情况,效果并不理想。 为了丰富小程序的交互体验,我们内置了一批手势组件,可以帮助开发者更好的实现交互动画的效果。 下图演示使用手势的半屏弹窗下拉效果与普通半屏下拉的对比。 当内部评论列表往下拉到顶部时,变为半屏的下拉,可直接下拉关闭弹窗。 [图片] 我们来看下这种操作是怎么实现的 在上面评论列表的半屏弹窗中会有一个 scroll-view 滚动组件,在 scroll-view 中会有滚动事件,当滚动到顶部时,我们希望有整个半屏的下拉事件。 所以我们需要在半屏的最外层放置一个拖动手势组件 pan-gesture-handler 由于拖动组件内部的 scroll-view 也是可以滚动的,所以这里需要进行一个手势协商的处理,就是什么条件下由哪个组件来响应手势。 当手势往下 ⬇️ 滚动时,此时判断内部 scroll-view 滚动条的位置 滚动条处于顶部:外层 pan-gesture-handler 响应滚动,此时半屏往下拖动至关闭半屏滚动条不处于顶部:内层 scroll-view 响应滚动,此时内部列表往上滚[图片] 当手势往上 ⬆️ 滚动时,此时判断半屏的位置 半屏不完全打开时:外层 pan-gesture-handler 响应滚动,此时半屏往上拖动至完全打开半屏半屏完全打开时:内层 scroll-view 响应滚动,此时内部列表往下滚[图片] 我们来看一下代码的实现,这里用到的手势组件 pan-gesture-handler(拖动时触发)和 vertical-drag-gesture-handler(纵向滑动时触发),手势组件有以下属性 on-gesture-event:手势回调事件should-response-on-move:是否响应当前手势的 move 阶段simultaneous-handlers:指定需要协商的手势是哪几个,下面演示表示 pan 和 scroll 协同触发。native-view:代理的原生节点,这里 scroll-view(scroll-y) 内有个 vertical-drag 手势,scroll-view 自身无法处理,需要被代理出来 ... 接着,我们看看在页面 js 中怎么处理手势。 在手势处理的回调中因为会改变半屏的状态值,所以这里的回调函数采用 worklet 函数,worklet 函数运行在 UI 线程,使得小程序可以做到类原生动画般的体验。 // page.js // shared 创建的变量为共享变量,可在 UI 线程和 JS 线程间同步 this.transY = wx.worklet.shared(1000) this.scrollTop = wx.worklet.shared(0) this.startPan = wx.worklet.shared(true) // shouldPanResponse 和 shouldScrollViewResponse 用于 pan 手势和 scroll-view 滚动手势的协商 shouldPanResponse() { 'worklet' return this.startPan.value }, shouldScrollViewResponse(pointerEvent) { 'worklet' // transY > 0 说明 pan 手势在移动半屏,此时 scroll-view 滚动不应生效 if (this.transY.value > 0) return false const scrollTop = this.scrollTop.value const { deltaY } = pointerEvent // deltaY > 0 是往上滚动,scrollTop <= 0 是滚动到顶部边界,此时 pan 开始生效,scroll-view 滚动不生效 const result = scrollTop <= 0 && deltaY > 0 this.startPan.value = result return !result }, // pan 手势处理 handlePan(gestureEvent) { 'worklet' if (gestureEvent.state === GestureState.ACTIVE) { const curPosition = this.transY.value const destination = Math.max(0, curPosition + gestureEvent.deltaY) // 改变半屏的位置 this.transY.value = destination } // 其他手势状态的处理,如滚动结束时计算半屏处于打开还是关闭的状态 } 目前,同程旅行 已经上线了手势结合半屏的效果 体验路径:酒店查询 - 选择酒店 - 选择入住人 - 新增入住人 [图片] 普通半屏结合手势代码片段:https://developers.weixin.qq.com/s/lx0RH1mD7rGj 手势除了在普通半屏的应用之外,也可以实现分段式半屏。下面演示的分段式半屏比普通半屏的判断条件更多一些。 判断条件同普通半屏类似,根据手势方向 和 分段式半屏当前的位置来判断是响应分段式半屏还是内部列表,响应分段式半屏是改变到哪一个位置。 [图片] 这里与普通半屏不同的是我们还改变了地图的缩放级别(scale) 因为 worklet 函数是在 UI 线程运行的,当要改变 data 值时,需要通过 wx.worklet.runOnJS 调回 JS 线程。 // page.js // 设置 map scale // 运行在 JS 线程 setMapScale(scale) { this.setData({ scale }) }, // worklet 函数,运行在 UI 线程 scrollTo(toValue) { 'worklet' let scale = 18 if (toValue > screenHeight / 2) { scale = 16 } // 从 UI 线程调回 JS 线程 wx.worklet.runOnJS(this.setMapScale.bind(this))(scale) this.transY.value = timing(toValue, { duration: 200 }) }, // 处理拖动半屏的手势 handlePan(gestureEvent) { 'worklet' // 滚动半屏的位置 if (gestureEvent.state === GestureState.ACTIVE) { // deltaY < 0,往上滑动 this.upward.value = gestureEvent.deltaY < 0 // 当前半屏位置 const curPosition = this.transY.value // 只能在 [statusBarHeight, screenHeight] 之间移动 const destination = clamp(curPosition + gestureEvent.deltaY, statusBarHeight, screenHeight) if (curPosition === destination) return // 改变 transY,来改变半屏的位置 this.transY.value = destination } if (gestureEvent.state === GestureState.END || gestureEvent.state === GestureState.CANCELLED) { if (this.transY.value <= screenHeight / 2) { // 在上面的位置 if (this.upward.value) { this.scrollTo(statusBarHeight) } else { this.scrollTo(screenHeight / 2) } } else if (this.transY.value > screenHeight / 2 && this.transY.value <= this.initTransY.value) { // 在中间位置的时候 if (this.upward.value) { this.scrollTo(screenHeight / 2) } else { this.scrollTo(this.initTransY.value) } } else { // 在最下面的位置 this.scrollTo(this.initTransY.value) } } }, 分段式页面代码片段:https://developers.weixin.qq.com/s/fw0U31mI7bGf 半屏的交互除了在页面内实现,也能跨页面实现,如常见的下沉式半屏交互。其中,半屏效果与上述实现类似,而前一页面的下沉实现需要结合自定义路由 后面的文章中我们会介绍自定义路由结合手势怎么去实现下沉式半屏效果,不仅如此,还有很多类原生的页面切换效果都能通过自定义路由实现 [图片]
2023-08-03 - wx.setTabBarItem强烈建议增加pagePath
- 需求的场景描述(希望解决的问题) wx.setTabBarItem设置后,设置tabbar页面路径,不填则默认不修改,可用于用户权限的设置- 希望提供的能力 希望尽快解决此问题
2018-10-17 - 微信小程序云存储数据库有没有关联查询(类似left join)?
创建了多个集合需要关联查询完整数据,但是开发文档上好像并没有说支持此功能
2019-08-12 - 云开发常用数据结构设计剖析丨云开发101
云开发常用数据结构设计 在使用云开发进行产品开发的时候,我们常常需要思考,我们的应用的数据结构应该如何设计,今天我们来看一些在进行应用开发时常见的一些场景的数据结构,来帮助你更好的理解云开发,以及不同场景下云开发的应用。 场景一:用户个人信息表 功能:判断用户是否注册/留存用户信息以备查询 在绝大多数场景下,用户信息都是我们需要保存的信息,或者我们需要判断一个用户是否注册时,我们会使用用户个人信息表来做判断。这个时候,我们可以借助用户个人信息表来完成功能。 [图片] 在完成这部分功能时,我们需要创建一个名为 [代码]profiles[代码] 的集合,用于存储用户的信息,同时,我们需要将 profiles 集合设置为仅创建者可读写,这样可以确保后续我们功能可以实现。 判断用户是否注册 由于我们将 [代码]profiles[代码] 集合设置为仅创建者可读写,因此,当用户在执行数据查询时,仅能查到自己创建的数据,因此,当我们需要实现判断用户是否注册时,只需要对集合内的数据进行查询,便可知道用户是否创建过数据。 基于这样的机制,我们可以让用户在注册时在 [代码]profiles[代码] 表中创建一个数据,这样在后续判断用户是否注册时,只需要查询是否创建数据,便可以知道用户是否已经注册。 [图片] 这里我们举个例子,当数据库中有 [代码]_openid[代码] 分别为 [代码]aa[代码]、[代码]bb[代码]、[代码]cc[代码] 的三条记录,如果当前用户的 openId 为 aa,则它执行 [代码]db.collection('profiles').count()[代码]时,得到的结果是 1 ,则表示这个用户已经注册了。如果当前用户的 openId 为 dd,因为权限是仅能查询自己创建的数据,因此,在执行 [代码]db.collection('profiles').count()[代码]时,得到的结果是0。 对于结果为 1 的,就视为该用户已经注册;对于结果为 0 的,就视为该用户未注册。 新用户注册时的操作 由于我们是基于 count 的结果来判断用户是否注册,因此,就要求我们在完成用户注册(获取用户基本信息,如头像、昵称)时,在 [代码]profiles[代码] 表中添加一个数据,从而用于后续的判断。 则在开发时,我们需要注意,在使用 getUserInfo 相关的组件和接口完成数据的获取后,我们需要在 [代码]profiles[代码] 表中添加一条数据,具体的代码可以参考下方的代码 [代码]Page({ bindGetUserInfo:function(userInfo){ const db = wx.cloud.database() db.collection('profiles').count().then(res => { if (res.total === 0){ db.collection('profiles').add({ data:userInfo }).then(res => { // 完成数据新增,即,完成注册的步骤 }) } }) } }) [代码] 这样我们就完成判断用户注册的最核心的部分,你如果需要在自己的应用中判断用户是否已经完成注册的流程,可以借助这样的方式,完成这部分的需求。 场景二:文章/视频/内容表 一般来说,我们的小程序中会或多或少加入一些内容方面的内容,有的是由官方发布的,有的是由用户发布的,根据发布者的不同,我们可以设计两种不同的表结构 仅能由官方发布的内容/文章/视频 对于仅限于官方发布的,我们可以考虑创建一个集合,名为 [代码]contents[代码],并将其权限设置为仅管理端可写,其他人可读,这样我们所添加的数据仅能在云函数中进行修改,而不能在小程序由普通用户修改,这样就可以确保我们的数据的修改必须经过云函数的确认,在云函数中,我们可以进行权限的判断,比如,某几个特定的人有权限修改数据。 由用户发布的内容/文章/视频 对于一些 UGC 的应用,我们需要用户产生内容时,可以考虑创建一个集合,名为 [代码]contents[代码] ,并将其权限设置为仅创建者可写,所有人可读,这样就可以实现用户自行发布的内容,可以被其他所有用户查看。同时,数据的创建者可以对数据进行修改。 场景三:用户评论表 当我们涉及到一些内容时,就常常会涉及到用户的评论的功能,在进行开发时,评论的存放位置也会让很多人迷茫,到底应该将评论放在文章的子级,还是要放在一个单独的集合中? 这一部分关键在于: 你是否有需求将所有的评论进行排序等操作 你是否有需求在用户个人的信息中显示其所有评论 如果你有上述两个中任何一个或多个需求,那么你都需要新建一个 collection,将所有评论都放在集合中,并将其对应的内容的 _id 放在评论中,用以后续的查询。 如果你没有上述需求,则可以考虑将评论放在文章/视频条目中的子属性中,随着文章/视频一同查询出来。 总结 这篇文章我们分享了一些常见场景下的数据库结构设计,如果你还对其他场景下的数据库结构设计不慎明了,可以在文章下方留言,我们后续更新文章来说明。 更多云开发使用技巧及 Serverless 行业动态,扫码关注我们~ [图片]
2019-09-24 - ColorUI-高颜值,高效率的小程序组件库
[图片] 简介 hi!开发者!ColorUI迎来了2.0的升级,相比之前的版本,2.0版本重构了基础代码,增加了更多的配色,这是一个全新的小程序UI解决方案。 ColorUI是一个Css类的UI组件库!不是一个Js框架。相比于同类小程序组件库,ColorUI更注重于视觉交互! 项目为个人开源项目,如果项目有帮到你,希望能支持下开发者。 [图片] 截图 [图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片][图片] 使用 浏览GitHub:https://github.com/weilanwl/ColorUI/
2018-12-25