React中最神奇的部分莫过于虚拟DOM,以及其高效的Diff算法。这让我们可以无需担心性能问题而”毫无顾忌”的随时“刷新”整个页面,由虚拟DOM来确保只对界面上真正变化的部分进行实际的DOM操作。React在这一部分已经做到足够透明,在实际开发中我们基本无需关心虚拟DOM是如何运作的。然而,作为有态度的程序员,我们总是对技术背后的原理充满着好奇。理解其运行机制不仅有助于更好的理解React组件的生命周期,而且对于进一步优化React程序也会有很大帮助。 什么是DOM Diff算法 Web界面由DOM树来构成,当其中某一部分发生变化时,其实就是对应的某个DOM节点发生了变化。在React中,构建UI界面的思路是由当前状态决定界面。前后两个状态就对应两套界面,然后由React来比较两个界面的区别,这就需要对DOM树进行Diff算法分析。 即给定任意两棵树,找到最少的转换步骤。但是标准的的Diff算法复杂度需要O(n^3),这显然无法满足性能要求。要达到每次界面都可以整体刷新界面的目的,势必需要对算法进行优化。这看上去非常有难度,然而Facebook工程师却做到了,他们结合Web界面的特点做出了两个简单的假设,使得Diff算法复杂度直接降低到O(n) 两个相同组件产生类似的DOM结构,不同的组件产生不同的DOM结构; 对于同一层次的一组子节点,它们可以通过唯一的id进行区分。 算法上的优化是React整个界面Render的基础,事实也证明这两个假设是合理而精确的,保证了整体界面构建的性能。 不同节点类型的比较 为了在树之间进行比较,我们首先要能够比较两个节点,在React中即比较两个虚拟DOM节点,当两个节点不同时,应该如何处理。这分为两种情况:(1)节点类型不同 ,(2)节点类型相同,但是属性不同。本节先看第一种情况。 当在树中的同一位置前后输出了不同类型的节点,React直接删除前面的节点,然后创建并插入新的节点。假设我们在树的同一位置前后两次输出不同类型的节点。
|
1 2 3 |
renderA: <div /> renderB: <span /> => [removeNode <div />], [insertNode <span />] |
当一个节点从div变成span时,简单的直接删除div节点,并插入一个新的span节点。这符合我们对真实DOM操作的理解。 需要注意的是,删除节点意味着彻底销毁该节点,而不是再后续的比较中再去看是否有另外一个节点等同于该删除的节点。如果该删除的节点之下有子节点,那么这些子节点也会被完全删除,它们也不会用于后面的比较。这也是算法复杂能够降低到O(n)的原因。 上面提到的是对虚拟DOM节点的操作,而同样的逻辑也被用在React组件的比较,例如:
|
1 2 3 |
renderA: <Header /> renderB: <Content /> => [removeNode <Header />], [insertNode <Content />] |
当React在同一个位置遇到不同的组件时,也是简单的销毁第一个组件,而把新创建的组件加上去。这正是应用了第一个假设,不同的组件一般会产生不一样的DOM结构,与其浪费时间去比较它们基本上不会等价的DOM结构,还不如完全创建一个新的组件加上去。 由这一React对不同类型的节点的处理逻辑我们很容易得到推论,那就是React的DOM Diff算法实际上只会对树进行逐层比较,如下所述。 逐层进行节点比较 提到树,相信大多数同学立刻想到的是二叉树,遍历,最短路径等复杂的数据结构算法。而在React中,树的算法其实非常简单,那就是两棵树只会对同一层次的节点进行比较。如下图所示: React只会对相同颜色方框内的DOM节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个DOM树的比较。 例如,考虑有下面的DOM结构转换: A节点被整个移动到D节点下,直观的考虑DOM Diff操作应该是
|
1 2 |
A.parent.remove(A); D.append(A); |
但因为React只会简单的考虑同层节点的位置变换,对于不同层的节点,只有简单的创建和删除。当根节点发现子节点中A不见了,就会直接销毁A;而当D发现自己多了一个子节点A,则会创建一个新的A作为子节点。因此对于这种结构的转变的实际操作是:
|
1 2 3 4 5 |
A.destroy(); A = new A(); A.append(new B()); A.append(new C()); D.append(A); |
可以看到,以A为根节点的树被整个重新创建。 虽然看上去这样的算法有些“简陋”,但是其基于的是第一个假设:两个不同组件一般产生不一样的DOM结构。根据React官方博客,这一假设至今为止没有导致严重的性能问题。这当然也给我们一个提示,在实现自己的组件时,保持稳定的DOM结构会有助于性能的提升。例如,我们有时可以通过CSS隐藏或显示某些节点,而不是真的移除或添加DOM节点。 由DOM Diff算法理解组件的生命周期 在上一篇文章中介绍了React组件的生命周期,其中的每个阶段其实都是和DOM Diff算法息息相关的。例如以下几个方法: constructor: 构造函数,组件被创建时执行; componentDidMount: 当组件添加到DOM树之后执行; componentWillUnmount: 当组件从DOM树中移除之后执行,在React中可以认为组件被销毁; componentDidUpdate: 当组件更新时执行。 为了演示组件生命周期和DOM Diff算法的关系,笔者创建了一个示例:https://supnate.github.io/react-dom-diff/index.html ,大家可以直接访问试用。这时当DOM树进行如下转变时,即从“shape1”转变到“shape2”时。我们来观察这几个方法的执行情况: 浏览器开发工具控制台输出如下结果:
|
1 2 3 4 5 6 7 |
C will unmount. C is created. B is updated. A is updated. C did mount. D is updated. R is updated. |
可以看到,C节点是完全重建后再添加到D节点之下,而不是将其“移动”过去。如果大家有兴趣,也可以fork示例代码:https://github.com/supnate/react-dom-diff 。从而可以自己添加其它树结构,试验它们之间是如何转换的。 相同类型节点的比较 第二种节点的比较是相同类型的节点,算法就相对简单而容易理解。React会对属性进行重设从而实现节点的转换。例如:
|
1 2 3 |
renderA: <div id="before" /> renderB: <div id="after" /> => [replaceAttribute id "after"] |
虚拟DOM的style属性稍有不同,其值并不是一个简单字符串而必须为一个对象,因此转换过程如下:
|
1 2 3 |
renderA: <div style={{color: 'red'}} /> renderB: <div style={{fontWeight: 'bold'}} /> => [removeStyle color], [addStyle font-weight 'bold'] |
列表节点的比较 上面介绍了对于不在同一层的节点的比较,即使它们完全一样,也会销毁并重新创建。那么当它们在同一层时,又是如何处理的呢?这就涉及到列表节点的Diff算法。相信很多使用React的同学大多遇到过这样的警告: 这是React在遇到列表时却又找不到key时提示的警告。虽然无视这条警告大部分界面也会正确工作,但这通常意味着潜在的性能问题。因为React觉得自己可能无法高效的去更新这个列表。 列表节点的操作通常包括添加、删除和排序。例如下图,我们需要往B和C直接插入节点F,在jQuery中我们可能会直接使用$(B).after(F)来实现。而在React中,我们只会告诉React新的界面应该是A-B-F-C-D-E,由Diff算法完成更新界面。 这时如果每个节点都没有唯一的标识,React无法识别每一个节点,那么更新过程会很低效,即,将C更新成F,D更新成C,E更新成D,最后再插入一个E节点。效果如下图所示: 可以看到,React会逐个对节点进行更新,转换到目标节点。而最后插入新的节点E,涉及到的DOM操作非常多。而如果给每个节点唯一的标识(key),那么React能够找到正确的位置去插入新的节点,入下图所示: 对于列表节点顺序的调整其实也类似于插入或删除,下面结合示例代码我们看下其转换的过程。仍然使用前面提到的示例:https://supnate.github.io/react-dom-diff/index.html ,我们将树的形态从shape5转换到shape6: 即将同一层的节点位置进行调整。如果未提供key,那么React认为B和C之后的对应位置组件类型不同,因此完全删除后重建,控制台输出如下:
|
1 2 3 4 5 6 7 8 |
B will unmount. C will unmount. C is created. B is created. C did mount. B did mount. A is updated. R is updated. |
而如果提供了key,如下面的代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
shape5: function() { return ( <Root> <A> <B key="B" /> <C key="C" /> </A> </Root> ); }, shape6: function() { return ( <Root> <A> <C key="C" /> <B key="B" /> </A> </Root> ); }, |
那么控制台输出如下:
|
1 2 3 4 |
C is updated. B is updated. A is updated. R is updated. |
可以看到,对于列表节点提供唯一的key属性可以帮助React定位到正确的节点进行比较,从而大幅减少DOM操作次数,提高了性能。 小结 本文分析了React的DOM Diff算法究竟是如何工作的,其复杂度控制在了O(n),这让我们考虑UI时可以完全基于状态来每次render整个界面而无需担心性能问题,简化了UI开发的复杂度。而算法优化的基础是文章开头提到的两个假设,以及React的UI基于组件这样的一个机制。理解虚拟DOM Diff算法不仅能够帮助我们理解组件的生命周期,而且也对我们实现自定义组件时如何进一步优化性能具有指导意义。 from:http://www.infoq.com/cn/articles/react-dom-diff
View Details前面几篇文章介绍了React相关的基本概念和运行原理,可以看到React是一个完全面向View的解决方案,它让我们能以一种新的思路去实现View,让很多复杂的场景可以用一种简单的方法去解决。然而在一个完整的应用程序中,除了实现View之外,我们还需要考虑如何同服务器通信、View之间如何交互以及View背后的数据模型如何去设计。那么Flux正是Facebook提出的解决这些问题的方案。 简单来说,Flux定义了一种单向数据流的方式,来实现View和Model之间的数据流动。它更像是一种设计模式而非一个正式的框架,以至于官方的Flux参考实现只有一个文件,区区100多行源代码。所以Flux继承了React的简单、直观的设计思想,让人一眼就能看明白其背后的运行原理。当然,要用好Flux,还是要正确理解其概念和背后的出发点,官方则是提供了两个具体的例子供大家参考。 Flux的标准实现非常简单,因此还衍生出了很多第三方实现,比较著名的包括Redux,Reflux,Fluxmm。而如今最为火热的应该属于Redux,它采用了函数式编程的思想来维护整个应用程序的状态。其实无论哪一种框架,都是以Flux的架构为基础而做的演变,其核心都是单向数据流和单向数据绑定。因而本文只会介绍官方的Flux,理解了标准实现之后也会更容易理解其他的实现方式。大家可以按照自己的兴趣和认可程度选择最适合自己的实现。 Flux 要解决的问题 在传统MVC框架中,通常使用双向绑定的方式来将Model的数据展现到View。当Model中的数据发生变化时,一个或多个View会发生变化;当View接受了用户输入时,Model中的数据则会发生变化。在实际的应用中,当一个Model中的数据发生变化时,也有可能另一个相关的Model中的数据会被同步更新。这样,很容易出现的一个现象就是连锁更新(Cascading Update),Model可以更新Model,Model可以更新View,View也可以更新Model。你很难去推断一个界面的变化究竟是由哪个局部的功能代码引起。如下图所示, Model 和 View 之间的关系错综复杂,导致出现问题时很难调试;实现新功能时也需要时刻注意代码是否会产生副作用。 对此问题,Flux的解决方案是让数据流变成单向,引入Store、Action、Action Creators和Dispatcher等概念来管理信息流。如下图所示: 可以看到,数据流变成单向的。同时,数据如何被处理也被明确的定义了。在MVC中,数据如何处理通常由Controller来完成,在Controller中实现大部分的业务逻辑来处理数据。而现在则被清晰的定义在Store或者Action Creators中。当然,上图隐藏了一些细节,更为全面的架构图则如下所示: 在Flux中,View完全是Store的展现形式,Store的更新则完全由Action触发。得益于React的View每次更新都是整体刷新的思路,我们可以完全不必关心Store的变化细节,只需要监听Store的onChange事件,每次变化都触发View的re-render。从而也可以看到,尽管Flux架构可以离开React单独使用,但无疑两者结合是一个更加和谐的方案,能够各发挥所长。 看一个具体的例子 为了对Flux有一个总体的印象,我们先考虑一个简单的使用场景:在文章评论页面提交一条评论。为此,我们需要向服务器发送一个请求提交新的评论,同时要将新的评论显示在列表中。这样的场景如果使用Flux去实现,大概需要实现以下几个部分: React组件用于显示评论列表以及评论框,并绑定到Store; 一个Store用于存储评论数据; Action Creator用于向服务器发送请求; Store中监听Action并进行处理,从而对Store自身进行更新。 整个架构如下图所示: (点击放大图像) 整个流程的运行大概如下: 用户点击提交按钮,Action Creator负责向服务器发送请求; 请求如果成功,那么将评论本身被添加到Store; 请求如果失败,那么在Store中标记一个特别的错误状态; View监听了Store的onChange的事件,因此,无论请求成功和失败,Store都会触发onChange事件,这时View就会进行整体更新。 可以看到,无论请求成功和失败,都是去修改组件之外的Store,由Store通知UI进行变化。在这样一个架构中,Store中存储的是整个或者一部分应用程序的状态,React实现的View只需要监听Store的变化,而无需知道变化的细节,这也是由React组件的特点决定的。这样,我们就使用Flux完成了评论功能,不同于双向绑定,在Flux的流程中,数据如何流转和变化,变得非常清晰明确。虽然可能需要写更多的代码,但是带来了更清楚的架构。下面,我们来具体看其中的每个具体组件的概念和用法。 View和Store 在Flux架构中,View即React的组件,而Store则存储的是应用程序的状态。在前面的文章中我们已经介绍过,React是完全面向View的解决方案,它提供了一种始终都是整体刷新的思路来构建界面。在React的思路中,UI就是一个状态机,每个确定的状态对应着一个确定的界面。对于一个小的组件,它的状态可能是在其内部进行维护;而对于多个组件组成的应用程序,如果某些状态需要在组件之间进行共享,则可以将这部分状态放到Store中进行维护。在Flux中,Store并不是一个复杂的机制,甚至Flux的官方实现中并没有任何Store相关的机制和接口,而是仅仅通过示例来描述了一个Store应该是什么样的数据结构。例如,在官方提供的TodoMVC例子( https://github.com/facebook/flux/tree/master/examples/flux-todomvc/ )中,Store的实现如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<span class="token keyword">var</span> _todos <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">var</span> TodoStore <span class="token operator">=</span> <span class="token function">assign</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> EventEmitter<span class="token punctuation">.</span>prototype<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * Get the entire collection of TODOs. * @return {object} */</span> getAll<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> _todos<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> emitChange<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">emit</span><span class="token punctuation">(</span>CHANGE_EVENT<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> addChangeListener<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span>callback<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span>CHANGE_EVENT<span class="token punctuation">,</span> callback<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> removeChangeListener<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span>callback<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">removeListener</span><span class="token punctuation">(</span>CHANGE_EVENT<span class="token punctuation">,</span> callback<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
可以看到,一个Flux的Store就是一个能触发onChange事件的对象,能够让其它对象订阅(addChangeListener)或者取消订阅(removeChangeListener)。同时,它提供了一些API供View来获取自己需要的状态。因此,也可以将Store理解为需要被不同View共享的公用状态。 那么,已经有了Store,React的组件(View)该如何使用它们呢?其实很简单,只需要在Store每次变化时都去获取一下最新的数据即可。我们可以看下TodoMVC中的实现:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
<span class="token keyword">var</span> TodoStore <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../stores/TodoStore'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** * Retrieve the current TODO data from the TodoStore */</span> <span class="token keyword">function</span> <span class="token function">getTodoState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> allTodos<span class="token punctuation">:</span> TodoStore<span class="token punctuation">.</span><span class="token function">getAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> areAllComplete<span class="token punctuation">:</span> TodoStore<span class="token punctuation">.</span><span class="token function">areAllComplete</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> TodoApp <span class="token operator">=</span> React<span class="token punctuation">.</span><span class="token function">createClass</span><span class="token punctuation">(</span><span class="token punctuation">{</span> getInitialState<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">getTodoState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> componentDidMount<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> TodoStore<span class="token punctuation">.</span><span class="token function">addChangeListener</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onChange<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> componentWillUnmount<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> TodoStore<span class="token punctuation">.</span><span class="token function">removeChangeListener</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onChange<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment" spellcheck="true">/** * @return {object} */</span> render<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div<span class="token operator">></span> <span class="token operator"><</span>Header <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span>MainSection allTodos<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>allTodos<span class="token punctuation">}</span> areAllComplete<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>areAllComplete<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span>Footer allTodos<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>allTodos<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment" spellcheck="true">/** * Event handler for 'change' events coming from the TodoStore */</span> _onChange<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token function">getTodoState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
可以看到,在组件的componentDidMount方法中,开始监听Store的onChange事件,在componentWillUnmount方法中,取消监听onChange事件。在Store的每次变化后,都去重新获取自己需要的状态数据:getTodoState()。 通过这样一种很简单的机制,我们建立了从Store到View的数据绑定,每当Store发生变化,View也会进行相应的更新。那么底下我们需要关心当View接收用户交互,需要将新的状态存入到Store中,应该如何去实现。这就需要引入Flux的另外两个概念Dispatcher和Action。 Dispatcher,Action 顾名思义,Dispatcher就是负责分发不同的Action。在一个Flux应用中,只有一个中心的Dispatcher,所有的Action都通过它来分发。而Facebook的官方Flux实现其实就仅仅是提供了Dispatcher。使用Dispatcher只需要将其作为npm模块引入:
|
1 |
<span class="token keyword">var</span> Dispatcher <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'flux'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Dispatcher<span class="token punctuation">;</span> |
典型的,Dispatcher有两个方法: dispatch:分发一个Action; register:注册一个Action处理函数。 这样,当View接受了一个用户的输入之后,通过Dispatcher来分发一个特定的Action,而对应的Action处理函数会负责去更新Store。这个流程在文章开始的图中可以清楚的看到。因此,通常来说Action的处理函数会和Store放在一起,因为Store的更新都是由Action处理函数来完成的。例如在TodoMVC中,TodoStore中会处理如下Action:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
Dispatcher<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span>action<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> text<span class="token punctuation">;</span> <span class="token keyword">switch</span><span class="token punctuation">(</span>action<span class="token punctuation">.</span>actionType<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_CREATE<span class="token punctuation">:</span> text <span class="token operator">=</span> action<span class="token punctuation">.</span>text<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>text <span class="token operator">!==</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">create</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_TOGGLE_COMPLETE_ALL<span class="token punctuation">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>TodoStore<span class="token punctuation">.</span><span class="token function">areAllComplete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">updateAll</span><span class="token punctuation">(</span><span class="token punctuation">{</span>complete<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token function">updateAll</span><span class="token punctuation">(</span><span class="token punctuation">{</span>complete<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_UNDO_COMPLETE<span class="token punctuation">:</span> <span class="token function">update</span><span class="token punctuation">(</span>action<span class="token punctuation">.</span>id<span class="token punctuation">,</span> <span class="token punctuation">{</span>complete<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_COMPLETE<span class="token punctuation">:</span> <span class="token function">update</span><span class="token punctuation">(</span>action<span class="token punctuation">.</span>id<span class="token punctuation">,</span> <span class="token punctuation">{</span>complete<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_UPDATE_TEXT<span class="token punctuation">:</span> text <span class="token operator">=</span> action<span class="token punctuation">.</span>text<span class="token punctuation">.</span><span class="token function">trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>text <span class="token operator">!==</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">update</span><span class="token punctuation">(</span>action<span class="token punctuation">.</span>id<span class="token punctuation">,</span> <span class="token punctuation">{</span>text<span class="token punctuation">:</span> text<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_DESTROY<span class="token punctuation">:</span> <span class="token function">destroy</span><span class="token punctuation">(</span>action<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> TodoConstants<span class="token punctuation">.</span>TODO_DESTROY_COMPLETED<span class="token punctuation">:</span> <span class="token function">destroyCompleted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TodoStore<span class="token punctuation">.</span><span class="token function">emitChange</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token comment" spellcheck="true">// no op</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
无论是添加、删除还是修改一个Todo项,都是由Action来触发的。在Action处理函数中,不仅对Store进行了更新,还触发了Store的onChange事件,从而让所有监听组件能够得到通知。完整的代码可以参考:https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/stores/TodoStore.js 。 通过Dispatcher和Action,实现了从View到Store的数据流,进而实现了整个Flux的单向数据流循环。从这里可以看到,Dispatcher是全局唯一的,相当于是所有Action的总hub,而每个Action处理函数都能够收到所有的Action,至于需要对哪些进行处理,则由处理函数自己决定。例子中是通过switch来判断Action的type属性来决定如何进行处理。因此,虽然不是必须,但是一般Action都会有一个type属性来标识其类型。 Action Creators 有了上述概念和机制,基本上就已经有了Flux的整个架构的模型。那么Action Creators又是什么呢?顾名思义,Action Creators即Action的创建者。它们负责去创建具体的Action。一个Action可以由一个界面操作产生,也可以由一个Ajax请求的返回结果产生。为了让这部分逻辑更加清晰,让View更少的去关心数据流的细节,于是有了Action Creators。例如,对于一个TodoItem组件,当用户点击其中的Checkbox时,会产生一个COMPLETE_TODO的Action,直观的看,完全可以在View的内部去实现Dispatcher.dispatch({type: ‘COMPLETE_TODO’, payload: {…}),而为了保持View的简单和直观,通常会在独立的Action Creators去封装这部分逻辑,例如:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<span class="token keyword">var</span> AppDispatcher <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../dispatcher/AppDispatcher'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> TodoConstants <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../constants/TodoConstants'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> TodoActions <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * @param {string} text */</span> create<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span> <span class="token punctuation">{</span> AppDispatcher<span class="token punctuation">.</span><span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> actionType<span class="token punctuation">:</span> TodoConstants<span class="token punctuation">.</span>TODO_CREATE<span class="token punctuation">,</span> text<span class="token punctuation">:</span> text <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment" spellcheck="true">/** * @param {string} id The ID of the ToDo item * @param {string} text */</span> updateText<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> text<span class="token punctuation">)</span> <span class="token punctuation">{</span> AppDispatcher<span class="token punctuation">.</span><span class="token function">dispatch</span><span class="token punctuation">(</span><span class="token punctuation">{</span> actionType<span class="token punctuation">:</span> TodoConstants<span class="token punctuation">.</span>TODO_UPDATE_TEXT<span class="token punctuation">,</span> id<span class="token punctuation">:</span> id<span class="token punctuation">,</span> text<span class="token punctuation">:</span> text <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> … <span class="token punctuation">}</span> |
这里的TodoActions就是一个Action Creator,它把具体分发Action的动作封装成具有语义的方法,供View去使用,那么在一个TodoItem的组件中,其界面JSX可能就是:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
… render<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> todo <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>todo<span class="token punctuation">;</span> <span class="token keyword">var</span> input<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>isEditing<span class="token punctuation">)</span> <span class="token punctuation">{</span> input <span class="token operator">=</span> <span class="token operator"><</span>TodoTextInput className<span class="token operator">=</span><span class="token string">"edit"</span> onSave<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onSave<span class="token punctuation">}</span> value<span class="token operator">=</span><span class="token punctuation">{</span>todo<span class="token punctuation">.</span>text<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>li className<span class="token operator">=</span><span class="token punctuation">{</span><span class="token function">classNames</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token string">'completed'</span><span class="token punctuation">:</span> todo<span class="token punctuation">.</span>complete<span class="token punctuation">,</span> <span class="token string">'editing'</span><span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>isEditing <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">}</span> key<span class="token operator">=</span><span class="token punctuation">{</span>todo<span class="token punctuation">.</span>id<span class="token punctuation">}</span><span class="token operator">></span> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"view"</span><span class="token operator">></span> <span class="token operator"><</span>input className<span class="token operator">=</span><span class="token string">"toggle"</span> type<span class="token operator">=</span><span class="token string">"checkbox"</span> checked<span class="token operator">=</span><span class="token punctuation">{</span>todo<span class="token punctuation">.</span>complete<span class="token punctuation">}</span> onChange<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onToggleComplete<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span>label onDoubleClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onDoubleClick<span class="token punctuation">}</span><span class="token operator">></span> <span class="token punctuation">{</span>todo<span class="token punctuation">.</span>text<span class="token punctuation">}</span> <span class="token operator"><</span><span class="token operator">/</span>label<span class="token operator">></span> <span class="token operator"><</span>button className<span class="token operator">=</span><span class="token string">"destroy"</span> onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_onDestroyClick<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">{</span>input<span class="token punctuation">}</span> <span class="token operator"><</span><span class="token operator">/</span>li<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> _onToggleComplete<span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> TodoActions<span class="token punctuation">.</span><span class="token function">toggleComplete</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>todo<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> … |
通过将Action的创建逻辑放到Action Creators,可以让View更加简单和纯粹,View不需要知道背后是否有Flux,而只需要知道调用某个方法来实现某个功能,从而让View的开发更加流畅和直观 。完整代码可以参考:https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/components/TodoItem.react.js 。 小结 本文介绍了Facebook提出的面向React的一种新的应用架构模式Flux,这种架构也已经在Facebook内部被广泛使用,其概念和原理虽然很简单直观,但是确实被证明有能力去组织一个完整的大型应用。然而Flux的中心Dispatcher的模式,以及Action作为全局可知的数据流的方式,仍然有一些争论。因此,社区中也是出现了非常多的类Flux实现,如今最成熟的莫过于Redux,其最大的特点在于Action能够分层次的去负责一个全局Store的不同部分,从而更容易去模块化应用状态的管理。理解了本文介绍的Flux概念,对于理解Redux也会有很大的帮助。 from:http://www.infoq.com/cn/articles/react-flux
View Details【1】js毫秒时间转换成日期时间 var oldTime = (new Date("2012/12/25 20:11:11")).getTime(); //得到毫秒数 //不是上面格式的时间需要转换 //starttime =’2012-12-25 20:17:24′; starttime = starttime.replace(new RegExp("-","gm"),"/"); var starttimeHaoMiao = (new Date(starttime)).getTime(); //得到毫秒数 【2】毫秒数转化为时间 var oldTime = (new Date("2012/12/25 20:11:11")).getTime(); //得到毫秒数 var newTime = new Date(oldTime); //就得到普通的时间了 参考:http://blog.csdn.net/qq435792305/article/details/7973751
View DetailsDate 对象 Date 对象用于处理日期和时间。 创建 Date 对象的语法:
|
1 |
var myDate=new Date() |
注释:Date 对象会自动把当前日期和时间保存为其初始值。 Date 对象属性 属性 描述 constructor 返回对创建此对象的 Date 函数的引用。 prototype 使您有能力向对象添加属性和方法。 Date 对象方法 方法 描述 Date() 返回当日的日期和时间。 getDate() 从 Date 对象返回一个月中的某一天 (1 ~ 31)。 getDay() 从 Date 对象返回一周中的某一天 (0 ~ 6)。 getMonth() 从 Date 对象返回月份 (0 ~ 11)。 getFullYear() 从 Date 对象以四位数字返回年份。 getYear() 请使用 getFullYear() 方法代替。 getHours() 返回 Date 对象的小时 (0 ~ 23)。 getMinutes() 返回 Date 对象的分钟 (0 ~ 59)。 getSeconds() 返回 Date 对象的秒数 (0 ~ 59)。 getMilliseconds() 返回 Date 对象的毫秒(0 ~ 999)。 getTime() 返回 1970 年 1 月 […]
View DetailsI was researching for a few feature rich UI frameworks based on React; that give the power of composability through React components that you can directly plug in into your React project Here is a compilation of a few ReactJS based UI frameworks (in no particular order), that I came across. Hope one of these choices will help you rapidly prototype your "ReactJS" ideas. Material UI Material-UI is a set of React components that implement Google’s Material Design . Out of the hundreds of UI frameworks out there, […]
View Details网上有很多文字作品写涉及在JS中呈现类似UrlEncode功能时都是自定义参数来呈现,其实JS中本身就有那样的参数。 参数parameter由于用类似URL的形式传过去 , 所以别直接就那样赋值 以下是对变量值的URL编码总结 : 意见用encodeURIComponent() , GET 和POST方法都能够发送过去 Java编程script中存在几种对URL字符串停止编码的窍门:escape(),encodeURI(),以及encodeURIComponent()。这几种编码所起的功能各不相同。 escape() 窍门: 采用ISO Latin字符集对指定的字符串停止编码。所有的空格符、标点符号、特殊字符以及更多有联系非ASCII字符都将被转化成%xx各式的字符编码(xx等于该字符在字符集表里面的编码的16进制数字)。比如,空格符对应的编码是%20。 不会被此窍门编码的字符: @ * / + encodeURI() 窍门: 把URI字符串采用UTF-8编码各式转化成escape各式的字符串。 不会被此窍门编码的字符:! @ # $& * ( ) = : / ; ? + ' encodeURIComponent() 窍门: 把URI字符串采用UTF-8编码各式转化成escape各式的字符串。与encodeURI()相比,那个窍门将对更多的字符停止编码,比如 / 等字符。所以假如字符串里面包含了URI的几个部份的话,别用那个窍门来停止编码,否则 / 字符被编码之后URL将呈现错误。 不会被此窍门编码的字符:! * ( ) ' 因此,对于汉文字符串来说,假如不期望把字符串编码各式转化成UTF-8各式的(比如原页面和目的页面的charset是一致的时候),只需求应用 escape。假如你的页面是GB2312或者更多有联系的编码,而接受参数parameter的页面是UTF-8编码的,就要采用encodeURI或者encodeURIComponent。 from:http://www.cnblogs.com/xiaofei59/archive/2011/09/13/2174498.html
View Details|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
//桌面提醒 function notify(title, content) { if(!title && !content){ title = "桌面提醒"; content = "您看到此条信息桌面提醒设置成功"; } var iconUrl = "/images/send_ok.png"; if (window.webkitNotifications) { //chrome老版本 if (window.webkitNotifications.checkPermission() == 0) { var notif = window.webkitNotifications.createNotification(iconUrl, title, content); notif.display = function() {} notif.onerror = function() {} notif.onclose = function() {} notif.onclick = function() {this.cancel();} notif.replaceId = 'Meteoric'; notif.show(); } else { window.webkitNotifications.requestPermission($jy.notify); } } else if("Notification" in window){ // 判断是否有权限 if (Notification.permission === "granted") { var notification = new Notification(title, { "icon": iconUrl, "body": content, }); } //如果没权限,则请求权限 else if (Notification.permission !== 'denied') { Notification.requestPermission(function(permission) { // Whatever the user answers, we make sure we store the // information if (!('permission' in Notification)) { Notification.permission = permission; } //如果接受请求 if (permission === "granted") { var notification = new Notification(title, { "icon": iconUrl, "body": content, }); } }); } } } |
from:http://www.cnblogs.com/foolin/p/3822294.html
View Details由于谷歌商店国内访问不了,即使访问得了,国内许多安卓机也没有google play的框架,还是安装不了google商店的应用。想用安卓机开发啊,找度娘,终于在csdn的下载上,找到了个可用的~ Apk下载 csdn:http://download.csdn.net/download/yyh352091626/9176197
View Details|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!doctype html> <html> <head> <meta charset="UTF-8"> <meta name="Generator" content="EditPlus®"> <meta name="Author" content=""> <meta name="Keywords" content=""> <meta name="Description" content=""> <title>Document</title> <style type="text/css"> @media screen and (orientation:portrait) { body{color:red} } @media screen and (orientation:landscape) { body{color:green;} } </style> </head> <body> 横屏竖屏测试。 </body> </html> <script type="text/javascript"> window.addEventListener('orientationchange', function(event){ if ( window.orientation == 180 || window.orientation==0 ) { alert("竖屏"); } if( window.orientation == 90 || window.orientation == -90 ) { alert("横屏"); } }); </script> |
View Details
近日刚做的一个功能,要在app里使用内嵌页面进行图像的上传。 从功能上看,原生的实现应该是最好的。毕竟页面上所有的东西都隔着一个浏览器,所有的实现都要依赖浏览器提供的接口,不同的浏览器对接口的实现又有差异……到最后又会陷入兼容性的大坑! 吐槽归吐槽,但是折腾的劲头不能丢! 使用input file[camera]属性调用相机 简直So easy!
|
1 2 |
<input type="file" accept="image/*;" capture="camera" > |
只需要这么一条简单的代码,在手机浏览器点击就可以打开相机了。 capture是什么?其实就是对打开方式的设置。
|
1 2 3 4 5 |
<!-- capture=camcorder,调用手机摄像功能 --> <input type="file" accept="video/*" capture="camcorder" > <!-- capture=microphone,调用手机录音功能 --> <input type="file" accept="audio/*" capture="microphone" > |
魅族MX5测试结果: 谷歌浏览器可以打开相机和摄像功能,其他方式均为相机、图库、文件管理器等混合选择项。 自带浏览器打开均为文件管理器。 由此说明此属性兼容性还是个问题。不过这并不能阻止我继续折腾下去! 图片压缩 在如今这个手机普遍千万像素的时代,一张照片动辄5M的大小。作为一个良心的开发者,我们是要为用户的流量负责的。 该怎么做?我也不知道。大家都在用canvas实现,我也就用了。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
document.getElementById('file').addEventListener('change', function() { var reader = new FileReader(); reader.onload = function (e) { compress(this.result); }; reader.readAsDataURL(this.files[0]); }, false); |
不管文件域是用何种方式打开的,都可以在 change 事件中获取到选择的文件或拍摄的照片。 创建一个FileReader对象,我们需要调用readAsDataURL把文件转换为base64图像编码,如data:image/jpeg;base64……这种格式。 onload是一个异步回调,当文件读取完执行该方法内代码。this.result记录读取结果,如果读取失败,该值为null。在这里进行图片压缩的具体操作。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
var compress = function (res) { var img = new Image(), maxH = 160; img.onload = function () { var cvs = document.createElement('canvas'), ctx = cvs.getContext('2d'); if(img.height > maxH) { img.width *= maxH / img.height; img.height = maxH; } cvs.width = img.width; cvs.height = img.height; ctx.clearRect(0, 0, cvs.width, cvs.height); ctx.drawImage(img, 0, 0, img.width, img.height); var dataUrl = cvs.toDataURL('image/jpeg', 0.6); // 上传略 } img.src = res; } |
创建一个Image对象,给src属性赋值为读取结果,同样在onload异步回调中编写处理图片的代码。 这里就要开始使用canvas进行图片压缩了。 首先是尺寸按比例缩放,然后把图片绘到画布上,最后调用toDataURL方法压缩图像质量。
|
1 2 |
context.toDataURL('MIME类型', 图像质量0-1); // 该方法返回base64图像编码 |
代码里省略了一些校监操作,如文件类型约束和文件大小判断(小于一定值可以不压缩)。 最后就是把数据发送到后端的操作,这里就不说了。 Html5调用摄像头 通过以上的代码已经可以实现调用手机相机拍照、压缩、上传这一整套流程了。 不过在折腾的过程中也发现了一种调用摄像头的方法。注意,是摄像头!使用input调用的是相机。其中的差别就是摄像头是只捕获画面,相机还包括原生的一些拍照、设置等控件。 通过对摄像头的调用可以做很多有趣的事,比如拍照美化、滤镜等。可以说实现一个第三方相机是没问题的。 之前下载过一款安卓相机APP,不到100K的大小,可以实现拍照的一些风格化,也许就是Html5实现的呢。 需要用到的是 getUserMedia API,具体的实现这里就不贴了。 from:http://www.imys.net/20150916/webapp-input-use-camera.html
View Details