taro小程序开发笔记

The development notes of mini-program based on taro

nojsja 2021-07-08
字数:2.4k丨 阅读时间:10 分钟

Contents

前言


最近新接触了小程序开发领域,在工作中使用 Taro 进行微信小程序相关开发,一如既往,踩坑记录也要留下。

Taro 是一个开放式跨端跨框架解决方案,支持使用 React/Vue/Nerv 等框架来开发 微信 / 京东 / 百度 / 支付宝 / 字节跳动 / QQ 小程序 / H5 / RN 等应用。
现如今市面上端的形态多种多样,Web、React Native、微信小程序等各种端大行其道。当业务要求同时在不同的端都要求有所表现的时候,针对不同的端去编写多套代码的成本显然非常高,这时候只编写一套代码就能够适配到多端的能力就显得极为需要。

微信小程序开发相关


I. 微信小程序像素单位rpx

Taro 默认以 750px 作为换算尺寸标准,如果设计稿不是以 750px 为标准,则需要在项目配置 config/index.js 中进行设置让微信正确将 rpx 转换为实际屏幕物理像素。例如设计稿尺寸是 640px,则需要修改项目配置 config/index.js 中的 designWidth 配置为 640,如果设计稿是 375px,不在以上三种之中,那么你需要把 designWidth 配置为 375,同时在 DEVICE_RATIO 中添加换算规则如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const config = {
projectName: '',
designWidth: 750, // 设定标准设计稿尺寸
deviceRatio: {
'375': 2 / 1, // 设计稿375px - 转换规则
'640': 2.34 / 2, // 设计稿640px - 转换规则
'750': 1, // 设计稿750px - 转换规则
'828': 1.81 / 2 // 设计稿828px - 转换规则
},
mini: {
postcss: {
pxtransform: { // 设置自动转换规则
enable: true, // 是否启用
config: {
onePxTransform: true, // 设置 1px 是否需要被转换
unitPrecision: 5, // REM 单位允许的小数位。
propList: ['*'], // 允许转换的属性。
selectorBlackList: [], // 黑名单里的选择器将会被忽略。
replace: true, // 直接替换而不是追加一条进行覆盖。
mediaQuery: false, // 允许媒体查询里的 px 单位转换
minPixelValue: 0, // 设置一个可被转换的最小 px 值
}
},
},
...
};

module.exports = config;

rpx 即 responsive pixel,可以根据屏幕宽度进行自适应的像素单位,比如规定屏幕宽为 750rpx。以 iPhone6 为例,屏幕宽度为 375px,共有 750 个物理像素,则 750rpx = 375px = 750 物理像素,1rpx = 0.5px = 1 物理像素。

小程序中默认配置会将所有的 px 单位转换为 rpx,有大写字母的 Px 或 PX 则会被忽略,可以自己在配置中设置自动转换规则(config.mini.postcss.pxtransform)。

II. 微信小程序自定义导航头适配胶囊按钮高度

iphonex2.png

如果某个小程序页面需要自定义导航头部的话,就需要启用相应页面的自定义导航头功能,可以在相关的 page.config 里面配置,比如:

1
2
3
4
5
6
7
export default {
navigationBarTitleText: '首页',
navigationBarTextStyle: 'white',
navigationStyle: 'custom', // 启用自定义导航
navigationBarBackgroundColor: '#2F66FE',
enableShareAppMessage: true,
};

1)原理:根据系统API - getMenuButtonBoundingClientRect 获取小程序右上角胶囊按钮的位置和宽高信息:

导航头垂直居中的情况下,导航头高度即为:bottom - height / 2(胶囊按钮底部位置 - 胶囊按钮高度的一半)

  • bottom [number] 下边界坐标,单位:px
  • height [number] 高度,单位:px
  • left [number] 左边界坐标,单位:px
  • right [number] 右边界坐标,单位:px
  • top [number] 上边界坐标,单位:px
  • width [number] 宽度,单位:px

2)自定义导航头部源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import React, { Component } from 'react'
import Taro from "@tarojs/taro";
import { View, Image, Text, Button } from '@tarojs/components';

import './index.scss';

/**
* 自定义头部导航栏 - fixed 布局 / 无底色
*/
export default class XbHeader extends Component {

state = {
navHeight: '48rpx'
}

componentDidMount () {
this.setNavSize();
}

/* 设置导航栏高度 */
setNavSize = () => {
const sysinfo = Taro.getSystemInfoSync();
const { top, height, bottom } = Taro.getMenuButtonBoundingClientRect();
const navHeight = bottom - height / 2;

// 注意不能使用 rpx,API获取到的是 css 像素
this.setState({
navHeight: `${navHeight}px`
});
}

render () {
const { navHeight, statusHeight } = this.state;

return (
<View className='xb-header' style={{ top: navHeight }}>
<View className='iconfont icon-xiebaojia'>&#xe67d;</View>
<View className='title'>
<View className='title-text'>纳斯达克上市</View>
<View className='title-text'>全国保险经纪牌照</View>
</View>
</View>
);
}
}

III. 微信小程序底部导航条适配 Iphonex

1. 原生小程序适配

> 底部导航条页面结构(fixed定位到屏幕底部):

1
2
3
4
5
6
7
8
9
10
11
12
13
<View
className='member-identify-footer'
style={{paddingBottom: utils.isIphoneX() ? utils.getScreenContentRect().padding : '10rpx' }}
>
<View className='footer-item-wrapper' onClick={this.goBack}>
<View className='item-icon'></View>
<View className='item-title'>{ routerAction[from].text }</View>
</View>
<View className='footer-item-wrapper' onClick={this.importAction}>
<View className='item-icon'></View>
<View className='item-title'>导入信息正确人员</View>
</View>
</View>

> 原理:通过API获取屏幕尺寸参数和屏幕安全区域参数,利用 getScreenContentRect 函数得到计算值:

  • 安全内容区域顶部坐标 top:可用于其它元素进行顶部位置定位
  • 安全内容区域底部坐标 bottom:可用于其它元素进行底部位置定位
  • 底部非安全内容区域内容高度 padding:可用于其它元素进行位置定位

> 屏幕安全内容区域计算函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* __getScreenContentRect [根据顶部和底部安全位置,计算屏幕内容区域位置]
* @param _systemInfo [可以提供已有的 Taro.getSystemInfoSync 信息]
* @param _menuInfo [可以提供已有的 Taro.getMenuButtonBoundingClientRect 信息]
* @param _footerHeight [底部导航栏的高度]
* @returns {Object} 安全内容区域的 top/bottom
*/
const getScreenContentRect = (_footerHeight='85rpx', _systemInfo=null, _menuInfo=null) => {
const systemInfo = _systemInfo || Taro.getSystemInfoSync();
const menuInfo = _menuInfo || Taro.getMenuButtonBoundingClientRect();
const { screenHeight, safeArea } = systemInfo
const { bottom } = safeArea;

return {
// 安全内容区域顶部坐标(加10px是为了防止区域完全重合)
top: `${menuInfo.bottom + 10}px`,
// 安全内容区域底部坐标
bottom: `calc(${screenHeight - bottom}px + 10rpx + ${_footerHeight})`,
// 底部非安全内容区域内容高度
padding: `calc(${screenHeight - bottom}px + 10rpx)`
};
}****

> 示意图:

位置标识图1:

iphonex2.png

真机标识图2:

iphonex1.png

2. 内嵌 H5 适配

> js 简易判断是否是 iphonex 以上的机型:

1
2
3
4
5
6
7
8
9
function isIphonex() {  // X XS, XS Max, XR
return (
window &&
navigator.userAgent.indexOf('iPhone') > -1 &&
// window.screen.height >= 812 &&
window.screen.height >= 724 &&
window.devicePixelRatio >= 2
);
},

> 如果是 iphonex 机型则添加相应的适配类名:

1
2
3
4
5
6
7
8
9
10
$(function() {
var isIphonex = T.check.isIphonex();
$('div[data-action=iphonex]').each(function() {
if (isIphonex) {
$(this).addClass('iphonex');
} else {
$(this).removeClass('iphonex');
}
});
});

> 样式表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.page-footer {
display: flex;
flex-direction: row;
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: rem(10px) rem(48px) rem(10px); // 默认不做iphonex适配
background-color: white;

// 特殊处理样式写入一个类里面
&.iphonex {
padding-bottom: rem(88px);
}
}

IV. H5页面唤起指定的小程序并打开特定页面

>>> 官方文档链接

  1. 原理:使用官方的 scheme url 生成接口生成系统可识别的小程序链接,类似于:weixin://dl/business/?t=DQftHzg06Tj

  2. Node.js (Express) 端生成 url 示例代码:

值得注意的是前端不能将 path 路径参数使用 encodeURIComponent 进行特殊字符编码,否则微信接口不能识别。最后网页端拿到后直接:location.href = [url] 就可以在网页唤醒微信指定页面了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
const miniApps = {
appId: "小程序id",
appSecret: "小程序秘钥"
};

/* 获取小程序 URL Schema 链接 */
router.post(/^(?:\/m)?\/get\/wechatUrlSchema\/?$/, function(req, res) {
// 01 - 获取小程序的accessToken
let url =
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" +
miniApps.appId +
"&secret=" +
miniApps.appSecret;

const {
path = '', // 小程序路径
query = '' // 小程序路径query参数
} = req.body;

request.get({ url: url }, function(err, httpResponse, body) {
if (!err && httpResponse.statusCode == 200) {
const data = JSON.parse(body);
const access_token = data.access_token;
// 02 - 获取小程序的 scheme url
url =
"https://api.weixin.qq.com/wxa/generatescheme?access_token=" + access_token;

request
.post({
url: url,
json: true,
body: {
is_expire: true, // 控制是否过期
expire_type: 1, // 过期类型
expire_interval: 1, // 剩余1天过期
jump_wxa: {
path: path, // 打开的指定页面
query: query // 指定页面 url 携带的查询参数
}
}
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
res.json(body);
} else {
res.json({
success: false,
code: response.statusCode,
msg: error || '获取小程序码失败'
});
}
});
} else {
res.json({
success: false,
code: httpResponse.statusCode,
msg: err || '获取小程序密钥失败'
});
}
});
});

V. 实现将任意 dom 元素内部 innerText 复制到剪贴板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* copyElementText [复制一个dom元素的文本内容-innerText]
* @author huize20156646
* @param {Element} $elem [原生dom元素]
* @return {String} [复制的字符串,使用了 textarea 复制,因此支持换行符]
*/
function copyElementText($elem) {
var input = document.createElement('textarea');
var issuccess = false;

input.setAttribute('readonly', 'readonly');
input.setAttribute('type', 'text');
input.value = $elem.innerText;
document.body.appendChild(input);
input.select();
if (document.execCommand && document.execCommand('copy')) {
document.execCommand('copy', false, null);
issuccess = true;
}
document.body.removeChild(input);

return issuccess;
}
[ loading ]⇷⇷