# 视图层

框架的视图层由 FXML 与 FTSS 编写,基础单元是组件。

  • FXML 用于描述页面的结构,类似于 HTML ;
  • FTSS 用于描述组件和页面的样式,是 css 的子集; fts 是小程序的一套脚本语言,基础语法同javascript,结合 FXML,可以创建出页面的结构。

组件 (Component) 是视图的基本组成单元,类似于HTML页面的各种标签,如div、span、img等

# 1. FXML

FXML 是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。

以下是一些简单的示例:​

# 1.1 数据绑定

<!--fxml-->
<view> hello {{name}} </view>
// page.js
Page({
  data: {
    name: 'cortana'
  }
})

# 1.2 列表渲染

<!--fxml-->
<view ft:for="{{array}}"> {{item}} </view>
// page.js
Page({
  data: {
    array: ["苹果", "香蕉", "橘子", "西瓜"]
  }
})

# 1.3 条件渲染

<!--fxml-->
<view ft:if="{{type == 1}}"> 类型 1 </view>
<view ft:elif="{{view == 2'}}"> 类型 2 </view>
<view ft:else="{{view == 3}}"> 类型 3  </view>
// page.js
Page({
  data: {
    type: 1
  }
})

# 1.4 模板

<!--fxml-->
<template name="cat">
  <view>
    age: {{name}}, age: {{age}}
  </view>
</template>

<template is="cat" data="{{...cat1}}"></template>
// page.js
Page({
  data: {
    cat1: {name: 'blue', age: '2'},
  }
})

# 1.5 事件

<view bindtap="getDate"> {{date}} </view> //点击事件 bindtap
Page({
  data: {
    date: ""
  },
  getDate: function(e) {
    this.setData({
      date: new Date()
    })
  }
})

# 2. FTSS

与 CSS FTSS 扩展的特性有:

  • 尺寸单位
  • 样式导入

# 2.1 尺寸单位

rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

设备 rpx换算px (屏幕宽度/750) px换算rpx (750/屏幕宽度)
iPhone5 1rpx = 0.42px 1px = 2.34rpx
iPhone6 1rpx = 0.5px 1px = 2rpx
iPhone6 Plus 1rpx = 0.552px 1px = 1.81rpx

注意

  • 开发“人民网+”小程序时设计师可以用 iPhone6 作为视觉稿的标准。
  • 在较小的屏幕上不可避免的会有一些毛刺,请在开发时尽量避免这种情况。

# 2.2 样式导入

使用@import语句可以导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用;表示语句结束。

示例代码

/** common.ftss **/
.small-p {
  padding:5px;
}
/** app.ftss **/
@import "common.ftss";
.middle-p {
  padding:15px;
}

# 2.3 内联样式

框架组件上支持使用 style、class 属性来控制组件的样式。

style:静态的样式统一写到 class 中。style 接收动态的样式,在运行时会进行解析,请尽量避免将静态的样式写进 style 中,以免影响渲染速度。

<view style="color:{{color}};" />

class:用于指定样式规则,其属性值是样式规则中类选择器名(样式类名)的集合,样式类名不需要带上.,样式类名之间用空格分隔。

<view class="normal_view" />

# 2.4 选择器

目前支持的选择器有:

选择器 样例 样例描述
.class .intro 选择所有拥有 class="intro" 的组件
#id #firstname 选择拥有 id="firstname" 的组件
element view 选择所有 view 组件
element, element view, checkbox 选择所有文档的 view 组件和所有的 checkbox 组件
::after view::after 在 view 组件后边插入内容
::before view::before 在 view 组件前边插入内容

# 2.5 全局样式与局部样式

定义在 app.ftss 中的样式为全局样式,作用于每一个页面。在 page 的 FTSS 文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖 app.ftss 中相同的选择器。

# 3. FTS

FTS(是小程序的一套脚本语言,结合 FXML,可以构建出页面的结构。​

注意

FTS 不依赖于SDK的基础库版本,可以在所有版本的小程序中运行。 FTS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致。 FTS 的运行环境和其他 JavaScript 代码是隔离的,FTS 中不能调用其他 JavaScript 文件中定义的函数,也不能调用小程序提供的API。 FTS 函数不能作为组件的事件回调。

以下是一些使用 FTS 的简单示例:

页面渲染

<!--fxml-->
<fts module="m1">
var msg = "hello world";
module.exports.message = msg;
</fts>

<view> {{m1.message}} </view>

页面输出

hello world

数据处理

// page.js
Page({
  data: {
    array: [1, 2, 3, 4, 5, 1, 2, 3, 4]
  }
})
<!--fxml-->
<!-- 下面的 getMax 函数,接受一个数组,且返回数组中最大的元素的值 -->
<fts module="m1">
var getMax = function(array) {
  var max = undefined;
  for (var i = 0; i < array.length; ++i) {
    max = max === undefined ?
      array[i] :
      (max >= array[i] ? max : array[i]);
  }
  return max;
}

module.exports.getMax = getMax;
</fts>

<!-- 调用 fts 里面的 getMax 函数,参数为 page.js 里面的 array -->
<view> {{m1.getMax(array)}} </view>

页面输出

5

# 4. FTS 响应事件

# 4.1 背景

当小程序需要实现频繁用户交互的效果时,如果采用常规实现方法,如:

页面有 2 个元素 A 和 B,用户在 A 上做 touchmove 手势,要求 B 也跟随移动,movable-view 就是一个典型的例子。一次 touchmove 事件的响应过程为:

i、touchmove 事件从视图层(View)抛到逻辑层(Service)

ii、逻辑层(Service)处理 touchmove 事件,再通过 setData 来改变 B 的位置

一次 touchmove 的响应需要经过 2 次的逻辑层和渲染层的通信以及一次渲染,通信的耗时比较大。

此外 setData 渲染也会阻塞其它脚本执行,导致了整个用户交互的动画过程会有延迟,交互的实际表现不会很理想。

# 4.2 解决方案

基于以上原因,可以使用 FTS 函数用来响应小程序事件,然后在视图层(View)处理 dom 样式,实现比较好的效果。

目前只能响应内置组件的事件,不支持自定义组件事件。

FTS 函数的除了纯逻辑的运算,还可以通过封装好的 ComponentDescriptor 实例来访问以及设置组件的 class 和样式,对于交互动画,设置 style 和 class 能满足绝大多数需求了。

FTS 函数的例子如下:

var event = function(event, ownerInstance) {
    // 获取组件实例
    var instance = ownerInstance.selectComponent('.some-component')
    instance.setStyle({
      color: 'red',
      "font-size": '18rpx'
    })
    instance.setClass('other-class')
    return false // 不往上冒泡
}

其中入参 event 是小程序事件对象基础上多了 event.instance 来表示触发事件的组件的 ComponentDescriptor 实例。

ownerInstance 表示的是触发事件的组件所在的组件的 ComponentDescriptor 实例,如果触发事件的组件是在页面内的,则 ownerInstance 表示的是页面实例。

ComponentDescriptor 目前支持的 API 如下:

方法 参数 描述
selectComponent selector 对象 返回组件的 ComponentDescriptor 实例。
selectAllComponents selector 对象数组 返回组件的 ComponentDescriptor 实例数组。
setStyle Object/string 设置组件样式,支持rpx。设置的样式优先级比组件 fxml 里面定义的样式高。不能设置最顶层页面的样式。
addClass/removeClass/hasClass string 设置组件的 class。设置的 class 优先级比组件 fxml 里面定义的 class 高。不能设置最顶层页面的 class。
callMethod (funcName:string, args:object) 调用当前组件/页面在逻辑层(App Service)定义的函数。funcName表示函数名称,args表示函数的参数。
getComputedStyle Array <string> 指定样式名列表,返回节点对应样式名的当前值。
getBoundingClientRect 返回节点的尺寸信息。

# 4.3 使用方法

FXML 定义事件:

<fts module="event" src="./event.fts"></fts>
<view bindtouchmove="{{event.touchmove}}" class="movable"></view>

注意:FTS 函数必须用 {{}} 括起来。

文件 event.ftx 里面定义并导出函数:

module.exports = {
    touchmove: function(event, instance) {
        console.log('log event')
    },
    otherEvent: function(event, instance) {
        console.log('log event')
    }
}

# 5. 简易双向绑定

在 FXML 中,普通的属性的绑定是单向的。例如:

<input value="{{value}}" />

如果使用 this.setData({ value: 'leaf' }) 来更新 value ,this.data.value 和输入框的中显示的值都会被更新为 leaf ;但如果用户修改了输入框里的值,却不会同时改变 this.data.value 。

如果需要在用户输入的同时改变 this.data.value ,需要借助简易双向绑定机制。此时,可以在对应项目之前加入 model: 前缀:

<input model:value="{{value}}" />

这样,如果输入框的值被改变了, this.data.value 也会同时改变。同时, FXML 中所有绑定了 value 的位置也会被一同更新, 数据监听器 也会被正常触发。

在开发者工具中预览效果

用于双向绑定的表达式有如下限制:

<input model:value="值为 {{value}}" />
<input model:value="{{ a + b }}" />

都是非法的;

<input model:value="{{ a.b }}" />

这样的表达式目前暂不支持。

# 5.1 在自定义组件中传递双向绑定

双向绑定同样可以使用在自定义组件上。如下的自定义组件:

// custom-component.js
Component({
  properties: {
    myValue: String
  }
})

<!-- custom-component.FXML -->
<input model:value="{{myValue}}" />

这个自定义组件将自身的 myValue 属性双向绑定到了组件内输入框的 value 属性上。这样,如果页面这样使用这个组件:

<custom-component model:my-value="{{pageValue}}" />

当输入框的值变更时,自定义组件的 myValue 属性会同时变更,这样,页面的 this.data.pageValue 也会同时变更,页面 FXML 中所有绑定了 pageValue 的位置也会被一同更新。

# 5.2 在自定义组件中触发双向绑定更新

自定义组件还可以自己触发双向绑定更新,做法就是:使用 setData 设置自身的属性。例如:

// custom-component.js
Component({
  properties: {
    myValue: String
  },
  methods: {
    update: function() {
      // 更新 myValue
      this.setData({
        myValue: 'leaf'
      })
    }
  }
})

如果页面这样使用这个组件:

<custom-component model:my-value="{{pageValue}}" />

当组件使用 setData 更新 myValue 时,页面的 this.data.pageValue 也会同时变更,页面 FXML 中所有绑定了 pageValue 的位置也会被一同更新。

# 6. 基础组件

框架为开发者提供了一系列基础组件,开发者可以通过组合这些基础组件进行快速开发。详细介绍请参考组件文档。​

# 6.1 什么是组件

  • 组件是视图层的基本组成单元。
  • 一个组件通常包括 开始标签 和 结束标签,属性 用来修饰这个组件,内容 在两个标签之内。
<tagname property="value">
Content goes here ...
</tagname>

注意

所有组件与属性都是小写,以连字符-连接

属性类型

类型 描述 注解
Boolean 布尔值 组件写上该属性,不管是什么值都被当作 true;只有组件上没有该属性时,属性值才为false。如果属性值为变量,变量的值会被转换为Boolean类型
Number 数字 1, 2.5
String 字符串 "string"
Array 数组 [ 1, "string" ]
Object 对象 { key: value }
EventHandler 事件处理函数名 "handlerName" 是 Page 中定义的事件处理函数名
Any 任意属性

公共属性

所有组件都有以下属性

属性名 类型 描述 注解
id String 组件的唯一标示 保持整个页面唯一
class String 组件的样式类 在对应的 FTSS 中定义的样式类
style String 组件的内联样式 可以动态设置的内联样式
hidden Boolean 组件是否显示 所有组件默认显示
data-* Any 自定义属性 组件上触发的事件时,会发送给事件处理函数
bind* / catch* EventHandler 组件的事件 详见事件

特殊属性

几乎所有组件都有各自定义的属性,可以对该组件的功能或样式进行修饰,请参考各个组件的定义。

# 7. 获取界面上的节点信息

# 7.1 FXML节点信息

节点信息查询 API 可以用于获取节点属性、样式、在界面上的位置等信息。

最常见的用法是使用这个接口来查询某个节点的当前位置,以及界面的滚动位置。​

示例代码

const query = ft.createSelectorQuery()
query.select('#the-id').boundingClientRect(function(res){
  res.top // #the-id 节点的上边界坐标(相对于显示区域)
})
query.selectViewport().scrollOffset(function(res){
  res.scrollTop // 显示区域的竖直滚动位置
})
query.exec()

上述示例中, #the-id 是一个节点选择器,与 CSS 的选择器相近但略有区别,请参见 SelectorQuery.select 的相关说明。

在自定义组件或包含自定义组件的页面中,推荐使用 this.createSelectorQuery 来代替 ft.createSelectorQuery ,这样可以确保在正确的范围内选择节点。

# 7.2 FXML节点布局相交状态

节点布局相交状态 API 可用于监听两个或多个组件节点在布局位置上的相交状态。这一组API常常可以用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见。

这一组API涉及的主要概念如下。

  • 参照节点:监听的参照节点,取它的布局区域作为参照区域。如果有多个参照节点,则会取它们布局区域的 交集 作为参照区域。页面显示区域也可作为参照区域之一。
  • 目标节点:监听的目标,默认只能是一个节点(使用 selectAll 选项时,可以同时监听多个节点)。
  • 相交区域:目标节点的布局区域与参照区域的相交区域。
  • 相交比例:相交区域占参照区域的比例。
  • 阈值:相交比例如果达到阈值,则会触发监听器的回调函数。阈值可以有多个。 以下示例代码可以在目标节点(用选择器 .target-class 指定)每次进入或离开页面显示区域时,触发回调函数。

示例代码

Page({
  onLoad: function(){
    ft.createIntersectionObserver().relativeToViewport().observe('.target-class', (res) => {
      res.id // 目标节点 id
      res.dataset // 目标节点 dataset
      res.intersectionRatio // 相交区域占目标节点的布局区域的比例
      res.intersectionRect // 相交区域
      res.intersectionRect.left // 相交区域的左边界坐标
      res.intersectionRect.top // 相交区域的上边界坐标
      res.intersectionRect.width // 相交区域的宽度
      res.intersectionRect.height // 相交区域的高度
    })
  }
})

以下示例代码可以在目标节点(用选择器 .target-class 指定)与参照节点(用选择器 .relative-class 指定)在页面显示区域内相交或相离,且相交或相离程度达到目标节点布局区域的20%和50%时,触发回调函数。​

示例代码

Page({
  onLoad: function(){
    ft.createIntersectionObserver(this, {
      thresholds: [0.2, 0.5]
    }).relativeTo('.relative-class').relativeToViewport().observe('.target-class', (res) => {
      res.intersectionRatio // 相交区域占目标节点的布局区域的比例
      res.intersectionRect // 相交区域
      res.intersectionRect.left // 相交区域的左边界坐标
      res.intersectionRect.top // 相交区域的上边界坐标
      res.intersectionRect.width // 相交区域的宽度
      res.intersectionRect.height // 相交区域的高度
    })
  }
})

注意

与页面显示区域的相交区域并不准确代表用户可见的区域,因为参与计算的区域是“布局区域”,布局区域可能会在绘制时被其他节点裁剪隐藏(如遇祖先节点中 overflow 样式为 hidden 的节点)或遮盖(如遇 fixed 定位的节点)。

在自定义组件或包含自定义组件的页面中,推荐使用 this.createIntersectionObserver 来代替 ft.createIntersectionObserver ,这样可以确保在正确的范围内选择节点。

# 8. 响应显示区域变化

# 8.1 显示区域尺寸

显示区域指小程序界面中可以自由布局展示的区域。在默认情况下,小程序显示区域的尺寸自页面初始化起就不会发生变化。但以下两种方式都可以改变这一默认行为。

# 8.1.1 在手机上启用屏幕旋转支持

从小程序基础库版本 1.5.33 开始,小程序在手机上支持屏幕旋转。使小程序中的页面支持屏幕旋转的方法是:在 app.json 的 window 段中设置 "pageOrientation": "auto" ,或在页面 json 文件中配置 "pageOrientation": "auto" 。

以下是在单个页面 json 文件中启用屏幕旋转的示例。​

代码示例

{
  "pageOrientation": "auto"
}

如果页面添加了上述声明,则在屏幕旋转时,这个页面将随之旋转,显示区域尺寸也会随着屏幕旋转而变化。

从小程序基础库版本 1.5.33 开始, pageOrientation 还可以被设置为 landscape ,表示固定为横屏显示。

# 8.1.2 在 iPad 上启用屏幕旋转支持

从小程序基础库版本 1.5.33 开始,在 iPad 上运行的小程序可以支持屏幕旋转。使小程序支持 iPad 屏幕旋转的方法是:在 app.json 中添加 "resizable": true 。​

代码示例

{
  "resizable": true
}

如果小程序添加了上述声明,则在屏幕旋转时,小程序将随之旋转,显示区域尺寸也会随着屏幕旋转而变化。

注意

在 iPad 上不能单独配置某个页面是否支持屏幕旋转。

# 8.2 Media Query

有时,对于不同尺寸的显示区域,页面的布局会有所差异。此时可以使用 media query 来解决大多数问题。

代码示例

.my-class {
  width: 40px;
}

@media (min-width: 480px) {
  /* 仅在 480px 或更宽的屏幕上生效的样式规则 */
  .my-class {
    width: 200px;
  }
}

# 8.3 屏幕旋转事件

有时,仅仅使用 media query 无法控制一些精细的布局变化。此时可以使用 js 作为辅助。

在 js 中读取页面的显示区域尺寸,可以使用 selectorQuery.selectViewport 。

页面尺寸发生改变的事件,可以使用页面的 onResize 来监听。对于自定义组件,可以使用 resize 生命周期来监听。回调函数中将返回显示区域的尺寸信息。(从基础库版本 1.5.33 开始支持。)​

代码示例

Page({
  onResize(res) {
    res.size.windowWidth // 新的显示区域宽度
    res.size.windowHeight // 新的显示区域高度
  }
})

# 9. 页面路由

在小程序中所有页面的路由全部由小程序框架进行管理。

# 9.1 页面栈

框架以栈的形式维护了当前所有页面。 当发生路由切换的时候,页面栈的表现如下:

路由方式 页面栈表现
初始化 新页面入栈
打开新页面 新页面入栈
页面重定向 当前页面出栈,新页面入栈
页面返回 页面不断出栈,直到目标返回页
Tab 切换 页面全部出栈,只留下新的 Tab 页面
重加载 页面全部出栈,只留下新的页面

getCurrentPages()

getCurrentPages() 函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。​

注意

  1. 不要尝试手动修改页面栈,会导致路由以及页面状态错误。
  2. 不要在 App.onLaunch 的时候调用 getCurrentPages(),此时 page 还没有生成。

路由方式

对于路由的触发方式以及页面生命周期函数如下:

路由方式 触发时机 路由前页面 路由后页面
初始化 小程序打开的第一个页面 onLoad, onShow
打开新页面 调用 API ft.navigateTo 或使用组件 <navigator open-type="navigateTo"/> onHide onLoad, onShow
页面重定向 调用 API ft.redirectTo 或使用组件 <navigator open-type="redirectTo"/> onUnload onLoad, onShow
页面返回 调用 API ft.navigateBack 或使用组件 <navigator open-type="navigateBack">或用户按左上角返回按钮 onUnload onShow
Tab 切换 调用 API ft.switchTab 或使用组件<navigator open-type="switchTab"/>或用户切换 Tab 各种情况请参考下表
重启动 调用 API ft.reLaunch 或使用组件 <navigator open-type="reLaunch"/> onUnload onLoad, onShow

Tab 切换对应的生命周期(以 A、B 页面为 Tabbar 页面,C 是从 A 页面打开的页面,D 页面是从 C 页面打开的页面为例):

当前页面 路由后页面 触发的生命周期(按顺序)
A A Nothing happend
A B A.onHide(), B.onLoad(), B.onShow()
A B(再次打开) A.onHide(), B.onShow()
C A C.onUnload(), A.onShow()
C B C.onUnload(), B.onLoad(), B.onShow()
D B D.onUnload(), C.onUnload(), B.onLoad(), B.onShow()
D(从转发进入) A D.onUnload(), A.onLoad(), A.onShow()
D(从转发进入) B D.onUnload(), B.onLoad(), B.onShow()

提示

  • navigateTo, redirectTo 只能打开非 tabBar 页面。
  • switchTab 只能打开 tabBar 页面。
  • reLaunch 可以打开任意页面。
  • 页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。
  • 调用页面路由带的参数可以在目标页面的 onLoad 中获取。

# 10. 动画

# 10.1 界面动画的常见方式

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

在开发者工具中预览效果

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

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

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

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

# 10.2 关键帧动画

还有一种更友好的动画创建方式,用于代替旧的 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
bottom Number/String 底边位置,即 CSS bottom
height Number/String 高度,即 CSS height
left Number/String 左边位置,即 CSS left
width Number/String 宽度,即 CSS width
opacity Number 不透明度,即 CSS opacity
right Number 右边位置,即 CSS right
top Number/String 顶边位置,即 CSS top
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 清除完成后的回调函数

# 10.3 高级的动画方式

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

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

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

# 11. 初始渲染缓存

# 11.1 初始渲染缓存工作原理

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

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

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

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

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

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

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

# 11.2 支持的组件

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

目前支持的内置组件:

  • <view />
  • <text />
  • <button />
  • <image />
  • <scroll-view />
  • <rich-text /> 自定义组件本身可以被展示(但它们里面用到的内置组件也遵循上述限制)。

# 11.3 静态初始渲染缓存

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

{
  "initialRenderingCache": "static"
}

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

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

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

请注意

这种情况下,初始渲染缓存记录的是页面data应用在页面FXML上的结果,不包含任何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的渲染结果,即页面的纯静态成分。

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

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

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

{
  "initialRenderingCache": "dynamic"
}

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

Page({
  data: {
    loading: true
  },
  onReady: function() {
    this.setInitialRenderingCache({
      loadingHint: '正在加载' // 这一部分数据将被应用于界面上,相当于在初始 data 基础上额外进行一次 setData
    })
  }
})

<view wx:if="{{loading}}">{{loadingHint}}</view>

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

注意:

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