- 小程序订阅消息默认不选中授权?
问题1:小程序一次性订阅消息要改成默认不选中了吗? 问题2:更改后,默认不支持用户勾选总是保持以上选择选项了吗? (1)现状:默认授权 [图片] (2)目前在灰度中,默认不选中? [图片]
2023-07-06 - 小程序滑动日历
需求 做项目的时候需要用到一种日历,看完设计师的设计稿总结成:可以左右滑动切换显示的日历,能够在某一个日期下显示文字和标签,并且提供选择某一个范围内的日期和单选两种功能。 刚开始看到,想着这种组件网上也是一大堆。然而,查找了一番才知道大部分的日历组件都是上下滚动类型的,能够符合项目需求的组件不多。当然也有一些复杂的日历组件,但是我不想学习使用方法(懒),又因为偶然间看到了日历的简单实现思路,自己花了点时间写这个组件 解析原件 根据项目需求,我得先清楚我的日历应该具备哪些信息: [代码]1. 标题,旨在说明日历的用途,如选择购票时间,选择团期等 2. 副标题,用来显示当前的年月 3. 左右切换按钮,用来翻阅日历 4. 星期(日-六) 5. 一个月的天数的排列,通过循环,只要设置好每个格子的大小,就可以按照顺序排下去,但是我们得知道几点: 5.1)每月的1号是星期几,即从哪个位置开始排 5.2)每月有多少天,即要排多少个格子 6. 按照这种排列,肯定会出现前面几个格子是空白的,后面几个格子是空白的,即上下月的残余天数 6.1)上个月的最后一天是几号,有多少 6.2)下个月的残余天数有多少 7. 底部是否存在按钮,用来操作 [代码] 分析出上面的几点,基本上可以确定为自己的日历应该是张什么样子 [图片] 分析日历的显示: [代码]1. 可以显示文字和标签,因为日历每个日期相当于一个块,所以利用定位可以很快解决显示的问题,颜色也可以根据传过来的属性进行自定义 2. 可以选择日期,从这可以分析出每一个块至少有三种颜色变化:没有选中状态,选中状态以及中间状态 [代码] 从上面的分析可以知道,我们的数据结构应该长成这样子(展示部分): [代码]/** * 日历副标题 */ subTitle: { year: '' month: '' } /** * 日历信息 */ calendarInfo: { last: { year: -1, month: -1, list: [], swiperHeight: 999 }, cur: { year: -1, month: -1, day: -1, select: -1, //选中的时间,位置 swiperHeight: 999, //滚动框的高度 }, next: { year: -1, month: -1, list: [], swiperHeight: 999 } }, /** * 提示信息 */ tipData: [ { value: '2020-10-1', text: '国庆节', type: 'text', //文本类型 color: 'red' //文本颜色 }, { vlaue: '2020-10-1', text: '休', type: 'tag', //标签类型 color: 'lightblue', //标签颜色 } ] [代码] 那么此时的日历应该是长成这样 [图片] 开始封装 首先我选择的组件是微信小程序的swiper 确定每个月的1号是星期几 [代码] /** * 获取某月1号是星期几 * @param {Object} date * @returns {Number} */ getFirstDayWeek(date) { return new Date(date.year, date.month - 1, 1).getDay() }, [代码] 确定每个月的天数 [代码] /** * 获取某年某月的总天数 * @param {Object} date * @returns {Number} totalDays */ getTotalDays(date) { return new Date(date.year, date.month, 0).getDate() }, [代码] 根据第二点和第三点可以轻松知道当前月份的上个月的残余天数和下个月的残余天数(日历上显示的总天数是7的倍数,且最多显示6行) [代码]/** * 根据这个月,计算上下两个月的残余天数 * @param {Object} date */ calculateResidualDays(date) { // 计算上月残余天数,需要知道1号是星期几(个数)且上月最后一天是几号(起始数值) let last_value = dateUtil.getTotalDays({ year: date.year, month: date.month - 1 }) for (let i = 0; i < date.firstDayWeek; i++) { date.list.unshift({ value: this.properties.showRemnantDays ? last_value - i : '', type: 'last' }) } // 计算下月残余天数,需要知道本月显示多少行 let total = Math.floor(date.list.length / 7) if (date.list.length % 7 > 0) { ++total } if (this.properties.fixRow) { // 设置了每月固定显示6行 total = 6 } let next_value = total * 7 - date.list.length // 设置滚动框的高度,设置变化的过渡动画 date.swiperHeight = total * 107 for (let i = 1; i <= next_value; i++) { date.list.push({ value: this.properties.showRemnantDays ? i : '', type: 'next' }) } // 格式化显示的提示信息 this.formatShowTip(date) }, [代码] 大致上日历已经显示出来了,配置显示的文字和标签 [代码] /** * 格式化文字标签 * @param {Object} date */ formatShowTip(date) { // 判断是否显示相关的节假日 if (this.properties.showHoliday) { this.formatHolidayTip(date) } // 循环遍历找出对应的要显示的文字日期 this.properties.dateText.forEach((item, index) => { date.list.forEach((arr, temp) => { let value = date.year + '-' + date.month + '-' + arr.value if (item.value === value && arr.type === 'cur') { arr.tip = item } }) }) }, [代码] 设置选中日期 点击按钮 最后 因为代码的篇幅比较大,所以没有粘贴全部,只是一部分,详细的代码可见小程序业务组件日历。 github里面的组件目前只是单纯的提供日历的显示,暂时还没有提供获取后端数据并且格式化的方法,这个可自行添加,也可以在切换月份的时候回调事件处理。 组件整体的效果变现为: [图片] 原文链接
2023-03-03 - 小程序仿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