动画

界面动画的常见方式

微信小程序中,通常可以使用 CSS 渐变 和 CSS 动画 来创建简易的界面动画。

在开发者工具中预览效果

动画过程中,可以使用 bindtransitionend bindanimationstart bindanimationiteration bindanimationend 来监听动画事件。

事件名 含义
transitionend CSS 渐变结束或 wx.createAnimation 结束一个阶段
animationstart CSS 动画开始
animationiteration CSS 动画结束一个阶段
animationend CSS 动画结束

注意:这几个事件都不是冒泡事件,需要绑定在真正发生了动画的节点上才会生效。

同时,还可以使用 wx.createAnimation 接口来动态创建简易的动画效果。(新版微信小程序基础库中推荐使用下述的关键帧动画接口代替。)

关键帧动画

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

从微信小程序基础库 2.9.0 开始支持一种更友好的动画创建方式,用于代替旧的 wx.createAnimation 。它具有更好的性能和更可控的接口。

在页面或自定义组件中,当需要进行关键帧动画时,可以使用 this.animate 接口:

this.animate(selector, keyframes, duration, callback)

参数说明

属性 类型 默认值 必填 说明
selector String 选择器(同 SelectorQuery.select 的选择器格式)
keyframes Array 关键帧信息
duration Number 动画持续时长(毫秒为单位)
callback function 动画完成后的回调函数

keyframes 中对象的结构

属性 类型 默认值 必填 说明
offset Number 关键帧的偏移,范围[0-1]
ease String linear 动画缓动函数
transformOrigin String 基点位置,即 CSS transform-origin
backgroundColor String 背景颜色,即 CSS background-color
opacity Number 不透明度,即 CSS opacity
width String 宽度,即 CSS width
height String 高度,即 CSS height
left String 左边位置,即 CSS left
top String 顶边位置,即 CSS top
right String 右边位置,即 CSS right
bottom String 底边位置,即 CSS bottom
matrix Array 变换矩阵,即 CSS transform matrix
matrix3d Array 三维变换矩阵,即 CSS transform matrix3d
rotate Number 旋转,即 CSS transform rotate
rotate3d Array 三维旋转,即 CSS transform rotate3d
rotateX Number X 方向旋转,即 CSS transform rotateX
rotateY Number Y 方向旋转,即 CSS transform rotateY
rotateZ Number Z 方向旋转,即 CSS transform rotateZ
scale Array 缩放,即 CSS transform scale
scale3d Array 三维缩放,即 CSS transform scale3d
scaleX Number X 方向缩放,即 CSS transform scaleX
scaleY Number Y 方向缩放,即 CSS transform scaleY
scaleZ Number Z 方向缩放,即 CSS transform scaleZ
skew Array 倾斜,即 CSS transform skew
skewX Number X 方向倾斜,即 CSS transform skewX
skewY Number Y 方向倾斜,即 CSS transform skewY
translate Array 位移,即 CSS transform translate
translate3d Array 三维位移,即 CSS transform translate3d
translateX Number X 方向位移,即 CSS transform translateX
translateY Number Y 方向位移,即 CSS transform translateY
translateZ Number Z 方向位移,即 CSS transform translateZ

示例代码

在开发者工具中预览效果


  this.animate('#container', [
    { opacity: 1.0, rotate: 0, backgroundColor: '#FF0000' },
    { opacity: 0.5, rotate: 45, backgroundColor: '#00FF00'},
    { opacity: 0.0, rotate: 90, backgroundColor: '#FF0000' },
    ], 5000, function () {
      this.clearAnimation('#container', { opacity: true, rotate: true }, function () {
        console.log("清除了#container上的opacity和rotate属性")
      })
  }.bind(this))

  this.animate('.block', [
    { scale: [1, 1], rotate: 0, ease: 'ease-out'  },
    { scale: [1.5, 1.5], rotate: 45, ease: 'ease-in', offset: 0.9},
    { scale: [2, 2], rotate: 90 },
  ], 5000, function () {
    this.clearAnimation('.block', function () {
      console.log("清除了.block上的所有动画属性")
    })
  }.bind(this))

调用 animate API 后会在节点上新增一些样式属性覆盖掉原有的对应样式。如果需要清除这些样式,可在该节点上的动画全部执行完毕后使用 this.clearAnimation 清除这些属性。

this.clearAnimation(selector, options, callback)

参数说明

属性 类型 默认值 必填 说明
selector String 选择器(同 SelectorQuery.select 的选择器格式)
options Object 需要清除的属性,不填写则全部清除
callback Function 清除完成后的回调函数

滚动驱动的动画

我们发现,根据滚动位置而不断改变动画的进度是一种比较常见的场景,这类动画可以让人感觉到界面交互很连贯自然,体验更好。因此,从微信小程序基础库 2.9.0 开始支持一种由滚动驱动的动画机制。

基于上述的关键帧动画接口,新增一个 ScrollTimeline 的参数,用来绑定滚动元素(目前只支持 scroll-view)。接口定义如下:

this.animate(selector, keyframes, duration, ScrollTimeline)

ScrollTimeline 中对象的结构

属性 类型 默认值 必填 说明
scrollSource String 指定滚动元素的选择器(只支持 scroll-view),该元素滚动时会驱动动画的进度
orientation String vertical 指定滚动的方向。有效值为 horizontal 或 vertical
startScrollOffset Number 指定开始驱动动画进度的滚动偏移量,单位 px
endScrollOffset Number 指定停止驱动动画进度的滚动偏移量,单位 px
timeRange Number 起始和结束的滚动范围映射的时间长度,该时间可用于与关键帧动画里的时间 (duration) 相匹配,单位 ms

示例代码

在开发者工具中预览效果

  this.animate('.avatar', [{
    borderRadius: '0',
    borderColor: 'red',
    transform: 'scale(1) translateY(-20px)',
    offset: 0,
  }, {
    borderRadius: '25%',
    borderColor: 'blue',
    transform: 'scale(.65) translateY(-20px)',
    offset: .5,
  }, {
    borderRadius: '50%',
    borderColor: 'blue',
    transform: `scale(.3) translateY(-20px)`,
    offset: 1
  }], 2000, {
    scrollSource: '#scroller',
    timeRange: 2000,
    startScrollOffset: 0,
    endScrollOffset: 85,
  })

  this.animate('.search_input', [{
    opacity: '0',
    width: '0%',
  }, {
    opacity: '1',
    width: '100%',
  }], 1000, {
    scrollSource: '#scroller',
    timeRange: 1000,
    startScrollOffset: 120,
    endScrollOffset: 252
  })

高级的动画方式

在一些复杂场景下,上述的动画方法可能并不适用。

WXS 响应事件 的方式可以通过使用 WXS 来响应事件的方法来动态调整节点的 style 属性。通过不断改变 style 属性的值可以做到动画效果。同时,这种方式也可以根据用户的触摸事件来动态地生成动画。

连续使用 setData 来改变界面的方法也可以达到动画的效果。这样可以任意地改变界面,但通常会产生较大的延迟或卡顿,甚至导致微信小程序僵死。此时可以通过将页面的 setData 改为 自定义组件 中的 setData 来提升性能。下面的例子是使用 setData 来实现秒表动画的示例。

在开发者工具中预览效果

初始渲染缓存

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

初始渲染缓存工作原理

微信小程序页面的初始化分为两个部分。

  • 逻辑层初始化:载入必需的微信小程序代码、初始化页面 this 对象(也包括它涉及到的所有自定义组件的 this 对象)、将相关数据发送给视图层。
  • 视图层初始化:载入必需的微信小程序代码,然后等待逻辑层初始化完毕并接收逻辑层发送的数据,最后渲染页面。

在启动页面时,尤其是微信小程序冷启动、进入第一个页面时,逻辑层初始化的时间较长。在页面初始化过程中,用户将看到微信小程序的标准载入画面(冷启动时)或可能看到轻微的白屏现象(页面跳转过程中)。

启用初始渲染缓存,可以使视图层不需要等待逻辑层初始化完毕,而直接提前将页面初始 data 的渲染结果展示给用户,这可以使得页面对用户可见的时间大大提前。它的工作原理如下:

  • 在微信小程序页面第一次被打开后,将页面初始数据渲染结果记录下来,写入一个持久化的缓存区域(缓存可长时间保留,但可能因为微信小程序更新、基础库更新、储存空间回收等原因被清除);
  • 在这个页面被第二次打开时,检查缓存中是否还存有这个页面上一次初始数据的渲染结果,如果有,就直接将渲染结果展示出来;
  • 如果展示了缓存中的渲染结果,这个页面暂时还不能响应用户事件,等到逻辑层初始化完毕后才能响应用户事件。

利用初始渲染缓存,可以:

  • 快速展示出页面中永远不会变的部分,如导航栏;
  • 预先展示一个骨架页,提升用户体验;
  • 展示自定义的加载提示;
  • 提前展示广告,等等。

支持的组件

在初始渲染缓存阶段中,复杂组件不能被展示或不能响应交互。

目前支持的内置组件:

  • <view />
  • <text />
  • <button />
  • <image />
  • <scroll-view />
  • <rich-text />

自定义组件本身可以被展示(但它们里面用到的内置组件也遵循上述限制)。

静态初始渲染缓存

若想启用初始渲染缓存,最简单的方法是在页面的 json 文件中添加配置项 "initialRenderingCache": "static"

{
  "initialRenderingCache": "static"
}

如果想要对所有页面启用,可以在 app.jsonwindow 配置段中添加这个配置:

{
  "window": {
    "initialRenderingCache": "static"
  }
}

添加这个配置项之后,在手机中预览微信小程序首页,然后杀死微信小程序再次进入,就会通过初始渲染缓存来渲染首页。

注意:这种情况下,初始渲染缓存记录的是页面 data 应用在页面 WXML 上的结果,不包含任何 setData 的结果。

例如,如果想要在页面中展示出“正在加载”几个字,这几个字受到 loading 数据字段控制:

<view wx:if="{{loading}}">正在加载</view>

这种情况下, loading 应当在 data 中指定为 true ,如:

// 正确的做法
Page({
  data: {
    loading: true
  }
})

而不能通过 setDataloading 置为 true

// 错误的做法!不要这么做!
Page({
  data: {},
  onLoad: function() {
    this.setData({
      loading: true
    })
  }
})

换而言之,这种做法只包含页面 data 的渲染结果,即页面的纯静态成分。

自定义渲染缓存时机

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

静态初始渲染缓存只在页面首次渲染完成后生成渲染缓存,在 onLoad 或之后的生命周期新增的页面内容无法进入缓存中。如果想要在自定义的时机生成缓存,可以配置 "initialRenderingCache": "capture"

{
  "initialRenderingCache": "capture"
}

此时,初始渲染缓存不会被自动启用,还需要在页面中调用 this.setInitialRenderingCache() 才能启用。

Page({
  data: {
    loading: true
  },
  onLoad: function() {
    this.setData({
      loading: false
    })
    this.setInitialRenderingCache() // 渲染缓存会在此时生成
  }
})

在初始渲染缓存中添加动态内容

有些场景中,只是页面 data 的渲染结果会比较局限。有时会想要额外展示一些可变的内容。

这种情况下可以使用“动态”初始渲染缓存的方式。首先,配置 "initialRenderingCache": "dynamic"

{
  "initialRenderingCache": "dynamic"
}

此时,初始渲染缓存不会被自动启用,还需要在页面中调用 this.setInitialRenderingCache(dynamicData) 才能启用。其中, dynamicData 是一组数据,与 data 一起参与页面 WXML 渲染。

Page({
  data: {
    loading: true
  },
  onReady: function() {
    this.setInitialRenderingCache({
      loadingHint: '正在加载' // 这一部分数据不会真的参与页面渲染,仅仅用于生成缓存。也并*不等价*于一次 setData 调用
    })
  }
})
<view wx:if="{{loading}}">{{loadingHint}}</view>

从原理上说,在动态生成初始渲染缓存的方式下,页面会在后台使用动态数据重新渲染一次,因而开销相对较大。因而要尽量避免频繁调用 this.setInitialRenderingCache ,如果在一个页面内多次调用,仅最后一次调用生效。

注意:

  • this.setInitialRenderingCache 调用时机不能早于 PageonReadyComponentready 生命周期,否则可能对性能有负面影响。
  • 如果想禁用初始渲染缓存,调用 this.setInitialRenderingCache(null)

微信小程序的运行环境

微信小程序运行在多种平台上:iOS/iPadOS 微信客户端、Android 微信客户端、Windows PC 微信客户端、Mac 微信客户端、微信小程序硬件框架和用于调试的微信开发者工具等。

不同运行环境下,脚本执行环境以及用于组件渲染的环境是不同的,性能表现也存在差异:

  • 在 iOS、iPadOS 和 Mac OS 上,微信小程序逻辑层的 JavaScript 代码运行在 JavaScriptCore 中,视图层是由 WKWebView 来渲染的,环境有 iOS 14、iPad OS 14、Mac OS 11.4 等;
  • 在 Android 上,微信小程序逻辑层的 JavaScript 代码运行在 V8 中,视图层是由基于 Mobile Chromium 内核的微信自研 XWeb 引擎来渲染的;
  • 在 Windows 上,微信小程序逻辑层 JavaScript 和视图层都是用 Chromium 内核;
  • 在 开发工具上,微信小程序逻辑层的 JavaScript 代码是运行在 NW.js 中,视图层是由 Chromium Webview 来渲染的。

JavaScriptCore 无法开启 JIT 编译 (Just-In-Time Compiler),同等条件下的运行性能要明显低于其他平台。

平台差异

尽管各运行环境是十分相似的,但是还是有些许区别:

  • JavaScript 语法和 API 支持不一致:语法上开发者可以通过开启 ES6ES5 的功能来规避(详情);此外,微信小程序基础库内置了必要的Polyfill,来弥补API的差异(详情)。

  • WXSS 渲染表现不一致:尽管可以通过开启样式补全来规避大部分的问题,还是建议开发者需要在各端分别检查微信小程序的真实表现。

开发者工具仅供调试使用,最终的表现以客户端为准。

微信小程序运行机制

1. 微信小程序的生命周期

微信小程序从启动到最终被销毁,会经历很多不同的状态,微信小程序在不同状态下会有不同的表现。

小程序生命周期

1.1 微信小程序启动

从用户认知的角度看,广义的微信小程序启动可以分为两种情况,一种是冷启动,一种是热启动

  • 冷启动:如果用户首次打开,或微信小程序销毁后被用户再次打开,此时微信小程序需要重新加载启动,即冷启动。
  • 热启动:如果用户已经打开过某微信小程序,然后在一定时间内再次打开该微信小程序,此时微信小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动。

从微信小程序生命周期的角度来看,我们一般讲的「启动」专指冷启动,热启动一般被称为后台切前台。

1.2 前台与后台

微信小程序启动后,界面被展示给用户,此时微信小程序处于「前台」状态。

当用户「关闭」微信小程序时,微信小程序并没有真正被关闭,而是进入了「后台」状态,此时微信小程序还可以短暂运行一小段时间,但部分 API 的使用会受到限制。切后台的方式包括但不限于以下几种:

  • 点击右上角胶囊按钮离开微信小程序
  • iOS 从屏幕左侧右滑离开微信小程序
  • 安卓点击返回键离开微信小程序
  • 微信小程序前台运行时直接把微信切后台(手势或 Home 键)
  • 微信小程序前台运行时直接锁屏

当用户再次进入微信并打开微信小程序,微信小程序又会重新进入「前台」状态。

1.3 挂起

微信小程序进入「后台」状态一段时间后(目前是 5 秒),微信会停止微信小程序 JS 线程的执行,微信小程序进入「挂起」状态。此时微信小程序的内存状态会被保留,但开发者代码执行会停止,事件和接口回调会在微信小程序再次进入「前台」时触发。

当开发者使用了后台音乐播放、后台地理位置等能力时,微信小程序可以在「后台」持续运行,不会进入到「挂起」状态

1.4 微信小程序销毁

如果用户很久没有使用微信小程序,或者系统资源紧张,微信小程序会被「销毁」,即完全终止运行。具体而言包括以下几种情形:

  • 当微信小程序进入后台并被「挂起」后,如果很长时间(目前是 30 分钟)都未再次进入前台,微信小程序会被销毁。
  • 当微信小程序占用系统资源过高,可能会被系统销毁或被微信客户端主动回收。
    • 在 iOS 上,当微信客户端在一定时间间隔内连续收到系统内存告警时,会根据一定的策略,主动销毁微信小程序,并提示用户 「运行内存不足,请重新打开该微信小程序」。具体策略会持续进行调整优化。
    • 建议微信小程序在必要时使用 wx.onMemoryWarning 监听内存告警事件,进行必要的内存清理。

微信小程序更新机制

开发者在管理后台发布新版本的微信小程序之后,微信客户端会有若干个时机去检查本地缓存的微信小程序有没有新版本,并进行微信小程序的代码包更新。但如果用户本地有微信小程序的历史版本,此时打开的可能还是旧版本。

1. 启动时同步更新

在以下情况下,微信小程序启动时会同步更新代码包。同步更新会阻塞微信小程序的启动流程,影响微信小程序的启动耗时。

如果更新失败或超时,为了保障微信小程序的可用性,还是会使用本地版本打开。

定期检查发现版本更新

微信运行时,会定期检查最近使用的微信小程序是否有更新。如果有更新,下次微信小程序启动时会同步进行更新,更新到最新版本后再打开微信小程序,尽可能保证用户能够尽快使用微信小程序的最新版本。

用户长时间未使用微信小程序

用户长时间未使用微信小程序时,为保障微信小程序版本的实时性,会强制同步检查版本更新,更新到最新版本后再打开微信小程序。

若用户处于弱网环境、下载最新版本失败等情况下,仍会启动本地的较低版本。

2. 启动时异步更新

即使启动前未发现更新,微信小程序每次冷启动时,都会异步检查是否有更新版本。如果发现有新版本,将会异步下载新版本的代码包。但当次启动仍会使用客户端本地的旧版本代码,即新版本的微信小程序需要等下一次冷启动才会使用。

开发者手动触发更新

在启动时异步更新的情况下,如果开发者希望立刻进行版本更新,可以使用 wx.getUpdateManager API 进行处理。在有新版本时提示用户重启微信小程序更新新版本。

const updateManager = wx.getUpdateManager()

updateManager.onCheckForUpdate(function (res) {
  // 请求完新版本信息的回调
  console.log(res.hasUpdate)
})

updateManager.onUpdateReady(function () {
  wx.showModal({
    title: '更新提示',
    content: '新版本已经准备好,是否重启应用?',
    success(res) {
      if (res.confirm) {
        // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
        updateManager.applyUpdate()
      }
    }
  })
})

updateManager.onUpdateFailed(function () {
  // 新版本下载失败
})

3. 微信小程序管理后台的相关设置

微信小程序开发者可以通过在微信小程序管理后台进行设置,影响更新逻辑。

优先使用本地版本设置

若开发者判断某些较新的微信小程序版本无需强制用户同步更新到最新版本,可以在微信小程序管理后台「设置」-「版本设置」-「优先使用本地版本设置」中进行设置,设置后若同步更新时检查本地版本不低于该版本,则优先使用本地版本,同时将会异步下载最新版本的代码包。

微信小程序最低可用版本设置

若开发者判断某些较旧的微信小程序版本服务不再可用,可以在微信小程序管理后台「设置」-「版本设置」-「微信小程序最低可用版本设置」中进行设置。设置后若同步更新时检查本地版本低于该版本,则无法打开,并继续尝试下载最新版本、若异步更新,则会在检查到更新后提示用户重启微信小程序更新新版本。

注意

  1. 开发者在后台发布新版本之后,无法立刻影响到所有现网用户,正常情况下,在全量发布 24 小时之后,新版本可以覆盖 99% 以上的用户。
  2. 微信小程序管理后台的「优先使用本地版本设置」和「微信小程序最低可用版本设置」不会影响同步更新与异步更新的选择。

需要帮助

简介

微信小程序一直以来采用的都是 AppService 和 WebView 的双线程模型,基于 WebView 和原生控件混合渲染的方式,微信小程序优化扩展了 Web 的基础能力,保证了在移动端上有良好的性能和用户体验。Web 技术至今已有 30 多年历史,作为一款强大的渲染引擎,它有着良好的兼容性和丰富的特性。 尽管各大厂商在不断优化 Web 性能,但由于其繁重的历史包袱和复杂的渲染流程,使得 Web 在移动端的表现与原生应用仍有一定差距。

为了进一步优化微信小程序性能,提供更为接近原生的用户体验,我们在 WebView 渲染之外新增了一个渲染引擎 Skyline,其使用更精简高效的渲染管线,并带来诸多增强特性,让 Skyline 拥有更接近原生渲染的性能体验。

架构

当微信小程序基于 WebView 环境下时,WebView 的 JS 逻辑、DOM 树创建、CSS 解析、样式计算、Layout、Paint (Composite) 都发生在同一线程,在 WebView 上执行过多的 JS 逻辑可能阻塞渲染,导致界面卡顿。以此为前提,微信小程序同时考虑了性能与安全,采用了目前称为「双线程模型」的架构。

在 Skyline 环境下,我们尝试改变这一情况:Skyline 创建了一条渲染线程来负责 Layout, Composite 和 Paint 等渲染任务,并在 AppService 中划出一个独立的上下文,来运行之前 WebView 承担的 JS 逻辑、DOM 树创建等逻辑。这种新的架构相比原有的 WebView 架构,有以下特点:

  • 界面更不容易被逻辑阻塞,进一步减少卡顿
  • 无需为每个页面新建一个 JS 引擎实例(WebView),减少了内存、时间开销
  • 框架可以在页面之间共享更多的资源,进一步减少运行时内存、时间开销
  • 框架的代码之间无需再通过 JSBridge 进行数据交换,减少了大量通信时间开销

而与此同时,这个新的架构能很好地保持和原有架构的兼容性,基于 WebView 环境的微信小程序代码基本上无需任何改动即可直接在新的架构下运行。WXS 由于被移到 AppService 中,虽然逻辑本身无需改动,但询问页面信息等接口会变为异步,效率也可能有所下降;为此,我们同时推出了新的 Worklet 机制,它比原有的 WXS 更靠近渲染流程,用以高性能地构建各种复杂的动画效果。

新的渲染流程如下图所示:

如果在使用过程中遇到任何问题,可以前往「Skyline 渲染引擎」专区查看说明。

特性

Skyline 以性能为首要目标,因此 CSS 特性上在满足基本需求的前提下进行了大幅精简,目前 Skyline 只保留更现代的 CSS 集合。另一方面,Skyline 又添加了大量的特性,使开发者能够构建出类原生体验的微信小程序。在编码上,Skyline 与 WebView 模式保持一致,仍使用 WXML 和 WXSS 编写界面。在不采用 Skyline 新增特性的情况下,适配了 Skyline 的微信小程序在低版本或未支持 Skyline 的平台上可无缝自动退回到 WebView 渲染。

支持与 WebView 混合使用

微信小程序支持页面使用 WebView 或 Skyline 任一模式进行渲染,Skyline 页面可以和 WebView 页面混跳,故开发者可以页面粒度或分包粒度按需适配。

// page.json
// skyline 渲染
{
    "renderer": "skyline"
}

// webview 渲染
{
    "renderer": "webview"
}
// app.json
{
  "subPackages": [
    {
      "root": "packageA",
      "pages": ["pages/cat"],
      "componentFramework": "glass-easel",
      "renderer": "skyline",
    },
  ],
}


提供更好的性能

Skyline 在渲染流程上较 WebView 更为精简,其对节点的渲染有着更精确的控制,尽量避免不可见区域的布局和绘制,以此来保证更高的渲染性能。WebView 由于其整体设计不同以及兼容性等问题,渲染流水线的实现更加冗长复杂。

在光栅化策略上,Skyline 采用的是同步光栅化的策略,WebView 是异步分块光栅化的策略。两种策略各有千秋,但 WebView 的策略存在一些难以规避的问题,例如:快速滚动会出现白屏问题;滚动过程中的 DOM 更新会出现不同步的问题,进而影响到用户体验。

在此基础上,我们还进一步实现了很多优化点。

1. 单线程版本组件框架

Skyline 下默认启用了新版本的组件框架 glass-easel,该版本适应了 Skyline 的单线程模型,使得建树流程的耗时有效降低(优化 30%-40%),同时 setData 调用也不再有通信开销和序列化开销。

2. 组件下沉

Skyline 内置组件的行为更接近原生体验,部分内置组件(如 scroll-view、swiper 等)借助于底层实现,有更好的性能和交互体验。同时,我们将部分内置组件(如 view、text、image 等)从 JS 下沉到原生实现,相当于原生 DOM 节点,降低了创建组件的开销(优化了 30% 左右)。

3.长列表按需渲染

长列表是一个常用的但又经常遇到性能瓶颈的场景,Skyline 对其做了一些优化,使 scroll-view 组件只渲染在屏节点(用法上有一定的约束),并且增加 lazy mount 机制优化首次渲染长列表的性能,后续我们也计划在组件框架层面进一步支持 scroll-view 的可回收机制,以更大程度降低创建节点的开销。

4. WXSS 预编译

同 WebView 传输 WXSS 文本不同,Skyline 在后台构建微信小程序代码包时会将 WXSS 预编译为二进制文件,在运行时直接读取二进制文件获得样式表结构,避免了运行时解析的开销(预编译较运行时解析快 5 倍以上)。

5. 样式计算更快

Skyline 通过精简 WXSS 特性大幅简化了样式计算的流程。在样式更新上,与 WebView 全量计算不同,Skyline 使用局部样式更新,可以避免对 DOM 树的多次遍历。Skyline 与微信小程序框架结合也更为紧密,例如: Skyline 结合组件系统实现了 WXSS 样式隔离、基于 wx:for 实现了节点样式共享(相比于 WebView 推测式样式共享更为精确、高效)。在节点变更、内联样式和继承样式的更新上,Skyline 也进行了一些优化,从而保证样式计算的性能。

此外,对于 rpx 单位,我们直接在样式计算阶段原生支持,这样避免了在 JS 层面做太多额外的计算。

<!-- 样式共享目前暂未自动识别,可手动声明 list-item 属性开启 -->
<scroll-view type="list" scroll-y>
    <view wx:for="{{list}}" list-item>{{index}}</view>
</scroll-view>

6. 降低内存占用

在 WebView 渲染模式下,一个微信小程序页面对应一个 WebView 实例,并且每个页面会重复注入一些公共资源。而 Skyline 只有 AppService 线程,且多个 Skyline 页面会运行在同一个渲染引擎实例下,因此页面占用内存能够降低很多,还能做到更细粒度的页面间资源共享(如全局样式、公共代码、缓存资源等)。


根除旧有架构的问题

在基于 Web 体系的架构下,微信小程序的部分基础体验会受限于 WebView 提供的能力(特别是 iOS WKWebView 限制更大一些),使得一些技术方案无法做得很完美,留下一些潜在的问题。

1. 原生组件同层渲染更稳定

iOS 下原生组件同层渲染的原理先前有介绍过,本质上是在 WKWebView 黑盒下一种取巧的实现方式,并不能完美融合到 WKWebView 的渲染流程,因此很容易在一些特殊的样式发生变化后,同层渲染会失效。在 Skyline 下可以很好地融合到渲染流程中,因此会更稳定。

2. 无需页面恢复机制

iOS 下 WKWebView 会受操作系统统一管理,当内存紧张时,操作系统就会将不在屏的 WKWebView 回收,会使得微信小程序除前台以外的页面丢失,虽然在页面返回时,我们对页面做了恢复,但页面的状态并不能 100% 还原。在 Skyline 下则不再有该问题。

3. 无页面栈层数限制

由于 WebView 的内存占用较大,页面层级最多有 10 层,而 Skyline 在内存方面更有优势,因此在连续 Skyline 页面跳转(复用同一引擎实例)的情况下,不再有该限制。


全新的交互动画体系

要达到类原生应用的体验,除渲染性能要好外,做好交互动画也很关键。在 Web 体系下,难以做到像素级可控,交互动画衔接不顺畅,究其原因,在于缺失了一些重要的能力。为此,Skyline 提供一套全新的交互动画能力。

1. Worklet 动画

Worklet 机制是 Skyline 交互动画体系的基础,它能够很方便地将 JavaScript 代码跑在渲染线程,那么基于 Worklet 机制的 动画模块,便能够在渲染线程同步运行动画相关逻辑,使动画不再会有延迟掉帧。

2. 手势系统

在原生应用的交互动画里,手势识别与协商是一个很重要的特性,而这块在 Web 体系下是缺失的,因此 Skyline 提供了基于 Worklet 机制的 手势系统。

  • 支持常用手势的识别,如缩放、拖动、双击等,并能够渲染线程同步监听手势、执行手势相关逻辑;
  • 支持手势协商处理,能够在遇到手势冲突(常见于滚动容器下)时决定让哪个手势生效,以实现更顺畅的动画衔接。

3. 自定义路由

页面间中转进行自定义的转场动画,在原生应用里也是一个很常见的交互动画。在原来的微信小程序架构下,每个页面都是独立的 WebView 渲染,互相隔离,其跨页能力是基本不具备的。因此,Skyline 提供了基于 Worklet 机制的 自定义路由模块,能实现市面上大多数页面转场动画效果。

4. 共享元素动画

支持 跨页面共享元素,能够很方便地将上一个页面的元素“共享”到下一个页面,并伴随着过渡动画,同时支持了一套可定制化接口,能实现自定义的过渡动画。

5. 内置组件扩展

对内置组件的扩展也是重要一环,特别是 scroll-view 组件,很多交互动画与滚动息息相关,Skyline 添加了很多在 Web 下很难做到又非常重要的特性。

  • 内置下拉刷新的实现,并完善相关事件。原来 WebView 的实现基于 transform,性能不够好且动画衔接不顺畅。
  • 提供“下拉二楼”交互的机制。
  • 提供 sticky 吸顶组件,能很方便地实现吸顶元素交错切换。
  • 使 scroll-view 组件在内容未溢出时也能滚动,让用户得到及时的交互反馈。
  • 为 scroll-view 组件提供更多控制能力,如最小触发滚动距离(min-drag-distance)、滚动结束事件(scrollend)、滚动原因(isDrag)等。
  • 提供原生的 swiper 实现,相比 WebView 基于 transform 的实现,性能更好。


更多的高级能力

除了交互动画的系列能力外,借助 Skyline 的优势,我们还提供了很多高级特性。

1. 提供 grid-view 瀑布流组件

瀑布流是一种常用的列表布局方式,得益于 Skyline 在布局过程中的可控性,我们直接在底层实现并提供出来,渲染性能要比 WebView 更优。

2. 提供 snapshot 截图组件

大多数微信小程序都会基于 canvas 实现自定义分享图的功能,一方面,需要通过 canvas 绘图指令手动实现,较为繁琐;另一方面,在分享图的布局较复杂时,或者在制作长图时会受限于系统对 canvas 尺寸限制,canvas 的方案实现成本都会很大。得益于 Skyline 在渲染过程中的可控性,Skyline 能直接对 WXML 子树进行截图,因此我们直接提供了截图组件,这样能复用更完善的 WXSS 能力,极大降低开发成本。

3. scroll-view 组件支持列表反转

在聊天对话的场景下,列表的滚动常常是反向的(往底部往上滚动),若使用正向滚动来模拟会有很多多余的逻辑,而且容易出现跳动,而 scroll-view 提供的 reverse 属性很好的解决这一问题。


还有更多计划提供出来的特性,请详见特性状态

Skyline 渲染引擎 / 概览 / 性能对比

性能对比

首屏耗时是衡量渲染性能一个最重要的指标。微信小程序的首屏耗时可以从上一个页面的点击到下一个页面 FCP(First Contentful Paint)的时间来衡量,首屏性能的好坏会影响上一个页面点击时的响应速度,以及下一个页面的白屏时间。

目前已经有一些微信小程序以 Skyline 模式在线上运行,以下取微信小程序助手的线上数据,可以看出 Skyline 的首屏时间比 WebView 快 66%,并且手机性能越低端,差异就越明显。

在内存占用方面,由于线上微信小程序未能较准确获取内存数据,我们在本地收集了一些测量数据,测量方法均是测 n 次取平均值,并且采用两个指标,一个是打开微信小程序示例首页,静置 30s 后采集数据,另一个是切 Tab 页面,同样静默后采集数据。

测量数据如下图所示(单位 M),可以看出,单个页面 Skyline 的占用比 WebView 减少 35%,两个页面 Skyline 的内存占用比 WebView 减少 50%,随着打开的页面变多,内存差距越明显。

与内存占用的测量类似,CPU 利用率的测量数据如下图所示(单位 %),也能看出,Skyline 相对 WebView 也有不错的提升。

效果对比

这里,我们分别录制了微信小程序助手在 Skyline 与 WebView 下的操作视频,从视频中的对比可以更直观地感受到区别。

视频录制的测试机为 OPPO R17,其中左边是 Skyline,右边是 WebView

快速体验

环境要求

目前,安卓微信 8.0.33、iOS 微信 8.0.34 起内置了 Skyline 渲染引擎,可先更新到该版本,预览时通过强切开关打开,方可体验 Skyline。

快速体验

以下微信小程序已适配 Skyline,可直接扫码打开体验。

扫码打开微信小程序助手,其中首页、切换微信小程序、版本查看、成员管理和成员申请均已适配。

扫码微信小程序示例,可进入 交互动画 tab 页体验 Skyline 的新特性。

演示案例

以下是用 Skyline 实现的各种常见交互动画的示例,可通过视频直接预览效果,也可直接扫码在移动端上体验。

通讯录
使用 scroll-view 自定义模式,配合 sticky 吸顶布局容器,实现通讯录字母交错吸顶的效果。
点击查看源代码
半屏
基于 worklet 动画,通过手势协商机制,实现在半屏内列表往下拉到顶之后,无缝切换到半屏下拉的效果。
点击查看源代码
分段半屏
通过 worklet 动画、手势协商,实现分段半屏,处于不同位置时联动半屏后的地图改变缩放比例。
点击查看源代码
相册
使用自定义路由、共享元素动画、手势系统等实现列表中图片共享放大过渡到图片预览页效果,并实现预览图片的手势交互。
点击查看源代码
Tab 指示条
利用 swiper 切换时的逐帧回调,配合 worklet 动画实现 tab 指示条顺滑切换的效果。
点击查看源代码
卡片转场
scroll-view 瀑布流模式配合共享元素动画实现卡片柔性转场效果。
点击查看源代码
搜索栏吸附
scroll-view 吸顶布局结合 worklet 布局轻松实现搜索栏吸附效果。
点击查看源代码
沉浸式商品浏览
微信小程序手势 + worklet 在页面中实现广告、商品无缝切换。
点击查看源代码