游戏的开发界有一个理论,就是当动画或者交互响应达到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)吧!