ホームページ >ウェブフロントエンド >htmlチュートリアル >React アニメーションの練習_html/css_WEB-ITnose
世界で最も難しい知識は、人間を研究することです。アニメーションがユーザーのデバイスの速度を低下させるほどリソースを大量に消費しない限り、アニメーションはユーザー インターフェイス エクスペリエンスを大幅に向上させることができます。
ページ アニメーションは次のタイプに簡単に分けることができます:
1. ユーザー操作によって引き起こされるページ要素アニメーション
2. ユーザーの視覚的な待ち時間を短縮します。装飾的なアニメーション: ユーザーの注意をそらすことになるため、避けるようにしてください。
4. 広告アニメーション: 広告のコンバージョン率を向上させます。
5. プロット アニメーション: 主に SPA で使用されます。アニメーションの読み込みを例に挙げます。ユーザー エクスペリエンスを向上させ、ユーザーの定着率を高めるために、開発の観点から最初に思い浮かぶのは、フロントツーバックのパフォーマンスの最適化であり、それによって待ち時間を短縮します。ユーザーがページを開くためには、帯域幅を増やす、ページの http リクエストを減らす、データ キャッシュを使用する、データベースを最適化する、ロード バランシングを使用するなどを検討できます。ただし、ビジネスの制限とユーザーの複雑なエクスペリエンス環境により、常にいくつかのボトルネックに遭遇します。このとき、私たちがやるべきことは、ユーザーの視覚的な待ち時間をいかに減らすかです。たとえそれが回転菊であっても、盲目的に待っているとビジネスが失われます。率直に言って、セクシーな菊はデータベースを最適化するのと同じくらい役立つ場合があります。
2. アニメーション実装の原則
アニメーションを実装するとき、私は常に次の原則に従っています:
2。 3. 大きくてゴージャスなアニメーションは目的を持ったものである必要があります。
4. アニメーションの長さは短くする必要があります。
6.アニメーションは突然停止すべきではありません
これが当てはまるかどうか考えてみてください。
3. React アニメーション
(1) 実装方法
React でアニメーションを実装するには 2 つの方法があります:
1. CSS グラデーション グループ
(2) CSS グラデーション グループ
ReactCSStransitionGroup は、基本的な CSS アニメーションとトランジションを単純に実装するために、プラグイン クラス ReactTransitionGroup の基礎となる API に基づいてさらにカプセル化された高レベルの API です。
1. クイックスタート
簡単な画像カルーセルを例に挙げます:
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; var Carousel = React.createClass({ propTypes: { transitionName: React.PropTypes.string.isRequired, imageSrc: React.PropTypes.string.isRequired }, render: function() { return ( <divclassName='carousel'> <ReactCSSTransitionGrouptransitionName={this.props.transitionName} > <imgsrc={this.props.imageSrc} key={this.props.imageSrc} /> </ReactCSSTransitionGroup> </div> ); }});
透明度切り替えエフェクト:
.carousel1-enter { opacity: 0;}.carousel1-enter-active { opacity: 1; transition: opacity 300ms ease-in;}.carousel1-leave { opacity: 1;}.carousel1-leave-active { opacity: 0; transition: opacity 300ms ease-in;}
ディスプレイスメント切り替えエフェクト:
.carousel2-enter { left: 100%;}.carousel2-enter-active { left: 0; transition: left 300ms ease-in;}.carousel2-leave { left: 0;}.carousel2-leave-active { left: -100%; transition: left 300ms ease-in;}
私がtransitionName属性について言及していることに誰もが気づいています。実際、ReactCSStransitionGroup では 1 つの属性が完全に不足しているため、そのプロパティを以下に詳しく紹介します。
2. プロパティ
(1)、transitionName
transitionName-appear;transitionName-appear-active;transitionName-enter;transitionName-enter-active;transitionName-leave;transitionName-leave-active;
transitionName={ { enter: ‘enter’, enterActive: ‘enterActive’, leave: ‘leave’, leaveActive: ‘leaveActive’, appear: ‘appear’, appearActive: ‘appearActive’ } }
(2)、transitionAppear
{React.PropTypes.bool} {false}
機能: マウントアニメーションを初期化します。コンポーネントの初期マウント中に追加の移行フェーズを追加します。 通常、transitionAppear のデフォルト値は false であるため、マウントの初期化時に移行フェーズはありません。例:
render: function() { return ( <ReactCSSTransitionGrouptransitionName="example" transitionAppear={true} > <h1>FadingatInitialMount</h1> </ReactCSSTransitionGroup> ); }
(3)、transitionEnter
{React.PropTypes.bool} {true}
{React.PropTypes.bool} {true}
作用:用来禁止leave动画 ReactCSSTransitionGroup 会在移除你的DOM节点之前等待一个动画完成。你可以添加transitionLeave={false} 到ReactCSSTransitionGroup 来禁用这些动画。
{React.PropTypes.any} {‘span’}
作用:默认情况下 ReactTransitionGroup 渲染为一个 span。你可以通过提供一个 component prop 来改变这种行为. 组件不需要是一个DOM组件,它可以是任何你想要的React组件,甚至是你自己写的。
{ React.PropTypes.string }
作用:给当前的component设置样式类
例如:
<ReactTransitionGroupcomponent=“ul” className="example" > ... </ReactTransitionGroup>
当子级被声明式的从其中添加或移除(就像上面的例子)时,特殊的生命周期挂钩会在它们上面被调用。
对于被初始化挂载到 CSSTransitionGroup 的组件,它和 componentDidMount() 在相同时间被调用 。它将会阻塞其它动画发生,直到callback被调用。它只会在CSS TransitionGroup 初始化渲染时被调用。
在 传给componentWillAppear 的 回调 函数被调用后调用。
对于被添加到已存在的 CSSTransitionGroup 的组件,它和 componentDidUpdate() 在相同时间被调用 。它将会阻塞其它动画发生,直到callback被调用。它不会在 CSSTransitionGroup 初始化渲染时被调用。
在传给 componentWillEnter 的回调函数被调用之后调用。
在子级从 ReactCSSTransitionGroup 中移除时调用。虽然子级被移除了,ReactTransitionGroup 将会保持它在DOM中,直到callback被调用。
在willLeave callback 被调用的时候调用(与 componentWillUnmount 同一时间)。
以componentWillEnter为例,伪代码如下:
componentWillEnter (callback) { letel = ReactDOM.findDOMNode(this); el.classList.add(styles.enter); requestAnimationFrame(() => { el.classList.add(styles.active); }); el.addEventListener('transitionend', () => { callback && callback(); el.classList.remove(styles.enter); el.classList.remove(styles.active); });}
在 componentWillEnter 里给 Animation 组件添加了 styles.enter 样式类,然后在浏览器下一个 tick 加入 styles.active 样式类 – 这里使用了 requestAnimationFrame,也可以使用 setTimeout,另外还监听 ‘transitionend’ 事件,transitionend 事件发生时执行回调 callback 并移除 styles.enter 与 styles.active 两个样式类
①. 一定要为ReactCSSTransitionGroup的所有子级提供 key属性。即使只渲染一个项目。React靠key来决定哪一个子级进入,离开,或者停留。
②、动画持续时间需要被同时在CSS和渲染方法里被指定。这告诉React什么时候从元素中移除动画类,并且如果它正在离开,决定何时从DOM移除元素。
③、ReactCSSTransitionGroup必须已经挂载到了DOM才能工作。为了使过渡效果应用到子级上,ReactCSSTransitionGroup必须已经挂载到了DOM或者 prop transitionAppear 必须被设置为 true。ReactCSSTransitionGroup 不能随同新项目被挂载,而是新项目应该在它内部被挂载。
ReactCSSTransitionGroup的优势是非常明显的,简化代码、提高性能等,但是其劣势我们也需要了解,以在做实际项目时进行适当的取舍。
① 不兼容较老的、不支持CSS3的浏览器;
② 不支持为CSS属性之外的东西(比如滚动条位置或canvas绘画)添加动画;
③ 可控粒度不够细。CSS3动画只支持start、end、iteration三个事件,不支持对中间状态进行处理。
④ transitionEnd和animationEnd事件不稳定。
新增属性:
transitionAppearTimeouttransitionEnterTimeouttransitionLeaveTimeout
控制动画持续时间,解决animationend transitionend 事件不稳定、时有时没有的现象,v0.15版本将彻底放弃监听animationend transitionend 事件。
官方原话是: To improve reliability, CSSTransitionGroup will no longer listen to transition events. Instead, you should specify transition durations manually using props such as transitionEnterTimeout={500}.
原理上其实是简化了,还是以componentWillEnter为例,伪代码如下:
componentWillEnter (callback) { letel = ReactDOM.findDOMNode(this); el.classList.add(styles.enter); requestAnimationFrame(() => { el.classList.add(styles.active); }); setTimeout( () => { callback && callback(); el.classList.remove(styles.enter); el.classList.remove(styles.active); }, props.transitionEnterTimeout);}
所以我们的轮播图就要改为这样实现:
<ReactCSSTransitionGrouptransitionName={this.props.transitionName} transitionEnterTimeout={300} transitionLeaveTimeout={300} > <imgsrc={this.props.imageSrc} key={this.props.imageSrc} /></ReactCSSTransitionGroup>
深入了解了CSS渐变组,大家也看到了它并不是万能的,所以需要间隔动画来做辅助,或者说是第二选择。
间隔动画实现方式很简单,有两种:
1、 requestAnimationFrame
2、 setTimeout
requestAnimationFrame可以以最小的性能损耗实现最流畅的动画,它被调用的次数频繁度超出你想象。在requestAnimationFrame不支持或不可用的情况下,就要考虑降级到不那么智能的setTimeout了。
间隔动画在实现原理上其实很简单,就是周期性的触发组件的状态更新,通过在组件的render方法中加入这个状态值,组件能够在每次状态更新触发的重渲染中正确表示当前的动态阶段。
以实现元素右移100px为例,代码实现如下所示:
1、requestAnimationFrame实现
var Todo = React.createClass( getInitialState: function() { return { left: 0 }; }, componentWillUpdate: function() { requestAnimationFrame(this. resolveAnimationFrame); }, render: function() { return <divstyle={{left: this.state.left}}>This willanimate!</div>; }, resolveAnimationFrame: function() { if(this.state.left <= 100) { this.setState({ left: this.state.left + 1 }); } });
2、requestAnimationFrame实现
var Todo = React.createClass( getInitialState: function() { return { left: 0 }; }, componentWillUpdate: function() { setTimeout(this. resolveAnimationFrame, this.props.tick); }, render: function() { return <divstyle={{left: this.state.left}}>This willanimate!</div>; }, resolveAnimationFrame: function() { if(this.state.left <= 100) { this.setState({ left: this.state.left + 1 }); } });
是不是很简单呢?
大家一定会想,React也提供了我们可以直接操作DOM的接口,我还是不习惯React的写法,为什么不能像原生js那样实现动画效果呢?那么我可以明确的告诉你,React就是不允许你这么做,它就是要规避前端这种肆无忌惮的写法,规范你的代码,降低维护成本。
至于性能,这里顺便简单提一下React的渲染过程,大家可以体会下。
首次渲染时,从JSX渲染成真实DOM的大体过程如下:
1、parse过程将JSX解析成Virtual DOM,是一种抽象语法树(AST);
2、compile过程则将AST通过DOM API 编译成页面真实的DOM。
二次渲染过程如下:
1、每次生成的页面DOM渲染后,其对应的Virtual Dom也会缓存起来;
2、当JSX发生变化,,会首先根据新的JSX生成一个全新的Virtual Dom;
3、新的Virtual Dom生成后,会检测是否存在旧的Virtual Dom;
4、发现存在,则通过react diff算法比较新旧Virtual Dom之间的差异,得出一个从旧Virtual Dom转换到新Virtual Dom 的最少操作(minimum operating);
5、最后,页面旧的真实Dom,根据刚刚react diff算法得出的最少操作,通过Dom api进行节点的增、删、改,得出新的真实Dom;
大家一定在怀疑diff算法的性能,因为传统的用递归算法来比较两棵树的时间复杂度是O(n^3),真是烂到了极致,但是,React通过几个先验条件将diff的算法复杂度控制在了O(n)。下面讲一下这几个条件:
1、 只在同层级做比较
在React 的diff算法中,两个virtual dom树的比较只在同层级进行。这样,只需一遍,即可遍历整棵树。这样做,是忽略了节点的跨层移动,因为web中节点的跨层操作较少。同时我们在使用React时,也要尽量避免这样做。
示例如下:
算法计算得出的操作是:删除body的子节点p及其子节点,创建div的子节点p,创建p的子节点a。
通过react的diff算法,两个Virtual Dom 比较后,因移动节点不同级,因此不做移动操作,而是直接删除重建。
2、 基于组件比较
在React 的diff算法中,virtual dom树的比较只在同组件进行。对于不同组件,即使结构相似,也不进行比较,而是直接执行删除+重建操作。这样做,是强化组件的概念,因为正常情况下,不同组件的页面结构是不一样的。
示例如下:
算法计算得出的操作是:删除body的子节点div及其子节点,创建body子节点div及其子节点p和子节点input。
如使用传统的diff算法,会计算出只需删除div的子节点a,并创建div子节点input。
而采用react的diff算法,两个Virtual Dom 比较时,发现绿框内结构为不同的组件,则绿框内容不做比较,直接删除重建。
3、节点使用唯一属性key
在React 的diff算法中,virtual dom树的节点可以通过key标识其身份,提高节点同级同组移动时的性能。增加身份标识来作为节点是否需要修改的一个条件。
算法计算得出的操作只需要:移动div节点到最后即可。
若使用传统的diff算法,判断body第一个子节点,旧的为div,新的为p,节点不一样,则删除div节点,新增插入p节点。之后节点操作类似,因此总的需要进行三次节点删除和新增。
而采用react的diff算法,因为节点多了key来标识,两个Virtual Dom 比较时,发现level1下的三个节点其实是一样的(key=1、key=2、key=3)。
相信通过上面的介绍,大家对React有了更进一步的了解。
1. React を使用してアニメーション効果を実現する場合は、まず CSS グラデーション グループを検討してください。それが機能しない場合は、インターバル レンダリングの使用を検討してください。
2. カスタマイズする必要がある機能が多い場合は、React に付属の CSSTransitionGroup プラグインを使用しないことをお勧めします。たとえば、アニメーションの最後に onEnd コールバックを渡したいとします。React のソース コードを変更すると、CSSTransitionGroup は、transitionGroup に依存し、transitionGroup は他のプラグインとメソッドに依存します。変更するのは困難ですが、問題を修正するのは簡単です。私は一連の CSSTransitionGroup プラグインを自分で実装しました。これについては、今後さらに共有する予定です。
お読みいただきありがとうございます