1、什麼是流暢的使用者體驗?
遊戲的開發界有一個理論,就是當動畫或者互動響應達到60FPS(60幀每秒)的時候,就可以定義為流暢,按此理論,那麼每幀裡所有操作必須在16ms完成。要想提高頁面的使用者體驗,必須在效能上下功夫。最早做動畫都是用 setTimeout來實現的,而 setTimeout的處理回撥的時間精度都在 16ms 左右。所以,可以想象正常用頁面這兩個函式就已經 16 ms了,再加上reflow/repaint/compositing 卡頓或跳幀就是家常便飯了。不過還好的是w3c 標準和各瀏覽器廠商較早就支援了動畫介面 RAF(RequestAnimationFrame 函式)來處理動畫幀回撥。解決了上述 setTimeout不足的問題。但是,另一個問題仍然沒解決,當瀏覽器開啟網頁時,需要解析文件,在記憶體中生成DOM結構,如果遇到複雜的文件,這個過程是很慢的。如果遇上低端的手機瀏覽器,可以想象一下,如果網頁上有上萬個形狀(不管是圖片或CSS),生成DOM需要多久?更不要提與其中某一個形狀互動了。
2、React是什麼?
使用者與瀏覽器互動,從技術上看就是使用者在操作DOM,所有的DOM操作都是同步的,會堵塞瀏覽器。JavaScript操作DOM時,必須等前一個操作結束,才能執行後一個操作。只要一個操作有卡頓,整個網頁就會短暫失去響應。瀏覽器重繪網頁的頻率是60FPS,JavaScript做不到在16毫秒內完成DOM操作,因此產生了跳幀。使用者體驗上的不流暢、不連貫就源於此。JavaScript語言執行效率本身很快,但是DOM太慢了,DOM拖慢JavaScript。為了解決這個問題,React出現了,React是 Facebook 推出的一個用來構建使用者介面的 JavaScript 開源框架,React的最引人注目的特徵就是引入了虛擬DOM(Virtual DOM)這個概念,在瀏覽器端用JavaScript實現了一套DOM API。基於React進行開發時所有的DOM構造都是通過虛擬DOM進行,每當使用者介面需要變化時,React都會重新構建整個DOM樹,然後React將當前整個DOM樹和上一次的DOM樹進行對比,得到DOM結構的區別,然後僅僅將需要變化的部分進行實際的瀏覽器DOM更新。React實現了程式碼最小化參與DOM操作的方法,大大提升了瀏覽器的效能。
3、Canvas是什麼?
Canvas 是 HTML5 的畫布元素,也一個原生的DOM 元素。它相當於一個“白板”,我們可以通過javascript在這塊白板上增加文字與影像,“繪製”一些可視內容。目前大多數H5遊戲和動畫特效都是用canvas實現的。很多在微信裡傳播的小遊戲和小應用,也是用canvas實現的。用canvas的話整個頁面只用一個DOM 元素,並且瀏覽器只需要繪製一次形成一幅圖。這大大降低了DOM 數量與渲染的複雜度。更好的是,canvas預設支援GPU硬體加速的,可以將原來 CPU 密集型操作變成 GPU 操作。提高了動畫的流暢度。值得一提的是,微信瀏覽器的核心,也即是QQ瀏覽器 X5 核心已經內建了很多遊戲引擎(比如白鷺遊戲引擎與cocos2dx),供開發者開發canvas遊戲,所以長時間來看,微信瀏覽器的畫布效能將會越來越強大。
4、新的方法
大多數現代移動裝置都擁有硬體加速的 canvas,我們為什麼不利用起來呢?HTML5 遊戲已經做到了。我們為什麼不採用遊戲的思路設計介面,在 canvas 上開發應用介面麼,用canvas來渲染頁面呢?相信你已經想到了,但是有人已經做到了,那就是Flipboard公司的React-canvas。React-canvas是什麼呢?光看名字就知道這是跟react和canvas相關的。React-canvas,可以使我們用react技術渲染canvas。
5、React-canvas入門
React Canvas 是依賴於React的一個元件,它擁有了渲染到canvas的能力,它可以讓我們脫離繁瑣的canvas命令式繪圖,使用簡單的css佈局(Layout)。接下來給大家演示一個簡單的圖文實現。
5.1.安裝node
新時代的前端開發離不開node環境,所以,react-canvas也不例外,node安裝的具體步驟不再贅述。記住,Node版本不低於4.0。
5.2建立專案空間
在D:nodejsreactdemo建立資料夾,此為開發的根目錄
開啟到此目錄,切換到命令列,執行 npm init,預設回車,初始化package.json
5.3安裝框架和外掛
需要在node環境上安裝一系列框架
切換到命令列,
執行
npm install react
安裝基礎框架react(注:安裝v0.13.0
,新版本我沒試驗,不知是否可行),
執行
npm install jsx-loader
安裝編譯react用的jsx-loader
外掛,
執行
npm install react-canvas
安裝核心框架react-canvas
,
執行
npm install webpack
安裝打包程式碼用的工具webpack。
然後建立文字檔案index.html
,
建立配置檔案webpack.config.js
,
建立js資料夾存放程式碼檔案。
至此,你的工作環境下應該有
其中node_modules
資料夾下,應該至少有自動生成的react、react-canvas、webpack
三個資料夾。
5.4讓程式碼跑起來
開啟index.html
檔案,程式碼如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>react canvas第一步</title>
</head>
<body>
<div id="main"></div>
<script src="bundle.js"></script>
</body>
</html>
主頁面只有一個id=main
的div
標籤,並外鏈一個js
檔案。
開啟js
資料夾,建立index.js
,程式碼如下
var React = require('react');
var ReactCanvas = require('react-canvas');
var Surface = ReactCanvas.Surface;
var Image = ReactCanvas.Image;
var Text = ReactCanvas.Text;
var MyComponent = React.createClass({
// 介面渲染
render: function () {
var surfaceWidth = window.innerWidth;
var surfaceHeight = window.innerHeight;
var imageStyle = this.getImageStyle();
var textStyle = this.getTextStyle();
return (
<Surface width={surfaceWidth} height={surfaceHeight} left={0} top={0}>
<Image style={imageStyle} src='http://img1.gtimg.com/joke/pics/hv1/193/44/1996/129801313.png' />
<Text style={textStyle}>
哈哈,你來打我呀
</Text>
</Surface>
);
},
// 計算居中
getImageHeight: function () {
return Math.round(window.innerHeight / 2);
},
getImageWidth: function () {
return Math.round(window.innerWidth / 2);
},
// 圖片樣式
getImageStyle: function () {
return {
top: this.getImageHeight() -32,
left: this.getImageWidth() -32,
width: 64,
height: 64
};
},
// 文字樣式
getTextStyle: function () {
return {
top: this.getImageHeight() + 64,
left: 0,
width: window.innerWidth,
height: 20,
lineHeight: 20,
fontSize: 12,
textAlign : 'center'
};
}
});
React.render(<MyComponent />, document.getElementById('main'));
配置webpack.config.js
module.exports = {
//入口檔案
entry: './js/main.jsx',
//輸出檔案
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
loaders: [
// 凡是遇到jsx、js結尾的,都用jsx-loader這個外掛來載入,
// 且啟用harmony模式
{ test: /\.jsx$/, loader: 'jsx-loader?harmony'},
{ test: /\.js$/, loader: 'jsx-loader?harmony'},
]
},
// 表示這個依賴項是外部lib,遇到require它不需要編譯,
// 且在瀏覽器端對應window.React
//externals: {
//'react': 'window.React'
//},
// 現在可以寫 require('file') 代替 require('file.jsx')
resolve: {
root: __dirname,
extensions: ['', '.js', '.jsx']
}
};
配置完畢後,切換到命令列執行webpack -p
命令,打包,編譯
打包後在根目錄下生成一個bundle.js
,直接用瀏覽器開啟,就可以看到效果了。
線上demo地址:點我
6、語法及元素(標籤、元件)
react-canvas
的語法和react
一樣,如果你熟悉react,react-canvas很容易上手。react-canvas自定義了幾個標籤。這些標籤也都是標準的React components
。
<Surface>
Surface是一個頂級標籤,是一個容器,你可以把任何元素放在上面,可以把它看成canvas元素,整個專案要被套在一個surface裡面。在上述示例中已經使用。
<Layer>
Layer 層級僅次於surface,可以放其他元素。 基本樣式和屬性例如top, width, left, height, backgroundColor and zIndex
可以在這一層設定。
<Group>
Group是一個容器,因為react渲染元件時必須要有一個總的標籤包含所有的雜湊標籤,在react-canvas中,Group扮演了div的角色,可以用來做零散標籤的父標籤。把一系列相關聯標籤用Group封裝起來,一方面可以提高程式碼的內聚,更加模組化,另一方面可以提高頁面滾動時的效能。
<Text>
Text 是用來存放文字的,是一個彈性的標籤,canvas不支援自動截斷換行,而Text標籤支援。
<Image>
Image 跟你想象的一樣,用來放圖片的。 但是它支援載入完畢後才顯示,並且可以隨意的隱藏。
<ListView>
ListView是一個列表,可以認為它相當於HTML頁面裡的ul
或者native app
中的UITableView
,它可以提高頁面的滾動效能。
同時react-canvas給各個標籤以事件支援,有touchstart,move,end,click
等事件。
7、圖文滾動列表
接下來介紹如何在 React Canvas 中建立一個達到60 fps
,分頁的滾動列表。事實證明這實現起來非常容易,
修改main.js
程式碼為
/** @jsx React.DOM */
'use strict';
var React = require('react');
var ReactCanvas = require('react-canvas');
// page檔案負責渲染單個頁面
var Page = require('./components/Page');
// data檔案存放json格式的圖文
var articles = require('./common/data');
var Surface = ReactCanvas.Surface;
var ListView = ReactCanvas.ListView;
var App = React.createClass({
// 渲染整個列表
render: function () {
var size = this.getSize();
return (
<Surface top={0} left={0} width={size.width} height={size.height}>
<ListView
style={this.getListViewStyle()}
snapping={true}
scrollingDeceleration={0.92}
scrollingPenetrationAcceleration={0.13}
numberOfItemsGetter={this.getNumberOfPages}
itemHeightGetter={this.getPageHeight}
itemGetter={this.renderPage} />
</Surface>
);
},
// 渲染單頁
renderPage: function (pageIndex, scrollTop) {
var size = this.getSize();
var article = articles[pageIndex % articles.length];
var pageScrollTop = pageIndex * this.getPageHeight() - scrollTop;
return (
<Page
width={size.width}
height={size.height}
article={article}
pageIndex={pageIndex}
scrollTop={pageScrollTop} />
);
},
// 瀏覽器大小
getSize: function () {
return document.getElementById('main').getBoundingClientRect();
},
// 整個列表的外觀
getListViewStyle: function () {
var size = this.getSize();
return {
top: 0,
left: 0,
width: size.width,
height: size.height,
};
},
// 設定頁面的可滾動的次數,若超過頁面的數量,迴圈滾動
getNumberOfPages: function () {
return 9;
},
// 計算單頁的高度
getPageHeight: function () {
return this.getSize().height;
}
});
React.render(<App />, document.getElementById('main'));
整個頁面程式碼由一個surface,一個listview,9個page組成,可以上下屏滾動。react-canvas將網頁變成了一個canvas,使用者就等於在跟圖片互動,這樣就繞開了DOM,降低了操作時滯。而且,canvas可以被硬體加速,這樣就提高了效能,體驗非常流暢。
你可以檢視這個線上的Demo點我,pc使用者記得用chrome模擬手機瀏覽器。文章末尾附件裡有完整實現的原始碼。
8、React-canvas的優缺點
8.1優點
React-canvas使用一個 canvas 元素來繪製介面,完成滾動。在每一個觸控事件時,根據當前的滾動程度去更新渲染樹。之後,整個渲染樹使用新的座標來重新渲染。在 canvas 上有個重要的技術叫離屏canvas(off-screen),可以現在記憶體中完成繪製,之後可以一次性複製到使用者介面,並且使用離屏層重新繪製也是非常快的。React在介面更新之前會做虛擬 DOM 的 diff 。在render() 函式中只更新有變動的介面,React進一步提升了React-canvas的效能。流暢是React-canvas的主要優點。
程式碼基於react,因為react現在比較火,很多前端已經熟悉react的書寫,react也有很多相關的元件,所以react-canvas比較應景,讓一部分人很快的可以上手。另外相容性也比較好,繼承react和canvas的相容性和跨平臺優點。
8.2缺點
當前不成熟和不穩定,react一直在變動,所以React-canvas也會隨之一併升級。react-canvas還不完善,很多DOM中的標籤特性在目前react-canvas裡面未能實現,比如無法對文字進行復制,這讓使用React-canvas的時候會有一些限制。這個專案已經在Github上開源,作者也在對專案進行重構,期望下次更新的時候是一個功能強大的版本。另外,react-canvas的學習成本也比較高,使用react-canvas需要對react和canvas有一定的瞭解。
9、總結
React Canvas並不能完全取代DOM。個人覺得只適用在移動web(或者webview)上,手機的硬體資源相對有限,使用者互動又相對頻繁,我們可以在我們的頁面中效能要求最關鍵的地方去使用它,尤其是在微信瀏覽器中很常見滾動檢視這部分。當渲染效能不是問題的時候, DOM 可能是一個更好的方法。事實上,對於某些元素比如輸入欄位,和音訊/視訊標籤等,DOM是唯一的方法。從某種意義上說,react-canvas也算是一個混合( hybird )的應用程式。相比傳統的原生應用,react-canvas內容全部是 web 。我們在開發中可以把介面基於 dom 實現,並在適當的地方使用 canvas 渲染。DOM和canvas各取所長,優勢互補。React-canvas能將頁面的互動和效能水平提升到可以與本地應用相競爭,這就是它引人注意的地方。
10、安利時間
我們在web開發過程中,都見過或者使用過一些奇技淫巧,這種技術我們統稱為黑魔法,這些黑魔法散落在各個角落,為了方便大家查閱和學習,我們做了收集、整理和歸類,並在github上做了一個專案——awesome-blackmargic,希望各位愛鑽研的開發者能夠喜歡,也希望大家可以把自己的獨門絕技分享出來,如果有興趣可以給我們發pr。
如果你對React感興趣,想進一步瞭解React,加入我們的QQ群(784383520)吧!