【黑科技】React-canvas助力HTML5

一、什麼是流暢的用戶體驗?

遊戲的開發界有一個理論,就是當動畫或者交互響應達到60FPS(60幀每秒)的時候,就能夠定義爲流暢,按此理論,那麼每幀裏全部操做必須在16ms完成。要想提升頁面的用戶體驗,必須在性能上下功夫。最先作動畫都是用 setTimeout來實現的,而 setTimeout的處理回調的時間精度都在 16ms 左右。因此,能夠想象正經常使用頁面這兩個函數就已經 16 ms了,再加上reflow/repaint/compositing 卡頓或跳幀就是屢見不鮮了。不過還好的是w3c 標準和各瀏覽器廠商較早就支持了動畫接口 RAF(RequestAnimationFrame 函數)來處理動畫幀回調。解決了上述 setTimeout不足的問題。可是,另外一個問題仍然沒解決,當瀏覽器打開網頁時,須要解析文檔,在內存中生成DOM結構,若是遇到複雜的文檔,這個過程是很慢的。若是趕上低端的手機瀏覽器,能夠想象一下,若是網頁上有上萬個形狀(無論是圖片或CSS),生成DOM須要多久?更不要提與其中某一個形狀互動了。javascript

二、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操做的方法,大大提高了瀏覽器的性能。css

三、Canvas是什麼?

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 是依賴於React的一個組件,它擁有了渲染到canvas的能力,它可讓咱們脫離繁瑣的canvas命令式繪圖,使用簡單的css佈局(Layout)。接下來給你們演示一個簡單的圖文實現。java

5.1.安裝node

新時代的前端開發離不開node環境,因此,react-canvas也不例外,node安裝的具體步驟再也不贅述。記住,Node版本不低於4.0。node

5.2建立項目空間

在D:\nodejs\reactdemo建立文件夾,此爲開發的根目錄 打開到此目錄,切換到命令行,執行 npm init,默認回車,初始化package.jsonreact

5.3安裝框架和插件

須要在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三個文件夾。

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=maindiv標籤,並外鏈一個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的優缺點

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有必定的瞭解。

九、總結

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)吧!

相關文章
相關標籤/搜索