渲染層
框架的渲染層由WXML與WXSS編寫,由組件來進行展示將邏輯層的數據反映成視圖,同時將渲染層的事件發送給邏輯層。
- WXML(WeiXin Markup language)用於描述頁面的結構。
- WXS(WeiXin Script)是小程序的一套指令碼語言,結合WXML,可以構建出頁面的結構。
- WXSS(WeiXin Style Sheet)用於描述頁面的樣式。
1.WXML
WXML(WeiXin Markup Language)是框架設計的一套標籤語言,結合 基礎組件 事件系統可以構建出頁面的結構。
WXML(WeiXin Markup Language)是框架設計的一套標籤語言,結合 WXML 語法參攷
用以下一些簡單的例子來看看WXML具有什麼能力:
1.1 數據綁定
<!-- wxml -->
<view> {{message}} </view>// page.js
Page({
data: {
message: 'Hello MINA!'
}
})1.2 清單渲染
<!--wxml-->
<view wx:for="{{array}}"> {{item}} </view>1.3 條件渲染
<!--wxml-->
<view wx:if="{{view == 'WEBVIEW'}}"> WEBVIEW </view>
<view wx:elif="{{view == 'APP'}}"> APP </view>
<view wx:else="{{view == 'MINA'}}"> MINA </view>// page.js
Page({
data: {
view: 'MINA'
}
})1.4 模版
<!--wxml-->
<template name="staffName">
<view>
FirstName: {{firstName}}, LastName: {{lastName}}
</view>
</template>
<template is="staffName" data="{{...staffA}}"></template>
<template is="staffName" data="{{...staffB}}"></template>
<template is="staffName" data="{{...staffC}}"></template>// page.js
Page({
data: {
staffA: {firstName: 'Hulk', lastName: 'Hu'},
staffB: {firstName: 'Shang', lastName: 'You'},
staffC: {firstName: 'Gideon', lastName: 'Lin'}
}
})2. WXSS
WXSS(WeiXin Style Sheets)是一套樣式語言,用於描述WXML的組件樣式。
WXSS用來决定WXML的組件應該怎麼顯示。
為了適應廣大的前端開發者,WXSS具有CSS大部分特性同時為了更適合開發Luffa小程序,WXSS對CSS進行了擴充以及修改。
與CSS相比,WXSS擴展的特性有:
- 尺寸單位
- 樣式導入
TIP
- iOS 8及以下的iOS版本,如使用flexbox佈局,需添加display: - webkit-flex内容。
2.1 尺寸單位
rpx(responsive pixel): 可以根據荧幕寬度進行自我調整規定荧幕寬為750rpx如在iPhone6上,荧幕寬度為375px,共有750個物理點數,則750rpx = 375px = 750物理點數,1rpx = 0.5px = 1物理點數。
根據不同的設備荧幕寬度,rpx和px的換算關係也會有所不同例如:
TIP
- 建議: 開發小程序時設計師可以用iPhone6作為視覺稿的標準。
- 注意: 在較小的荧幕上不可避免的會有一些毛刺,請在開發時儘量避免這種情況。
2.2 樣式導入
使用import語句可以導入外聯樣式表, import後跟需要導入的外聯樣式表的相對路徑,用; 表示語句結束。
示例代碼:
/** common.wxss **/
.small-p {
padding:5px;
}/** app.wxss **/
@import "common.wxss";
.middle-p {
padding:15px;
}2.3 內聯樣式
框架組件上支持使用style、class内容來控制組件的樣式。
- style: 靜態的樣式統一寫到class中style接收動態的樣式,在運行時會進行解析,請儘量避免將靜態的樣式寫進style中,以免影響渲染速度。
/** common.wxss **/
.small-p {
padding:5px;
}- class: 用於指定樣式規則,其屬性值是樣式規則中類選擇器名(樣式類名)的集合,樣式類名不需要帶上,樣式類名之間用空格分隔。
<view class="normal_view" />2.4 選擇器。
現時支持的選擇器有:
2.5 全域樣式與局部樣式
定義在app.wxss中的樣式為全域樣式,作用於每一個頁面在page的wxss文件中定義的樣式為局部樣式,只作用在對應的頁面,並會覆蓋app.wxss中相同的選擇器。
3. WXS
WXS(WeiXin Script)是內聯在WXML中的腳本段通過WXS可以在模版中內聯少量處理腳本,豐富模版的數據預處理能力另外,WXS還可以用來編寫簡單的WXS事件響應函數
從語法上看,WXS類似於有少量限制的JavaScript要完整瞭解WXS語法,請參攷WXS -介紹
以下是一些使用WXS的簡單示例。
頁面渲染:
<!--wxml-->
<wxs module="m1">
var msg = "hello world";
module.exports.message = msg;
</wxs>
<view> {{m1.message}} </view>頁面輸出:
hello world資料處理:
// page.js
Page({
data: {
array: [1, 2, 3, 4, 5, 1, 2, 3, 4]
}
})<!--wxml-->
<!-- The following getMax function takes an array and returns the value of the largest element in the array -->
<wxs 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;
</wxs>
<!-- Calls the getMax function in WXS with the "array" in page.js as the parameter -->
<view> {{m1.getMax(array)}} </view>頁面輸出:
54. 事件系統
4.1 什麼是事件
- 事件是渲染層到邏輯層的通訊管道。
- 事件可以將用戶的行為迴響到邏輯層進行處理。
- 事件可以綁定在組件上,當達到觸發事件,就會執行邏輯層中對應的事件處理函數。
- 事件對象可以攜帶額外資訊,如id, dataset, touches。
4.1 事件的使用管道
在組件中綁定一個事件處理函數
如bindtap,當用戶點擊該組件的時候會在該頁面對應的Page中找到相應的事件處理函數。
<view id="tapTest" data-hi="Weixin" bindtap="tapName"> Click me! </view>在相應的Page定義中寫上相應的事件處理函數,參數是event。
Page({
tapName: function(event) {
console.log(event)
}
})可以看到log出來的資訊大致如下:
{
"type":"tap",
"timeStamp":895,
"target": {
"id": "tapTest",
"dataset": {
"hi":"Weixin"
}
},
"currentTarget": {
"id": "tapTest",
"dataset": {
"hi":"Weixin"
}
},
"detail": {
"x":53,
"y":14
},
"touches":[{
"identifier":0,
"pageX":53,
"pageY":14,
"clientX":53,
"clientY":14
}],
"changedTouches":[{
"identifier":0,
"pageX":53,
"pageY":14,
"clientX":53,
"clientY":14
}]
}4.3 使用WXS函數響應事件
4.3.1 背景
有頻繁用戶互動的效果在小程序上表現是比較卡頓的,例如頁面有2個元素A和B,用戶在A上做touchmove手勢,要求B也跟隨移動,movable-view就是一個典型的例子一次touchmove事件的響應過程為:
touchmove事件從渲染層(Webview)拋到邏輯層(App Service);
邏輯層(App Service)處理touchmove事件,再通過setData來改變B的位置。
4.3.2 解決方案
本方案基本的思路是减少通信的次數,讓事件在渲染層(Webview)響應小程序的框架分為渲染層(Webview)和邏輯層(App Service),這樣分層的目的是管控,開發者的代碼只能運行在邏輯層(App Service),而這個思路就必須要讓開發者的代碼運行在渲染層(Webview)。
使用WXS函數用來響應小程序事件,現時只能響應內寘組件的事件,不支持自定義組件事件。
WXS函數的除了純邏輯的運算,還可以通過封裝好的ComponentDescriptor實例來訪問以及設定組件的class和樣式,對於互動動畫,設定style和class足够了。
WXS函數接受2個參數,第一個是event,在原有的event的基礎上加了event.instance對象,第二個參數是ownerInstance和event.instance一樣是一個ComponentDescriptor對象具體使用如下:
- 在組件中綁定和注册事件處理的WXS函數:
<wxs module="wxs" src="./test.wxs"></wxs>
<view id="tapTest" data-hi="Weixin" bindtap="{{wxs.tapName}}"> Click me! </view>
**Note: Bound WXS functions must be enclosed in {{}}**- test.wxs文件實現tapName函數:
function tapName(event, ownerInstance) {
console.log('tap Weixin', JSON.stringify(event))
}
module.exports = {
tapName: tapName
}ownerInstance包含了一些方法,可以設定組件的樣式和class。
WXS函數的例子如下:
var wxsFunction = function(event, ownerInstance) {
var instance = ownerInstance.selectComponent('.classSelector') // Returns an instance of the component
instance.setStyle({
"font-size": "14px" // Support for rpx
})
instance.getDataset()
instance.setClass(className)
// ...
return false // does not bubble up, which is equivalent to calling both stopPropagation and preventDefault
}其中入參event是小程序事件對象基礎上多了event.instance來表示觸發事件的組件的ComponentDescriptor實例。
ownerInstance表示的是觸發事件的組件所在的組件的ComponentDescriptor實例,如果觸發事件的組件是在頁面內的,則ownerInstance表示的是頁面實例。
ComponentDescriptor 現時支持的API如下:
4.3.3 使用方法
WXML定義事件:
<wxs module="test" src="./test.wxs"></wxs>
<view
change:prop="{{test.propObserver}}"
prop="{{propValue}}"
bindtouchmove="{{test.touchmove}}"
class="movable">
</view>上面的 change:prop(屬性前面帶 change: 前綴)是在 prop 屬性被設定的時候觸發 WXS 函數,值必須用{{}} 括起來。類似 Component 定義的 properties 裡面的 observer 屬性,在 setData({propValue: newValue}) 呼叫之後會觸發。
TIP
WXS 函數必須用 {{}} 括起來。當 prop 的值被設定 WXS 函數就會觸發,而不只是值改變,所以在頁面初始化的時候會呼叫一次 WxsPropObserver 的函式。
WXS文件test.wxs裡面定義並匯出事件處理函數和内容改變觸發的函數:
module.exports = {
touchmove: function(event, instance) {
console.log('log event', JSON.stringify(event))
},
propObserver: function(newValue, oldValue, ownerInstance, instance) {
console.log('prop observer', newValue, oldValue)
}
}4.4 事件詳解
4.4.1 事件分類
事件分為冒泡事件和非冒泡事件:
- 冒泡事件:當一個組件上的事件被觸發後,該事件會向父節點傳遞;
- 非冒泡事件:當一個組件上的事件被觸發後,該事件不會向父節點傳遞。
說明
除上錶之外的其他組件自定義事件如無特殊聲明都是非冒泡事件,如form的submit事件,input的input事件,scroll-view的scroll事件,詳見 組件概覽
4.4.2 普通事件綁定
事件綁定的寫法類似於組件的内容,如:
<view bindtap="handleTap">
Click here!
</view>如果用戶點擊這個view,則頁面的handleTap會被調用。
事件綁定函數可以是一個數據綁定,如:
<view bindtap="{{ handlerName }}">
Click here!
</view>此時,頁面的this.data.handlerName必須是一個字串,指定事件處理函數名; 如果它是個空字串,則這個綁定會失效(可以利用這個特性來暫時禁用一些事件)。
在大多數組件和自定義組件中, bind後可以緊跟一個冒號,其含義不變,如bind: tap。
4.4.3 綁定並封锁事件冒泡
除bind外,也可以用catch來綁定事件與bind不同,catch會封锁事件向上冒泡。
例如在下邊這個例子中,按一下inner view會先後調用handleTap3和handleTap2(因為tap事件會冒泡到middle view,而middle view封锁了tap事件冒泡,不再向父節點傳遞),按一下middle view會觸發handleTap2,按一下outer view會觸發handleTap1。
<view id="outer" bindtap="handleTap1">
outer view
<view id="middle" catchtap="handleTap2">
middle view
<view id="inner" bindtap="handleTap3">
inner view
</view>
</view>
</view>4.4.4 互斥事件綁定
自基礎庫版本1.5.44起,除bind和catch外,還可以使用mut-bind來綁定事件一個mut-bind觸發後,如果事件冒泡到其他節點上,其他節點上的mut-bind綁定函數不會被觸發,但bind綁定函數和catch綁定函數依舊會被觸發。
換而言之,所有mut-bind是'互斥'的,只會有其中一個綁定函數被觸發同時,它完全不影響bind和catch的綁定效果。
例如在下邊這個例子中,按一下inner view會先後調用handleTap3和handleTap2,按一下middle view會調用handleTap2和handleTap1。
<view id="outer" mut-bind:tap="handleTap1">
outer view
<view id="middle" bindtap="handleTap2">
middle view
<view id="inner" mut-bind:tap="handleTap3">
inner view
</view>
</view>
</view>4.4.5 事件的捕獲階段
觸摸類事件支持捕獲階段捕獲階段位於冒泡階段之前,且在捕獲階段中,事件到達節點的順序與冒泡階段恰好相反需要在捕獲階段監聽事件時,可以採用capture-bind、capture-catch關鍵字,後者將中斷捕獲階段和取消冒泡階段。
在下麵的代碼中,按一下inner view會先後調用handleTap2、 handleTap4、handleTap3、handleTap1。
<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>如果將上面代碼中的第一個capture-bind改為capture-catch,將只觸發handleTap2。
<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>4.4.6 事件對象
如無特殊說明,當組件觸發事件時,邏輯層綁定該事件的處理函數會收到一個事件對象。
BaseEvent基礎事件對象内容清單:
CustomEvent自定義事件對象内容清單(繼承BaseEvent):
TouchEvent觸摸事件對象内容清單(繼承BaseEvent):
特殊事件:canvas中的觸摸事件不可冒泡,所以沒有currentTarget。
4.4.7 type
代表事件的類型。
4.4.8 timeStamp
頁面打開到觸發事件所經過的毫秒數。
4.4.9 target
觸發事件的源組件。
4.4.10 currentTarget
事件綁定的當前組件。
TIP
target和currentTarget可以參考上例中,按一下inner view時,handleTap3收到的事件對象target和currentTarget都是inner,而handleTap2收到的事件對象target就是inner,currentTarget就是middle。
4.4.11 dataset
在組件節點中可以附加一些自定義數據這樣,在事件中可以獲取這些自定義的節點數據,用於事件的邏輯處理。
在WXML中,這些自定義數據以data-開頭,多個單詞由連字號-連接這種寫法中,連字號寫法會轉換成駝峰寫法,而大寫字元會自動轉成小寫字元如:
- data-element-type , 最終會呈現為event.currentTarget.dataset.elementType;
- data-elementType , 最終會呈現為event.currentTarget.dataset.elementtype。
<view data-alpha-beta="1" data-alphaBeta="2" bindtap="bindViewTap"> DataSet Test </view>Page({
bindViewTap:function(event){
event.currentTarget.dataset.alphaBeta === 1 // - will convert to camel case
event.currentTarget.dataset.alphaBeta === 2 // uppercase will be converted to lowercase
}
})4.4.12 touches
touches是一個數組,每個元素為一個Touch對象(canvas觸摸事件中攜帶的touches是CanvasTouch數組)表示當前停留在荧幕上的觸摸點。
Touch對象
CanvasTouch對象
4.4.13 changedTouches
changedTouches數據格式同touches表示有變化的觸摸點,如從無變有(touchstart),位置變化(touchmove),從有變無(touchend、touchcancel)。
4.4.14 detail
自定義事件所攜帶的數據,如表單組件的提交事件會攜帶用戶的輸入,媒體的錯誤事件會攜帶錯誤資訊,詳見 組件概覽 定義中各個事件的定義。
按一下事件的detail帶有的x, y同pageX, pageY代表距離文件左上角的距離。
5. 基礎組件
框架為開發者提供了一系列基礎組件,開發者可以通過組合這些基礎組件進行快速開發詳細介紹請參攷 組件概覽 定義中各個事件的定義。
什麼是組件:
- 組件是視圖層的基本組成單元。
- 一個組件通常包括開始標籤和結束標籤,内容用來修飾這個組件,內容在兩個標籤之內。
<tagname property="value">
Content goes here ...
</tagname>TIP
所有組件與内容都是小寫,以連字號-連接
内容類型:
公共内容
所有組件都有以下内容:
特殊内容
幾乎所有組件都有各自定義的内容,可以對該組件的功能或樣式進行修飾,請參攷各個 組件概覽 的定義。
6. 獲取介面上的節點資訊
6.1 WXML節點資訊
節點資訊查詢API 可以用於獲取節點内容、樣式、在介面上的位置等資訊。
最常見的用法是使用這個接口來査詢某個節點的當前位置,以及介面的滾動位置。
示例代碼:
const query = wx.createSelectorQuery()
query.select('#the-id').boundingClientRect()
query.selectViewport().scrollOffset()
query.exec(function (res) {
res[0].top // The upper boundary coordinate of the #the-id node.
res[1].scrollTop // The vertical scroll position of the display area.
})上述示例中,# the-id是一個節點選擇器,與CSS的選擇器相近但略有區別,請參見 SelectorQuery.select 章節中的相關說明。
在自定義組件或包含自定義組件的頁面中,推薦使用this.createSelectorQuery來代替 wx.createSelectorQuery ,這樣可以確保在正確的範圍內選擇節點。
6.2 WXML節點佈局相交狀態
節點佈局相交狀態API 可用於監聽兩個或多個組件節點在佈局位置上的相交狀態這一組API常常可以用於推斷某些節點是否可以被用戶看見、有多大比例可以被用戶看見。
這一組API涉及的主要概念如下。
- 參照節點:監聽的參照節點,取它的佈局區域作為參照區域如果有多個參照節點,則會取它們佈局區域的交集作為參照區域頁面顯示區域也可作為參照區域之一;
- 目標節點:監聽的目標,默認只能是一個節點(使用selectAll選項時,可以同時監聽多個節點);
- 相交區域:目標節點的佈局區域與參照區域的相交區域;
- 相交比例:相交區域占參照區域的比例;
- 閾值:相交比例如果達到閾值,則會觸發監聽器的回呼函數閾值可以有多個;
示例代碼:
Page({
onLoad: function(){
wx.createIntersectionObserver().relativeToViewport().observe('.target-class', (res) => {
res.id // target node id
res.dataset // target node dataset
res.intersectionRatio // the proportion of the intersection area to the layout area of the target node
res.intersectionRect // The intersection region
res.intersectionRect.left // the coordinates of the left boundary of the intersection region
res.intersectionRect.top // the upper boundary of the intersection region
res.intersectionRect.width // width of the intersection region
res.intersectionRect.height // height of the intersection region
})
}
})以下示例代碼可以在目標節點(用選擇器.target-class指定)每次進入或離開頁面顯示區域時,觸發回呼函數。
示例代碼:
Page({
onLoad: function(){
wx.createIntersectionObserver(this, {
thresholds: [0.2, 0.5]
}).relativeTo('.relative-class').relativeToViewport().observe('.target-class', (res) => {
res.intersectionRatio // the proportion of the intersection area to the layout area of the target node
res.intersectionRect // The intersection region
res.intersectionRect.left // the coordinates of the left boundary of the intersection region
res.intersectionRect.top // the upper boundary of the intersection region
res.intersectionRect.width // width of the intersection region
res.intersectionRect.height // height of the intersection region
})
}
})說明
與頁面顯示區域的相交區域並不準確代表用戶可見的區域,因為參與計算的區域是'佈局區域',佈局區域可能會在繪製時被其他節點裁剪隱藏(如遇祖先節點中overflow樣式為hidden的節點)或遮蓋(如遇fixed定位的節點)。
在自定義組件或包含自定義組件的頁面中,推薦使用this.createIntersectionObserver來代替 wx.createIntersectionObserver ,這樣可以確保在正確的範圍內選擇節點。
7.響應顯示區域變化
7.1顯示區域尺寸
顯示區域指小程序介面中可以自由佈局展示的區域在默認情况下,小程序顯示區域的尺寸自頁面初始化起就不會發生變化但以下三種方式都可以改變這一默認行為。
在手機上啟用荧幕旋轉支持
小程序在手機上支持荧幕旋轉使小程序中的頁面支持荧幕旋轉的方法是:在app.json的window段中設定'pageOrientation':'auto',或在頁面json文件中配置'pageOrientation':'auto'。
以下是在單個頁面json文件中啟用荧幕旋轉的示例。
代碼示例:
{
"pageOrientation": "auto"
}如果頁面添加了上述聲明,則在荧幕旋轉時,這個頁面將隨之旋轉,顯示區域尺寸也會隨著荧幕旋轉而變化pageOrientation還可以被設定為landscape,表示固定為橫屏顯示。
- 在iPad上不能單獨配置某個頁面是否支持荧幕旋轉
代碼示例:
{
"resizable": true
}如果小程序添加了上述聲明,則在荧幕旋轉時,小程序將隨之旋轉,顯示區域尺寸也會隨著荧幕旋轉而變化。
說明
在iPad上不能單獨配置某個頁面是否支持荧幕旋轉
7.2 Media Query
有時,對於不同尺寸的顯示區域,頁面的佈局會有所差异此時可以使用media query來解决大多數問題。
代碼示例:
.my-class {
width: 40px;
}
@media (min-width: 480px) {
/* Style rules that only work on screens 480px or wider */
.my-class {
width: 200px;
}
}7.3 荧幕旋轉事件
有時,僅僅使用media query無法控制一些精細的佈局變化此時可以使用js作為輔助。
在js中讀取頁面的顯示區域尺寸,可以使用 selectorQuery.selectViewport
頁面尺寸發生改變的事件,可以使用頁面的onResize來監聽對於自定義組件,可以使用resize生命週期來監聽回呼函數中將返回顯示區域的尺寸資訊。
代碼示例:
Page({
onResize(res) {
res.size.windowWidth // The width of the new display area.
res.size.windowHeight // the height of the new display area
}
})Component({
pageLifetimes: {
resize(res) {
res.size.windowWidth // The width of the new display area.
res.size.windowHeight // the height of the new display area
}
}
})此外,還可以使用wx.onWindowResize來監聽(但這不是推薦的管道)。
8. 頁面動畫
8.1 介面動畫的常見管道
在小程序中,通常可以使用CSS漸變和CSS動畫來創建簡易的介面動畫。
動畫過程中,可以使用bindtransitionend bindanimationstart bindanimationiteration bindanimationend來監聽動畫事件。
說明
這幾個事件都不是冒泡事件,需要綁定在真正發生了動畫的節點上才會生效。
同時,還可以使用wx.createAnimation接口來動態創建簡易的動畫效果(新版小程序基礎庫中推薦使用下述的關鍵幀動畫接口代替)。
8.2 高級的動畫管道
在一些複雜場景下,上述的動畫方法可能並不適用。
WXS響應事件 的管道可以通過使用WXS來響應事件的方法來動態調整節點的style内容通過不斷改變style内容的值可以做到動畫效果同時,這種管道也可以根據用戶的觸摸事件來動態地生成動畫。
連續使用setData來改變介面的方法也可以達到動畫的效果這樣可以任意地改變介面,但通常會產生較大的延遲或卡頓,甚至導致小程序僵死此時可以通過將頁面的setData改為 自定義組件 中的setData來提升效能。