网络

在微信小程序/小游戏中使用网络相关的 API 时,需要注意下列问题,请开发者提前了解。

1. 服务器域名配置

每个微信小程序需要事先设置通讯域名,微信小程序只可以跟指定的域名进行网络通信。包括普通 HTTPS 请求(wx.request)、上传文件(wx.uploadFile)、下载文件(wx.downloadFile) 和 WebSocket 通信(wx.connectSocket)。

从基础库 2.4.0 开始,网络接口允许与局域网 IP 通信,但要注意 不允许与本机 IP 通信

从 2.7.0 开始,提供了 UDP 通信(wx.createUDPSocket)。

从 2.18.0 开始,提供了 TCP 连接(wx.createTCPSocket),只允许与同个局域网内的非本机 IP 以及配置过的服务器域名通信。

如使用微信云托管作为后端服务,则可无需配置通讯域名(在微信小程序内通过callContainer和connectContainer通过微信私有协议向云托管服务发起 HTTPS 调用和 WebSocket 通信)。

配置流程

服务器域名请在 「微信小程序后台-开发-开发设置-服务器域名」 中进行配置,配置时需要注意:

  • 域名只支持 https (wx.request、wx.uploadFile、wx.downloadFile) 和 wss (wx.connectSocket) 协议;
  • 域名不能使用 IP 地址(微信小程序的局域网 IP 除外)或 localhost;
  • 对于 https 域名,可以配置端口,如 https://myserver.com:8080,但是配置后只能向 https://myserver.com:8080 发起请求。如果向 https://myserver.com、https://myserver.com:9091 等 URL 请求则会失败。如果不配置端口。如 https://myserver.com,那么请求的 URL 中也不能包含端口,甚至是默认的 443 端口也不可以。如果向 https://myserver.com:443 请求则会失败。
  • 对于 wss 域名,无需配置端口,默认允许请求该域名下所有端口。
  • 域名必须经过 ICP 备案;
  • 出于安全考虑,api.weixin.qq.com 不能被配置为服务器域名,相关API也不能在微信小程序内调用。 开发者应将 AppSecret 保存到后台服务器中,通过服务器使用 getAccessToken 接口获取 access_token,并调用相关 API;
  • 不支持配置父域名,使用子域名。

2. DNS预解析域名

微信客户端 iOS 8.0.24,Android 8.0.23)开始支持。

微信小程序一般会依赖一些网络请求(如逻辑层的wx.request、渲染层的图片等网络资源),优化请求速度将会提升用户体验,而网络请求耗时中就包括DNS解析。DNS预解析域名,是框架提供的一种在微信小程序启动时,提前解析业务域名的技术。

配置流程

DNS域名配置请求「微信小程序后台-开发-开发设置-服务器域名」 中进行配置,配置时需要注意:

  • 预解析域名无需填写协议头
  • 预解析域名最多可添加 5 个
  • 其他安全策略同服务器域名配置策略

3. 网络请求

超时时间

  • 默认超时时间是 60s
  • 超时时间可以在 app.jsongame.json 中通过 networktimeout 配置
  • 也可以在接口调用时指定超时时间,如 wx.request({ timeout: 5000 }),单位为ms。接口调用的timeout配置优先级高于app.json中的配置

使用限制

  • 网络请求的 referer header 不可设置。其格式固定为 https://servicewechat.com/{appid}/{version}/page-frame.html,其中 {appid} 为微信小程序的 appid,{version} 为微信小程序的版本号,版本号为 表示为开发版、体验版以及审核版本,版本号为 devtools 表示为开发者工具,其余为正式版本;
  • wx.request、wx.uploadFile、wx.downloadFile 的最大并发限制是 10 个;
  • wx.connectSocket 的最大并发限制是 5 个。
  • 微信小程序进入后台运行后,如果 5s 内网络请求没有结束,会回调错误信息 fail interrupted;在回到前台之前,网络请求接口调用都会无法调用。

返回值编码

  • 建议服务器返回值使用 UTF-8 编码。对于非 UTF-8 编码,微信小程序会尝试进行转换,但是会有转换失败的可能。
  • 微信小程序会自动对 BOM 头进行过滤(只过滤一个BOM头)。

回调函数

  • 只要成功接收到服务器返回,无论 statusCode 是多少,都会进入 success 回调。请开发者根据业务逻辑对返回值进行判断。

4. 常见问题

HTTPS 证书

微信小程序必须使用 HTTPS/WSS 发起网络请求。请求时系统会对服务器域名使用的 HTTPS 证书进行校验,如果校验失败,则请求不能成功发起。由于系统限制,不同平台对于证书要求的严格程度不同。为了保证微信小程序的兼容性,建议开发者按照最高标准进行证书配置,并使用相关工具检查现有证书是否符合要求。

对证书要求如下:

  • HTTPS 证书必须有效;
    • 证书必须被系统信任,即根证书已被系统内置
    • 部署 SSL 证书的网站域名必须与证书颁发的域名一致
    • 证书必须在有效期内
    • 证书的信任链必需完整(需要服务器配置)
  • iOS 不支持自签名证书;
  • iOS 下证书必须满足苹果 App Transport Security (ATS) 的要求;
  • TLS 必须支持 1.2 及以上版本。部分旧 Android 机型还未支持 TLS 1.2,请确保 HTTPS 服务器的 TLS 版本支持 1.2 及以下版本;
  • 部分 CA 可能不被操作系统信任,请开发者在选择证书时注意微信小程序和各系统的相关通告。
    • Chrome 56/57 内核对 WoSign、StartCom 证书限制周知

业务域名

基础库 1.6.4 开始支持,低版本需做 兼容处理

承载网页的容器。会自动铺满整个微信小程序页面,小游戏和个人类型的微信小程序暂不支持使用。 客户端 6.7.2 版本开始, navigationStyle: custom 对 web-view 组件无效 微信小程序插件中不能使用。

为便于开发者灵活配置微信小程序,现开放微信小程序内嵌网页能力。

使用流程

1. 在管理后台配置业务域名

开发者登录微信小程序后台mp.weixin.qq.com,选择开发管理->开发设置->业务域名,点击新增,按照要求配置业务域名。目前微信小程序内嵌网页能力暂不开放给个人类型账号和小游戏账号。

2. 调用web-view组件实现微信小程序内嵌网页

在微信小程序管理后台成功配置业务域名后,才可使用web-view组件。微信小程序内调用web-view组件实现内嵌的网页,目前仅支持部分jsapi能力,关于web-view接口具体使用说明和限制,请 点击查看

限制说明

1)每个微信小程序账号支持配置最多300个域名;

2)每个域名支持绑定最多100个主体的微信小程序;

3)域名只支持https协议,不支持IP地址;

4)业务域名需经过ICP备案,新备案域名需24小时后才可配置;

5)域名格式只支持英文大小写字母、数字及“- ”;

6)配置业务域名后,可打开任意合法的子域名;

局域网通信

基础库 2.4.0 提供了 wx.startLocalServiceDiscovery 等一系列 mDNS API,可以用来获取局域网内提供 mDNS 服务的设备的 IP。 wx.request/wx.connectSocket/wx.uploadFile/wx.downloadFile 的 url 参数允许为 ${IP}:${PORT}/${PATH} 的格式,当且仅当 IP 与手机 IP 处在同一网段且不与本机 IP 相同(一般来说,就是同一局域网,如连接在同一个 wifi 下)时,请求/连接才会成功。

在这种情况下,不会进行安全域的校验,不要求必须使用 https/wss,也可以使用 http/ws。

wx.request({
  url: 'http://10.9.176.40:828'
  // 省略其他参数
})

wx.connectSocket({
  url: 'ws://10.9.176.42:828'
  // 省略其他参数
})

基础库 2.7.0 开始,提供了 wx.createUDPSocket 接口用于进行 UDP 通信。通信规则同上,仅允许同一局域网下的非本机 IP。

mDNS

目前微信小程序只支持通过 mDNS 协议获取局域网内其他设备的 IP。iOS 上 mDNS API 的实现基于 Bonjour,Android 上则是基于 Android 系统接口

由于操作系统相关能力变更,iOS 微信客户端 7.0.18 及以上版本无法使用 mDNS 相关接口,安卓版本不受影响

serviceType

发起 mDNS 服务搜索 wx.startLocalServiceDiscovery 的接口有 serviceType 参数,指定要搜索的服务类型。

serviceType 的格式和规范,iOS Bonjour OverviewBonjour Names for Existing Service Types 有提及。

Bonjour serviceType.png

Android 文档 对此也有提及。

Android serviceType.png

移动解析HttpDNS

从基础库 2.19.2 开始支持

开发者调用 wx.request 时,可以开启移动解析 HttpDNS 服务。 该服务基于 Http 协议向服务商的 DNS 服务器发送域名解析请求,替代了基于 DNS 协议向运营商 Local DNS 发起解析请求的传统方式,可以避免 Local DNS 造成的域名劫持和跨网访问问题,解决移动互联网服务中域名解析异常带来的困扰。

微信小程序开发者使用移动解析说明

  1. 前往微信服务市场选购 HttpDNS 资源,并在服务详情页-接入文档获取 Service ID。
  2. 微信小程序调用 wx.request,将 enableHttpDNS 参数设置为 true,并在 httpDNSServiceId 参数中填入选用的服务商 Service ID。

代码示例

wx.request({
  url: 'example.php', //仅为示例,并非真实的接口地址
  enableHttpDNS: true,
  httpDNSServiceId: 'wxa410372c837a5f26',
  success(res) {
    console.log('request success', res)
  },
  fail(res) {
    console.error('request fail', res)
  }
})

计费说明

  1. 使用服务所产生的费用会按照实际调用服务商接口情况进行计费,定价策略由服务提供方制定,开发者需自行前往微信服务市场进行购买、续费等操作。
  2. 微信侧每次代开发者调用服务商接口时,微信侧会进行缓存,缓存策略由服务商返回的 ttl 决定,因此不一定每次调用 request 接口都会产生费用。
  3. 从基础库 v2.32.1 开始,若开发者的服务可用额度为 0,仍在 wx.request 接口中声明使用服务商提供的移动解析能力时,会使用 localDNS 解析来兜底,并在 success 回调参数 exception.reasons ( reasons 是数组) 中返回 httpdns 欠费的错误信息和错误码,类似 [{ "errMsg": "getDNSInfo:fail no enough httpdns quota", "errno": 602103 }]

注意事项

  1. HttpDNS 不兼容网络代理

在基础库 v2.22.1 版本之前,当用户设备使用了网络代理,同时又开启了 enableHttpDNS 时,request 接口会调用失败,fail 回调 errMsg 中会包含 ERR_PROXY_CONNECTION_FAILED 字样,如{"errno":600001,"errMsg":"request:fail -130:net::ERR_PROXY_CONNECTION_FAILED"}{"errno":600001,"errMsg":"request:fail errcode:-130 cronet_error_code:-130 error_msg:net::ERR_PROXY_CONNECTION_FAILED"}

为解决此问题,从基础库 v2.22.1 开始,若用户使用了网络代理,基础库会主动强制关闭 enableHttpDNS。开发者也可以通过 wx.getNetworkType 接口检查用户是否开启了网络代理。用法:

wx.getNetworkType({
  success(res) {
    console.log(res.hasSystemProxy) // 开启网络代理时为 true,否则为 false
  }
})

HttpDNS 相关错误码

错误码 说明
600000 网络错误
602000 网络请求错误
602001 系统错误
602002 http请求httpdns服务商错误
602101 微信小程序未在服务市场购买httpdns服务
602102 微信小程序在httpdns服务市场资源包过期
602103 微信小程序在httpdns服务市场额度不足
602104 httpdns服务商返回结果为空
602105 调用httpdns服务商结果超时
602106 httpdns服务商返回数据不合法
602107 httpdns域名解析结果为空
602108 不支持的httpdns服务商id

移动解析服务提供商接入说明

为了保护微信客户端的安全,微信小程序使用的移动解析服务需要通过微信侧安全认证,认证后可在微信服务市场上架。

微信侧欢迎更多服务商为微信小程序提供移动解析服务。申请接入按照以下模板发送邮件,我们会有专人与您联系。

收件人:servicemarket@tencent.com
主题:【上架服务市场】XXX(服务商)申请上架HttpDNS服务
正文:需要写明服务商的基本资料,包括不仅限于服务商名称、业务范围、技术证书、合作意向、联系方式等

存储

每个微信小程序都可以有自己的本地缓存,可以通过 wx.setStorage/wx.setStorageSync、wx.getStorage/wx.getStorageSync、wx.clearStorage/wx.clearStorageSync,wx.removeStorage/wx.removeStorageSync 对本地缓存进行读写和清理。

隔离策略

同一个微信用户,同一个微信小程序 storage 上限为 10MB。storage 以用户维度隔离,同一台设备上,A 用户无法读取到 B 用户的数据;不同微信小程序之间也无法互相读写数据。

插件隔离策略

  1. 同一微信小程序使用不同插件:不同插件之间,插件与微信小程序之间 storage 不互通。
  2. 不同微信小程序使用同一插件:同一插件 storage 不互通。

清理策略

本地缓存的清理时机跟代码包一样,只有在代码包被清理的时候本地缓存才会被清理。

文件系统

文件系统是微信小程序提供的一套以微信小程序和用户维度隔离的存储以及一套相应的管理接口。通过 wx.getFileSystemManager() 可以获取到全局唯一的文件系统管理器,所有文件系统的管理操作通过 FileSystemManager 来调用。

var fs = wx.getFileSystemManager()

文件主要分为两大类:

  • 代码包文件:代码包文件指的是在项目目录中添加的文件。
  • 本地文件:通过调用接口本地产生,或通过网络下载下来,存储到本地的文件。

其中本地文件又分为三种:

  1. 本地临时文件:临时产生,随时会被回收的文件。运行时最多存储 4GB,结束运行后,如果已使用超过 2GB,会以文件为维度按照最近使用时间从远到近进行清理至少于2GB。
  2. 本地缓存文件:微信小程序通过接口把本地临时文件缓存后产生的文件,不能自定义目录和文件名。跟本地用户文件共计,微信小程序(含小游戏)最多可存储 200MB。
  3. 本地用户文件:微信小程序通过接口把本地临时文件缓存后产生的文件,允许自定义目录和文件名。跟本地缓存文件共计,微信小程序(含小游戏)最多可存储 200MB。

代码包文件

由于代码包文件大小限制,代码包文件适用于放置首次加载时需要的文件,对于内容较大或需要动态替换的文件,不推荐用添加到代码包中,推荐在小游戏启动之后再用下载接口下载到本地。

访问代码包文件

代码包文件的访问方式是从项目根目录开始写文件路径,不支持相对路径的写法。如:/a/b/ca/b/c 都是合法的,./a/b/c ../a/b/c 则不合法。 image.png

修改代码包文件

代码包内的文件无法在运行后动态修改或删除,修改代码包文件需要重新发布版本。

本地文件

本地文件指的是微信小程序被用户添加到手机后,会有一块独立的文件存储区域,以用户维度隔离。即同一台手机,每个微信用户不能访问到其他登录用户的文件,同一个用户不同 appId 之间的文件也不能互相访问。 本地文件沙盒.png

本地文件的文件路径均为以下格式:

{{协议名}}://文件路径

其中,协议名在 iOS/Android 客户端为 "wxfile",在开发者工具上为 "http",开发者无需关注这个差异,也不应在代码中去硬编码完整文件路径。

本地临时文件

本地临时文件只能通过调用特定接口产生,不能直接写入内容。本地临时文件产生后,仅在当前生命周期内保证有效,重启之后不一定可用。如果需要保证在下次启动时无需下载,可通过 FileSystemManager.saveFile() 或 FileSystemManager.copyFile() 接口把本地临时文件转换成本地缓存文件或本地用户文件。

临时文件的清理策略为:微信小程序退出后系统会检查该微信小程序的临时文件占用,若不超过2GB则不进行清理,超过上限则以文件为维度按照最近使用时间从远到近进行清理。同时也会检查所有微信小程序的临时文件占用,若超过6GB则以微信小程序为维度进行清理。

因此,开发者在下载临时文件时,可先通过FileSystemManager.access()检查该文件是否存在,减少重复文件下载,提升用户体验。

示例

wx.chooseImage({
  success: function (res) {
    var tempFilePaths = res.tempFilePaths // tempFilePaths 的每一项是一个本地临时文件路径
  }
})

本地缓存文件

本地缓存文件只能通过调用特定接口产生,不能直接写入内容。本地缓存文件产生后,重启之后仍可用。本地缓存文件只能通过 FileSystemManager.saveFile() 接口将本地临时文件保存获得。

示例

fs.saveFile({
  tempFilePath: '', // 传入一个本地临时文件路径
  success(res) {
    console.log(res.savedFilePath) // res.savedFilePath 为一个本地缓存文件路径
  }
})

注意:本地缓存文件是最初的设计,1.7.0 版本开始,提供了功能更完整的本地用户文件,可以完全覆盖本地缓存文件的功能,如果不需要兼容低于 1.7.0 版本,可以不使用本地缓存文件。

本地用户文件

本地用户文件是从 1.7.0 版本开始新增的概念。我们提供了一个用户文件目录给开发者,开发者对这个目录有完全自由的读写权限。通过 wx.env.USER_DATA_PATH 可以获取到这个目录的路径。

示例

// 在本地用户文件目录下创建一个文件 hello.txt,写入内容 "hello, world"
const fs = wx.getFileSystemManager()
fs.writeFileSync(`${wx.env.USER_DATA_PATH}/hello.txt`,  'hello, world', 'utf8')

读写权限

接口、组件
代码包文件
本地临时文件
本地缓存文件
本地用户文件

清理策略

  • 本地临时文件只保证在微信小程序当前生命周期内,一旦微信小程序被关闭就可能被清理,即下次冷启动不保证可用。
  • 本地缓存文件和本地用户文件的清理时机跟代码包一样,只有在代码包被清理的时会被清理。

Canvas 画布

canvas 组件 提供了绘制界面,可以在之上进行任意绘制

基础使用

第一步:在 WXML 中添加 canvas 组件

<!-- 2d 类型的 canvas -->
<canvas id="myCanvas" type="2d" style="border: 1px solid; width: 300px; height: 150px;" />

首先需要在 WXML 中添加 canvas 组件。

指定 id="myCanvas" 唯一标识一个 canvas,用于后续获取 Canvas 对象。

指定 type 用于定义画布类型,本例子使用 type="2d" 示例。

第二步:获取 Canvas 对象和渲染上下文

this.createSelectorQuery()
    .select('#myCanvas') // 在 WXML 中填入的 id
    .fields({ node: true, size: true })
    .exec((res) => {
        // Canvas 对象
        const canvas = res[0].node
        // 渲染上下文
        const ctx = canvas.getContext('2d')
    })

通过 SelectorQuery 选择上一步的 canvas,可以获取到 Canvas 对象。

再通过 Canvas.getContext,我们可以获取到 渲染上下文 RenderingContext。

后续的画布操作与渲染操作,都需要通过这两个对象来实现。

第三步:初始化 Canvas

this.createSelectorQuery()
    .select('#myCanvas') // 在 WXML 中填入的 id
    .fields({ node: true, size: true })
    .exec((res) => {
        // Canvas 对象
        const canvas = res[0].node
        // 渲染上下文
        const ctx = canvas.getContext('2d')

        // Canvas 画布的实际绘制宽高
        const width = res[0].width
        const height = res[0].height

        // 初始化画布大小
        const dpr = wx.getWindowInfo().pixelRatio
        canvas.width = width * dpr
        canvas.height = height * dpr
        ctx.scale(dpr, dpr)
    })

canvas 的宽高分为渲染宽高和逻辑宽高:

  • 渲染宽高为 canvas 画布在页面中所实际占用的宽高大小,即通过对节点进行 boundingClientRect 请求获取到的大小。
  • 逻辑宽高为 canvas 在渲染过程中的逻辑宽高大小,如绘制一个长方形与逻辑宽高相同,最终长方形会占满整个画布。逻辑宽高默认为 300 * 150

不同的设备上,存在物理像素和逻辑像素不相等的情况,所以一般我们需要用 wx.getWindowInfo 获取设备的像素比,乘上 canvas 的渲染大小,作为画布的逻辑大小。

第四步:进行绘制

在开发者工具中预览效果

// 省略上面初始化步骤,已经获取到 canvas 对象和 ctx 渲染上下文

// 清空画布
ctx.clearRect(0, 0, width, height)

// 绘制红色正方形
ctx.fillStyle = 'rgb(200, 0, 0)';
ctx.fillRect(10, 10, 50, 50);

// 绘制蓝色半透明正方形
ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
ctx.fillRect(30, 30, 50, 50);

通过 渲染上下文 上的绘图 api,我们可以在画布上进行任意的绘制。

进阶使用

绘制图片

在开发者工具中预览效果

// 省略上面初始化步骤,已经获取到 canvas 对象和 ctx 渲染上下文

// 图片对象
const image = canvas.createImage()
// 图片加载完成回调
image.onload = () => {
    // 将图片绘制到 canvas 上
    ctx.drawImage(image, 0, 0)
}
// 设置图片src
image.src = 'https://open.weixin.qq.com/zh_CN/htmledition/res/assets/res-design-download/icon64_wx_logo.png'

通过 Canvas.createImage 我们可以创建图片对象并加载图片。当图片加载完成触发 onload 回调之后,使用 ctx.drawImage 即可将图片绘制到 canvas 上。

生成图片

在开发者工具中预览效果

// 省略上面初始化步骤,已经获取到 canvas 对象和 ctx 渲染上下文

// 绘制红色正方形
ctx.fillStyle = 'rgb(200, 0, 0)';
ctx.fillRect(10, 10, 50, 50);

// 绘制蓝色半透明正方形
ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
ctx.fillRect(30, 30, 50, 50);

// 生成图片
wx.canvasToTempFilePath({
    canvas,
    success: res => {
        // 生成的图片临时文件路径
        const tempFilePath = res.tempFilePath
    },
})

通过 wx.canvasToTempFilePath 接口,可以将 canvas 上的内容生成图片临时文件。

帧动画

在开发者工具中预览效果

// 省略上面初始化步骤,已经获取到 canvas 对象和 ctx 渲染上下文

const startTime = Date.now()

// 帧渲染回调
const draw = () => {
  const time = Date.now()
  // 计算经过的时间
  const elapsed = time - startTime

  // 计算动画位置
  const n = Math.floor(elapsed / 3000)
  const m = elapsed % 3000
  const dx = (n % 2 ? 0 : 1) + (n % 2 ? 1 : -1) * (m < 2500 ? easeOutBounce(m / 2500) : 1)
  const x = (width - 50) * dx

  // 渲染
  ctx.clearRect(0, 0, width, height)
  ctx.fillStyle = 'rgb(200, 0, 0)';
  ctx.fillRect(x, height / 2 - 25, 50, 50);

  // 注册下一帧渲染
  canvas.requestAnimationFrame(draw)
}

draw()

通过 Canvas.requestAnimationFrame 可以注册动画帧回调,在回调内进行动画的逐帧绘制。

自定义字体

通过 wx.loadFontFace 可以为 Canvas 加载自定义字体。

在开发者工具中预览效果

录制视频

通过 MediaRecorder 可以将 Canvas 内容录制为视频并保存。

在开发者工具中预览效果

WebGL

在开发者工具中预览效果

<canvas type="webgl" id="myCanvas" />
// 省略上面初始化步骤,已经获取到 canvas 对象

const gl = canvas.getContext('webgl') // 获取 webgl 渲染上下文

旧版 Canvas 迁移指南

微信小程序的 旧版 canvas 接口 已经不再维护,本指南将指引如何迁移至新版 Canvas 2D 接口。

特性差异

旧版 canvas 接口 Canvas 2D 接口
同层渲染 不支持 支持
api支持 部分支持 支持全部 Web 标准
绘制 异步绘制 同步绘制
性能

迁移步骤

第一步:修改 WXML

<canvas canvas-id="myCanvas" />
<!-- 修改为以下 -->
<canvas id="myCanvas" type="2d" />

旧版 canvas 接口使用 canvas-id 属性唯一标识 canvas;新版 Canvas 2D 可直接使用 id 标识。

另外需要给 canvas 添加 type="2d" 属性标识为新版 Canvas 2D 接口。

第二步:修改获取 CanvasContext

const context = wx.createCanvasContext('myCanvas')
//
// 修改为以下
//
this.createSelectorQuery()
    .select('#myCanvas') // 在 WXML 中填入的 id
    .node(({ node: canvas }) => {
        const context = canvas.getContext('2d')
    })
    .exec()

旧版 canvas 接口使用 wx.createCanvasContext 同步获取 CanvasContext。

新版 Canvas 2D 接口需要先通过 SelectorQuery 异步获取 Canvas 对象,再通过 Canvas.getContext 获取渲染上下文 RenderingContext。

第三步:画布大小初始化

// 旧版 canvas 不能修改宽高
this.createSelectorQuery()
    .select('#myCanvas') // 在 WXML 中填入的 id
    .fields({ node: true, size: true })
    .exec((res) => {
        // Canvas 对象
        const canvas = res[0].node
        // Canvas 画布的实际绘制宽高
        const renderWidth = res[0].width
        const renderHeight = res[0].height
        // Canvas 绘制上下文
        const ctx = canvas.getContext('2d')

        // 初始化画布大小
        const dpr = wx.getWindowInfo().pixelRatio
        canvas.width = renderWidth * dpr
        canvas.height = renderHeight * dpr
        ctx.scale(dpr, dpr)
    })

旧版 canvas 接口的画布大小是根据实际渲染宽度决定的,开发者无法修改。

新版 Canvas 2D 接口允许开发者自由修改画布的逻辑大小,默认宽高为 300*150。

不同的设备上,存在物理像素和逻辑像素不相等的情况,所以一般我们需要用 wx.getWindowInfo 获取设备的像素比,乘上 canvas 的实际大小。

第四步:修改绘制方法

// 若干绘制调用
context.fillRect(0, 0, 50, 50)
context.fillRect(20, 20, 50, 50)

context.draw(false, () => {
    // 这里绘制完成
    console.log('draw done')
})

//
// 修改为以下
//

// 绘制前清空画布
context.clearRect(0, 0, canvas.width, canvas.height)
// 若干绘制调用
context.fillRect(0, 0, 50, 50)
context.fillRect(20, 20, 50, 50)

// 这里绘制完成
console.log('draw done')

旧版 canvas 接口绘制需要调用 CanvasContext.draw 才会进行绘制,并且绘制过程是异步的,需要等待绘制完成回调才能进行下一步操作。

新版 Canvas 2D 接口不再需要调用 draw 函数,所有绘制方法都会同步绘制到画布上。

需要注意的是 CanvasContext.draw 函数第一个参数控制在绘制前是否保留上一次绘制(默认值为 false,即不保留),若设置为 false,则迁移至新接口后,需要在绘制前通过 clearRect 清空画布。

第五步:修改图片绘制

context.drawImage(
    'https://open.weixin.qq.com/zh_CN/htmledition/res/assets/res-design-download/icon64_wx_logo.png',
    0,
    0,
    150,
    100,
)
//
// 修改为以下
//
const image = canvas.createImage()
image.onload = () => {
    context.drawImage(
        image,
        0,
        0,
        150,
        100,
    )
}
image.src = 'https://open.weixin.qq.com/zh_CN/htmledition/res/assets/res-design-download/icon64_wx_logo.png'

旧版 canvas 接口 CanvasContext.drawImage 直接传入图片 url 进行绘制。

新版 Canvas 2D 接口需要先通过 Canvas.createImage 创建图片对象,onload 图片加载完成回调触发后,再将图片对象传入 context.drawImage 进行绘制。

其余接口调整

wx.canvasToTempFilePath

wx.canvasToTempFilePath({
    canvasId: 'myCanvas',
    success(res) {
        //
    }
})
//
// 修改为以下
//
wx.canvasToTempFilePath({
    canvas: canvas,
    success(res) {
        //
    }
})

旧版 canvas 接口传入 canvas-id

新版 Canvas 2D 接口需要直接传入 Canvas 实例

wx.canvasPutImageData

wx.canvasPutImageData({
    canvasId: 'myCanvas',
    x: 0,
    y: 0,
    width: 1,
    height: 1,
    data: data,
    success (res) {
        // after put image data
    }
})
//
// 修改为以下
//
const context = canvas.getContext('2d')
context.putImageData(data, 0, 0, 0, 0, 1, 1)
// after put image data

新版 canvas 不支持 wx.canvasPutImageData,应使用 context.putImageData 代替。

wx.canvasGetImageData

wx.canvasGetImageData({
    canvasId: 'myCanvas',
    x: 0,
    y: 0,
    width: 100,
    height: 100,
    success(res) {
        console.log(res.width) // 100
        console.log(res.height) // 100
        console.log(res.data instanceof Uint8ClampedArray) // true
        console.log(res.data.length) // 100 * 100 * 4
    }
})
//
// 修改为以下
//
const context = canvas.getContext('2d')
const imageData = context.getImageData(0, 0, 100, 100)
console.log(imageData.width) // 100
console.log(imageData.height) // 100
console.log(imageData.data instanceof Uint8ClampedArray) // true
console.log(imageData.data.length) // 100 * 100 * 4

新版 canvas 不支持 wx.canvasGetImageData,应使用 context.getImageData 代替。

wx.loadFontFace

wx.loadFontFace({
  family: 'Bitstream Vera Serif Bold',
  source: 'url("https://sungd.github.io/Pacifico.ttf")',
  success: console.log
})
//
// 修改为以下
//
wx.loadFontFace({
  family: 'Bitstream Vera Serif Bold',
  source: 'url("https://sungd.github.io/Pacifico.ttf")',
  scopes: ['webview', 'native'],
  success: console.log
})

新版 Canvas 2D 接口需要为 scopes 设置 native

分包加载

微信客户端 6.6.0,基础库 1.7.3 及以上版本开始支持。开发者工具请使用 1.01.1712150 及以上版本,可点此下载。

某些情况下,开发者需要将微信小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。

在构建微信小程序分包项目时,构建会输出一个或多个分包。每个使用分包的微信小程序必定含有一个主包。所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到的公共资源/JS 脚本;而分包则是根据开发者的配置进行划分。

在微信小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。

目前微信小程序分包大小有以下限制:

  • 整个微信小程序所有分包大小不超过 30M(服务商代开发的微信小程序不超过 20M)
  • 单个分包/主包大小不能超过 2M

对微信小程序进行分包,可以优化微信小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。

具体使用方法请参考:

  • 使用分包
  • 独立分包
  • 分包预下载
  • 分包异步化

使用分包

配置方法

假设支持分包的微信小程序目录结构如下:

├── app.js
├── app.json
├── app.wxss
├── packageA
│   └── pages
│       ├── cat
│       └── dog
├── packageB
│   └── pages
│       ├── apple
│       └── banana
├── pages
│   ├── index
│   └── logs
└── utils

开发者通过在 app.json 的 subPackages 字段中声明微信小程序的项目分包结构:

写成 subpackages 也是支持的。

{
  "pages":[
    "pages/index",
    "pages/logs"
  ],
  "subPackages": [
    {
      "root": "packageA",
      "pages": [
        "pages/cat",
        "pages/dog"
      ],
      "entry": "index.js"
    }, {
      "root": "packageB",
      "name": "pack2",
      "pages": [
        "pages/apple",
        "pages/banana"
      ]
    }
  ]
}

subPackages 中,每个分包的配置包含以下几项:

字段 类型 说明
root String 分包根目录
name String 分包别名,在分包预下载时可以使用
pages StringArray 分包页面路径,相对于分包根目录
independent Boolean 分包是否是独立分包
entry String 分包入口文件

打包原则

  • 声明 subPackages 后,会按照 subPackages 配置的路径进行打包,subPackages 配置路径之外的目录会被打包到主包中
  • 主包也可以有自己的 pages,即最外层的 pages 字段。
  • subPackages 的根目录不能是另一个 subPackages 内的子目录
  • tabBar 页面必须在主包内

引用原则

  • packageA 无法引用 packageB 的 JS 文件,但可以引用主包和 packageA 内的 JS 文件;使用分包异步化时不受此限制
  • packageA 无法导入 packageB 的 template,但可以引用主包和 packageA 内的 template
  • packageA 无法使用 packageB 的资源,但可以使用主包和 packageA 内的资源

分包入口文件

每个分包的配置中,entry 字段可以指定该分包中的任意一个 JS 文件作为入口文件,这个文件会在分包注入时首先被执行。

指定的 JS 文件应该填写相对于分包根目录的路径,例如需要指定 /path/to/subPackage/src/index.js 作为分包 /path/to/subPackage 的入口文件时,应填写 src/index.js

调试这个功能需要 1.06.2406242 或以上版本的微信开发者工具,正式环境没有版本要求。

在开发者工具中预览效果

示例项目

下载 微信小程序示例(分包加载版)源码