小程序开发日记_小程序开发制作

小程序 (55) 2023-03-24 20:10

大家好,我是编程小6,很高兴遇见你,有问题可以及时留言哦。

这里主要记录这段时间开发小程序过程中遇到的坑和要注意的点。主要是希望在以后开发小程序的过程中能在评审需求的时候就发现哪些能实现,哪些实现起来比较困难。这样就能在定设计和交互之前尽可能的减少后期踩坑的风险。

下拉刷新

自定义的下拉刷新

微信自带的下拉刷新需要在配置中开启:

  1. 配置 app.json 中的 window或者单个页面的json文件
属性 类型 默认值 描述
backgroundTextStyle string dark 下拉 loading 的样式,仅支持 dark / light
enablePullDownRefresh boolean false true 开启 false关闭
  1. 页面中通过onPullDownRefresh方法监听下拉动作
  onPullDownRefresh() {
    setTimeout(() => {
      wx.stopPullDownRefresh()
    }, 1000)
  }

这里有个要注意的问题:
onPullDownRefresh中不能调用wx.startPullDownRefresh(),否则会死循环

自定义的下拉刷新,目前只支持整个页面的下拉刷新,对于局部的下拉刷新,就没有办法了:

局部的下拉刷新

小程序开发日记_小程序开发制作_https://bianchenghao6.com/blog_小程序_第1张

这种我们可以使用自定义下拉刷新的方式来实现。详细的可以看我的另一篇博客,这里也就不赘述了。

关于自定义顶部navigationBar

小程序自带的navigationBar仅可以在json文件中配置。提供的可配置项也是少的可怜。主要如下:

属性 类型 默认值 描述
navigationBarBackgroundColor HexColor #000000 导航栏标题文字内容
navigationBarTextStyle string white 导航栏标题颜色,仅支持 black / white
navigationBarTitleText string 导航栏标题文字内容

从上面的表格可以看出来,顶部导航栏样式固定,我们仅可以修改文字,字体颜色和背景色。

不过还好小程序还提供了自定义顶部导航的配置,window.navigationStyle,这个配置项支持两个值default|custom。默认是default,表示的是用小程序自带的导航栏,配置为custom时是自定义导航栏,其实就是小程序隐藏掉导航栏,然后我们自己实现。

但是自定义导航栏不仅带来了设计上的自由,也带来了很多坑。

下拉刷新

不同于web开发,小程序和app对于下拉刷新的需求非常的多,也算是基本功能之一,小程序本身也自带下拉刷新。我们只需将window.enablePullDownRefresh设置为true,然后在页面监听onPullDownRefresh即可。

但是当我们用到自定义navigationBar的时候,会发现,本来fixed定位在顶端的navigationBar会被一起拉下来。

系统自带的navigationBar的下拉刷新

小程序开发日记_小程序开发制作_https://bianchenghao6.com/blog_小程序_第2张

自定义navigationBar的下拉刷新

小程序开发日记_小程序开发制作_https://bianchenghao6.com/blog_小程序_第3张

这个时候我们就必须要使用自定义的下拉刷新。关于自定义的下拉刷新的实现原理这里就不多说了。但是实现起来在真机中发现,android手机会有明显的卡顿。

层级问题

按照正常的navigationBar,一般这个组件的层级是最高,仅次于遮罩层和弹窗这些组件。一般的组件可以使用z-index属性来控制层级。

但是小程序中有种概念叫做:原生组件

这些组件包括cameracanvasinput(仅在focus时表现为原生组件),maptextareavideolive-playerlive-pusher

这种组件脱离于WebView渲染流程之外,层级也是最高的,因此无论z-index设置多大,都无法覆盖原生组件。

系统自带的navigationBar

小程序开发日记_小程序开发制作_https://bianchenghao6.com/blog_小程序_第4张

自定义navigationBar

小程序开发日记_小程序开发制作_https://bianchenghao6.com/blog_小程序_第5张

虽然理论上我们可以用cover-viewcover-image来实现自定义的navigationBar,但是个人觉得还是尽量避免使用自定义的navigationBar

虽然cover-viewcover-image组件可以覆盖在部分原生组件上面。对于原生组件之间,可以使用z-index来控制他们的层级。

但是小程序cover-view组件内部只支持嵌套cover-viewcover-image以及button。这在很大程度上不支持我们作出多么有个性化的组件。而且实现起来坑也很多。

而且关于用z-index来控制层级这点也存疑,虽然文档上这样说明,但是,我在开发中发现,实际上还是看渲染的顺序。后渲染的始终在先渲染的上层。

键盘弹起时会将顶部导航栏顶上去

这个是针对textarea组件在页面底部的时候,准确来说是textarea组件距离底部的距离没有键盘高的时候。在键盘弹起时造成了整个页面上移,从而导致了导航栏会移到页面外。

当键盘未弹起时

小程序开发日记_小程序开发制作_https://bianchenghao6.com/blog_小程序_第6张

当键盘弹起时

小程序开发日记_小程序开发制作_https://bianchenghao6.com/blog_小程序_第7张

当然这个不算是硬伤,毕竟出现的条件有限,我们可以在设计上尽量避免将textarea放到底部来避免这个坑。

自定义底部tabbar

自定义的tabbar

在说自定义之前先看看小程序自带的tabbar可以做到什么程度。

自定义的tabbar是在app.json中配置的,在tabBar下:

  • 基本配置
属性 类型 描述
color HexColor tab 上的文字默认颜色,仅支持十六进制颜色
selectedColor HexColor tab 上的文字选中时的颜色,仅支持十六进制颜色
backgroundColor HexColor tab 的背景色,仅支持十六进制颜色
borderStyle string tabbar上边框的颜色, 仅支持 black / white
list Array tab 的列表,详见 list 属性说明,最少2个、最多5个 tab
position string tabBar的位置,仅支持 bottom / top
custom boolean 自定义 tabBar,见详情
  • list选项配置
属性 类型 描述
pagePath string 页面路径,必须在 pages 中先定义
text string tab 上按钮文字
iconPath string 图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px,不支持网络图片。当 position 为 top 时,不显示 icon。
selectedIconPath string 选中时的图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px,不支持网络图片。当 position 为 top 时,不显示 icon。

所以从上面我们可以看到,我们可以定义tabbar的选中和未选中图标和字体颜色。没办法加入别的样式和嵌入别的自定义点击事件。

自定义的几种实现方式

  1. 组件形式

这是比较老的版本的形式。在需要tabbar的页面嵌入tabbar组件。这是最简单的实现方式。但是在首次切换的时候,会有很明显的闪屏。

  1. 官网提供的customer-tab-bar的形式

这个比第一种要好点,也是我在项目中用到的一种模式,但是切换的时候也有稍微的闪屏

  1. tabbar的形势

这种实现方式稍微复杂,也就是将首页的几个页面作为组件传入,通过路由控制页面切换。

之所以叫伪tabbar的形势,是因为这个只是表面上是tabbar

理论上这种方式实现的在切换的时候可以做到不闪屏。但是会不会带来别的问题呢?

比如说返回的时候会不会造成页面错乱?
原本的页面生命周期和组件的生命周期略有不同,会不会造成一些坑?
还有个几乎可以肯定的问题,就是如果不使用cover-view的话,我们就没法盖住原生组件。

不过好在小程序组件和页面之间的切换很方便,特别是在用Taro之后,组件和页面的区分仅仅只是是否在app.tsx中注册。所以第二和第三种实现方式切换起来并不是很麻烦。但是目前看来的话第二种实现方式体验还算满意,因此也没有必要切换到第三种方式。

关于弹窗

额,其实我说的这三个,几乎可以总结出一个问题,那就是小程序中让人吐血的层级问题。

其实不论是弹窗还是navigation还是tabbar他们都有一个特点,就是定位在页面的某一个位置,还有层级要足够高,要能够覆盖住底层元素。

官方没有专门的弹窗容器(我觉得应该有一个弹窗容器)因此只能靠我们自己写了。但是因为cover-view令人蛋疼的样式支持度,个人觉得仅仅用cover-viewcover-image来实现一个定制化的弹窗几乎不可能。

如果不用cover-view你会发现很多常用的组件都是骑在你脸上,而你毫无办法的。

因此个人建议,在有原生组件的页面上,尽量避免弹层的出现。

如果是在无法妥协,那也建议弹窗组件分两块来写,一种专门用cover-viewcover-image来写,并且一定要写z-index来控制层级,理论上是后面的元素会覆盖在上一个元素上面,但是还是要防止有些组件在操作的过程中重新渲染,而改变原有的层级。而对于页面中没有原生组件的,可以用view来写,这样样式上就自由很多。

关于html2wxml

关于富文本的渲染。现在基本的做法都是先把html解析为节点信息,然后再通过模板渲染为wxml。但是因为小程序模板不支持递归调用。所以在很多第三方组件中都出现以下的代码:

<!--temp0-->
<template>
...
  <template is="temp1"></template>
</template>
<!--temp1-->
<template>
...
  <template is="temp2"></template>
</template>
<!--temp2-->
<template>
...
  <template is="temp3"></template>
</template>
...

通常这种代码会出现十几到二十几个,也就是说最多支持嵌套二十多层。如果不够的话就得自己加了。我就遇到过一个富文本,足足嵌套到了两百多层。我复制到一百的时候实在受不了了,写了一个模板生成器来完成。

网上有人说这种代码看起来蠢哭了。的确,但是也很无奈。

时间格式化问题

这个不能说是小程序的坑,应该说是ios和android对new Date()处理上的差异。我们可以用safari浏览器和chrome来复现这两种差异

我们公司前后端交互用的时间格式是YYYY-MM-DDTHH:mm:ss。这种格式的时间字符串用new Date()来处理,在safarichrome的表现如下:

new Date('2019-05-29T14:00:00')
// safari Wed May 29 2019 22:00:00 GMT+0800 (CST) = $2
// chrome Wed May 29 2019 14:00:00 GMT+0800 (中国标准时间)

safari是比chrome要早8小时的,这是因为chrome认为这个时间是本地时间,而safari认为是国际标准时间,所以会有这样的8小时差异(仅限于中国)。

因此在调用new Date()之前我们需要把YYYY-MM-DDTHH:mm:ss格式的转换为YYYY/MM/DD HH:mm:ss这种格式的字符串。

此外,我在处理的过程中还发现带毫秒数的事件字符串2019-05-29T14:00:00.000,这种的还需要将毫秒数去掉变成这种格式2019/05/29 14:00:00。然后在iosandroid上表现也就一致了。

function getDate(date: any) {
  if(typeof date === 'string') {
    return new Date(date.replace('T', ' ').replace(/\-/g, '/').split('.')[0])
  }
  return new Date(date)
}

字体

小程序在android下,字体的font-weight必须要设置到700及以上才会变粗,或者统一使用bold

ios

小程序开发日记_小程序开发制作_https://bianchenghao6.com/blog_小程序_第8张

android

小程序开发日记_小程序开发制作_https://bianchenghao6.com/blog_小程序_第9张

小程序分包

小程序大小是有限制的,目前是主包不超过2M。可是为了实现一些功能,导致我们很容易就超过了这个限制。

好在官方提供了分包方式。具体可以参照官方文档。

就一点:对于副包内引用的,较大的包,应该包含在分包的文件夹内部。不然仍然会打包在主包内部

关于pxrpx

不能无脑的全站用rpx来做适配

最近在做一个需求,觉得有个点还是需要注意的,特此记录一下。

这个需求就是一个简单的消息轮播。如下图:

小程序开发日记_小程序开发制作_https://bianchenghao6.com/blog_小程序_第10张

就是红框区域的一个向下无限滚动轮播,时间间隔为2s。我是使用translateY来实现的。每次translateY的高度和消息块的高度相同。

但是在滚动的过程中,发现在某些机型上面每次滚动都会有细微的偏移,而在某些机型上面正常。

消息显示窗口的高度和每个消息的高度都是80rpx。代码如下:

<View className="notice-pannel" style={{transform: `translateY(${curIndex * -80}rpx)`, transition}}>
{
  list.map(item => (
    <View className="notice-item" key={item.id}>
      <Image className="notice-avatar" src={item.actor.avatar_url}></Image>
      <Text className="notice-desc">{`${item.actor.login} ${item.payload.action} ${item.repo.name} at ${new DateX(item.created_at).format()}`}</Text>
    </View>
  ))
}
</View>

最后定位问题的原因是因为部分屏幕宽度在换算rpx的时候会有误差,导致每次translateY的时候会有一个小误差。

rpx根据官方文档的定义是:rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。

因此,会有些屏幕宽度在rpx转px的时候会有除不尽的时候,这时候往往会有四舍五入取整的情况。

在看这行代码${curIndex * -80}rpx),假设我们的设备的屏幕宽度为412这时候80rpx专成px的时候是43.946666666666665px。开发者工具上会发现,实际上是换算成了43,也就是每个消息块的高度是43px

curIndex2的时候,实际上我们应该偏移43 * 2也就是86px,可是如果是直接用rpx的话,我们发现会是87px。这时候就产生了偏移了。

知道原因的话解决方案也有了,就是用px做单位就可以了

<View className="notice-pannel" style={{transform: `translateY(${curIndex * rpx2px(-80)}rpx)`, transition}}>
{
  list.map(item => (
    <View className="notice-item" key={item.id}>
      <Image className="notice-avatar" src={item.actor.avatar_url}></Image>
      <Text className="notice-desc">{`${item.actor.login} ${item.payload.action} ${item.repo.name} at ${new DateX(item.created_at).format()}`}</Text>
    </View>
  ))
}
</View>

发表回复