遊戲的開發界有一個理論,就是當動畫或者交互響應達到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:nodejsreactdemo建立文件夾,此爲開發的根目錄
打開到此目錄,切換到命令行,執行 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是一個頂級標籤,是一個容器,你能夠把任何元素放在上面,能夠把它當作canvas元素,整個項目要被套在一個surface裏面。在上述示例中已經使用。
Layer 層級僅次於surface,能夠放其餘元素。 基本樣式和屬性例如top, width, left, height, backgroundColor and zIndex
能夠在這一層設置。
Group是一個容器,由於react渲染組件時必需要有一個總的標籤包含全部的散列標籤,在react-canvas中,Group扮演了div的角色,能夠用來作零散標籤的父標籤。把一系列相關聯標籤用Group封裝起來,一方面能夠提升代碼的內聚,更加模塊化,另外一方面能夠提升頁面滾動時的性能。
Text 是用來存放文本的,是一個彈性的標籤,canvas不支持自動截斷換行,而Text標籤支持。
Image 跟你想象的同樣,用來放圖片的。 可是它支持加載完畢後才顯示,而且能夠隨意的隱藏。
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)吧!