遊戲的開發界有一個理論,就是當動畫或者交互響應達到60FPS(60幀每秒)的時候,就能夠定義爲流暢,按此理論,那麼每幀裏全部操做必須在16ms完成。要想提升頁面的用戶體驗,必須在性能上下功夫。最先作動畫都是用 setTimeout來實現的,而 setTimeout的處理回調的時間精度都在 16ms 左右。因此,能夠想象正經常使用頁面這兩個函數就已經 16 ms了,再加上reflow/repaint/compositing 卡頓或跳幀就是屢見不鮮了。不過還好的是w3c 標準和各瀏覽器廠商較早就支持了動畫接口 RAF(RequestAnimationFrame 函數)來處理動畫幀回調。解決了上述 setTimeout不足的問題。可是,另外一個問題仍然沒解決,當瀏覽器打開網頁時,須要解析文檔,在內存中生成DOM結構,若是遇到複雜的文檔,這個過程是很慢的。若是趕上低端的手機瀏覽器,能夠想象一下,若是網頁上有上萬個形狀(無論是圖片或CSS),生成DOM須要多久?更不要提與其中某一個形狀互動了。javascript
用戶與瀏覽器互動,從技術上看就是用戶在操做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操做的方法,大大提高了瀏覽器的性能。css
Canvas 是 HTML5 的畫布元素,也一個原生的DOM 元素。它至關於一個「白板」,咱們能夠經過javascript在這塊白板上增長文字與圖像,「繪製」一些可視內容。目前大多數H5遊戲和動畫特效都是用canvas實現的。不少在微信裏傳播的小遊戲和小應用,也是用canvas實現的。用canvas的話整個頁面只用一個DOM 元素,而且瀏覽器只須要繪製一次造成一幅圖。這大大下降了DOM 數量與渲染的複雜度。更好的是,canvas默認支持GPU硬件加速的,能夠將原來 CPU 密集型操做變成 GPU 操做。提升了動畫的流暢度。值得一提的是,微信瀏覽器的內核,也便是QQ瀏覽器 X5 內核已經內置了不少遊戲引擎(好比白鷺遊戲引擎與cocos2dx),供開發者開發canvas遊戲,因此長時間來看,微信瀏覽器的畫布性能將會愈來愈強大。html
大多數現代移動設備都擁有硬件加速的 canvas,咱們爲何不利用起來呢?HTML5 遊戲已經作到了。咱們爲何不採用遊戲的思路設計界面,在 canvas 上開發應用界面麼,用canvas來渲染頁面呢?相信你已經想到了,可是有人已經作到了,那就是Flipboard公司的React-canvas。React-canvas是什麼呢?光看名字就知道這是跟react和canvas相關的。React-canvas,可使咱們用react技術渲染canvas。前端
React Canvas 是依賴於React的一個組件,它擁有了渲染到canvas的能力,它可讓咱們脫離繁瑣的canvas命令式繪圖,使用簡單的css佈局(Layout)。接下來給你們演示一個簡單的圖文實現。java
新時代的前端開發離不開node環境,因此,react-canvas也不例外,node安裝的具體步驟再也不贅述。記住,Node版本不低於4.0。node
在D:\nodejs\reactdemo建立文件夾,此爲開發的根目錄 打開到此目錄,切換到命令行,執行 npm init,默認回車,初始化package.jsonreact
須要在node環境上安裝一系列框架 切換到命令行, 執行webpack
npm install react
複製代碼
安裝基礎框架react(注:安裝v0.13.0
,新版本我沒試驗,不知是否可行), 執行git
npm install jsx-loader
複製代碼
安裝編譯react用的jsx-loader
插件, 執行github
npm install react-canvas
複製代碼
安裝核心框架react-canvas
, 執行
npm install webpack
複製代碼
安裝打包代碼用的工具webpack。 而後建立文本文件index.html
, 建立配置文件webpack.config.js
, 建立js文件夾存放代碼文件。 至此,你的工做環境下應該有
其中node_modules
文件夾下,應該至少有自動生成的react、react-canvas、webpack
三個文件夾。
打開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地址:點我
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
等事件。
接下來介紹如何在 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模擬手機瀏覽器。文章末尾附件裏有完整實現的源代碼。
React-canvas使用一個 canvas 元素來繪製界面,完成滾動。在每個觸摸事件時,根據當前的滾動程度去更新渲染樹。以後,整個渲染樹使用新的座標來從新渲染。在 canvas 上有個重要的技術叫離屏canvas(off-screen),能夠如今內存中完成繪製,以後能夠一次性複製到用戶界面,而且使用離屏層從新繪製也是很是快的。React在界面更新以前會作虛擬 DOM 的 diff 。在render() 函數中只更新有變更的界面,React進一步提高了React-canvas的性能。流暢是React-canvas的主要優勢。 代碼基於react,由於react如今比較火,不少前端已經熟悉react的書寫,react也有不少相關的組件,因此react-canvas比較應景,讓一部分人很快的能夠上手。另外兼容性也比較好,繼承react和canvas的兼容性和跨平臺優勢。
當前不成熟和不穩定,react一直在變更,因此React-canvas也會隨之一併升級。react-canvas還不完善,不少DOM中的標籤特性在目前react-canvas裏面未能實現,好比沒法對文本進行復制,這讓使用React-canvas的時候會有一些限制。這個項目已經在Github上開源,做者也在對項目進行重構,指望下次更新的時候是一個功能強大的版本。另外,react-canvas的學習成本也比較高,使用react-canvas須要對react和canvas有必定的瞭解。
React Canvas並不能徹底取代DOM。我的以爲只適用在移動web(或者webview)上,手機的硬件資源相對有限,用戶互動又相對頻繁,咱們能夠在咱們的頁面中性能要求最關鍵的地方去使用它,尤爲是在微信瀏覽器中很常見滾動視圖這部分。當渲染性能不是問題的時候, DOM 多是一個更好的方法。事實上,對於某些元素好比輸入字段,和音頻/視頻標籤等,DOM是惟一的方法。從某種意義上說,react-canvas也算是一個混合( hybird )的應用程序。相比傳統的原生應用,react-canvas內容所有是 web 。咱們在開發中能夠把界面基於 dom 實現,並在適當的地方使用 canvas 渲染。DOM和canvas各取所長,優點互補。React-canvas能將頁面的交互和性能水平提高到能夠與本地應用相競爭,這就是它引人注意的地方。
咱們在web開發過程當中,都見過或者使用過一些奇技淫巧,這種技術咱們統稱爲黑魔法,這些黑魔法散落在各個角落,爲了方便你們查閱和學習,咱們作了收集、整理和歸類,並在github上作了一個項目——awesome-blackmargic,但願各位愛鑽研的開發者可以喜歡,也但願你們能夠把本身的獨門絕技分享出來,若是有興趣能夠給咱們發pr。
若是你對React感興趣,想進一步瞭解React,加入咱們的QQ羣(784383520)吧!