React 動畫

動畫

React 提供了一個 ReactTransitionGroup 的組件元件來作為一個底層的動畫 API,以及另一個 ReactCSSTransitionGroup 來方便實作基本的 CSS 動畫。

高度抽象化的 API(High-level API) 與 底層的 API(Low-level API) 是什麼?
High-level API 允許開發者操作WSDL,一般來說一個 WDSL 編譯器全部的 SOAP 界面來產生物件,而開發者需要做的就只是直接呼叫物件中的 method,簡單的來說你不需要直接操作 SOAP 的結構。而 Low-level API 則允許開發者直接去動 SOAP 結構,而不是呼叫物件中的方法
更多關於 High-level 與 Low-level 定義

高度抽象化的 API: ReactCSSTransitionGroup

ReactCSSTransitionGroup 建構在 ReactTransitionGroup 之上,當 React 元件掛載或卸載到 DOM 物件時,您可以透過 ReactCSSTransitionGroup 可以簡單加入 CSS 過場特效與動畫。
這個靈感來自于 ng-animate

入門

ReactCSSTransitionGroup 是一個連接到 ReactTransitions 的介面。這個單純的元素需要把你想要做動畫效果的元件全部包起來。下面的範例是我們要讓列表的項目淡入淡出:

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
/** @jsx React.DOM */

var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

var TodoList = React.createClass({
getInitialState: function() {
return {items: ['hello', 'world', 'click', 'me']};
},
handleAdd: function() {
var newItems =
this.state.items.concat([prompt('Enter some text')]);
this.setState({items: newItems});
},
handleRemove: function(i) {
var newItems = this.state.items;
newItems.splice(i, 1);
this.setState({items: newItems});
},
render: function() {
var items = this.state.items.map(function(item, i) {
return (
<div key={item} onClick={this.handleRemove.bind(this, i)}>
{item}
</div>
);
}.bind(this));
return (
<div>
<button onClick={this.handleAdd}>Add Item</button>
<ReactCSSTransitionGroup transitionName="example">
{items}
</ReactCSSTransitionGroup>
</div>
);
}
});

在這個元件中,當你把一個 item 加入 ReactCSSTransitionGroup ,它將會取得 example-enter 的 CSS 屬性,而當你再次點擊這個項目時會取得 example-enter-active 樣式。
而這個慣例是根據在 transitionName 的名稱所組成的。
現在,您可以使用這些 class 樣式去觸發 CSS 的動畫或者過場的效果。舉例來說讓我們加入下面的 CSS

1
2
3
4
5
6
7
8
.example-enter {
opacity: 0.01;
transition: opacity .5s ease-in;
}

.example-enter.example-enter-active {
opacity: 1;
}

你可能注意到了,當您要移除某個項目的時候, ReactCSSTransitionGroup 卻把它保留在 DOM 裏面。
同時如果你用的是未經壓縮的 react-with-addons 版本,就會看到 console 警告你,React 期望取得一個 CSS 效果。
ReactCSSTransitionGroup 會保留您的 DOM 元素直到動畫執行完畢

試著加入下面這段 CSS

1
2
3
4
5
6
7
8
example-leave {
opacity: 1;
transition: opacity .5s ease-in;
}

.example-leave.example-leave-active {
opacity: 0.01;
}

動畫群組被需要被掛載才能執行

為了套用這些過場特效到子元素中,ReactCSSTransitionGroup 必須要先被掛載到 DOM 中。下面是一個錯誤的示範,因為
ReactCSSTransitionGroup 是隨著新的項目一起被掛載而無法生效,如果您把 ReactCSSTransitionGroup 包覆在子元素的外層則會出現奇怪的效果。
比對上面一開始的範例就會明白了。

這小節要說明的觀念是: ReactCSSTransitionGroup 運作的方式是先安裝到 DOM 在賦予子元件這些效果。

有無 items 的動畫行為

雖然上面的範例我們在 ReactCSSTransitionGroup 中渲染了列表,不過 ReactCSSTransitionGroup 的是允許沒有子元件的。
意味著您可以移除到完全沒有項目,即渲染的 items 是空的。
這個部分我們理解到 ReactCSSTransitionGroup 是針對單一元素在掛載或者卸載的時候套用動畫。同時也可以用在新元素取代舊元素的時候
其行為為兩個元素同時播放動畫,舊的元素會先保留 DOM 等其動畫播放完畢。
舉例來說,我們可以實作一個簡單圖片輪播的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** @jsx React.DOM */

var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

var ImageCarousel = React.createClass({
propTypes: {
imageSrc: React.PropTypes.string.isRequired
},
render: function() {
return (
<div>
<ReactCSSTransitionGroup transitionName="carousel">
<img src={this.props.imageSrc} key={this.props.imageSrc} />
</ReactCSSTransitionGroup>
</div>
);
}
});

注意: 您必須替每一個在 ReactCSSTransitionGroup 中的子元素設定 key 的屬性,即使只有一個項目。這是 React 偵測判斷子元素狀態的方式(entered, left, stayed)。

這邊提供一段展示影片,即使我們只透過 this.state 去更新圖片的 src ,ReactCSSTransitionGroup 還是會再產生一個節點。

官方提供的範例只是概念上的說明,上面展示影片的範例程式碼如下:

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
/** @jsx React.DOM */
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

var ImageCarousel = React.createClass({
propTypes: {
imageSrc: React.PropTypes.string.isRequired
},
getInitialState: function () {
return {imageSrc: this.props.imageSrc}
},
handleChangeImage: function () {
console.log('changed');
this.setState({imageSrc: 'dist/images/1.jpg'});
},
render: function () {
return (
<div>
<ReactCSSTransitionGroup transitionName="carousel">
<img src={this.state.imageSrc} key={this.state.imageSrc} onClick={this.handleChangeImage}/>
</ReactCSSTransitionGroup>
</div>
);
}
});

React.renderComponent(
<ImageCarousel imageSrc='dist/images/0.jpg' />,
document.getElementById('example')
);

關閉動畫

如果您想,您也可以取消 enterleave 的動畫。有時候你的需求是只要物件出現時的動畫,但是最一開始的範例有提到如果沒有實作移除的動畫, ReactCSSTransitionGroup 會卡著 DOM 物件。
您可以透過在 ReactCSSTransitionGroup 加入 transitionEnter={false}transitionLeave={false} props 已關閉動畫效果。

1
2
3
<ReactCSSTransitionGroup transitionName='example' transitionLeave={false}>
{items}
</ReactCSSTransitionGroup>

注意: 使用 ReactCSSTransitionGroup 時,裡面的元件並沒辦法在過場或動畫結束後收到任何通知,也不能在動畫時期執行更多複雜的邏輯。
如果您需要控制更多細節,您可以使用底層的 API ReactTransitionGroup API ,它一樣是透過提供 hooks 的方式讓您可以自訂效果。

底層 API: ReactTransitionGroup

ReactTransitionGroup 是動畫的基礎。當子元素被加入或移除(如上面範例),會在一個特有的生命週期去呼叫它的 hooks。

  • componentWillEnter(callback)
    當元件被加入一個已存在的 TransitionGroup 這個事件被呼叫,而觸發時間跟 componentDidMount() 時一樣。
    它會中斷其他的動畫直到 callback 被執行。另外是他不會在初始化的時候被呼叫。

  • componentDidEnter()
    當上面的 callback 執行完畢之後接著會執行這個事件。

  • componentWillLeave(callback)
    當子元件已經被從 ReactTransitionGroup 移除的時候會執行這個事件,雖然子元件已經被移除了,但是 ReactTransitionGroup 還是會保留 DOM 直到 callback 被呼叫。

  • componentDidLeave()
    一樣的當 willLeave callback 被呼叫(時間點跟 componentWillUnmount 一樣)。

渲染一個不同元件

預設 ReactTransitionGroup 會輸出一個 span

不過您是可以使用 component 屬性修改這個預設的行為

1
2
3
<ReactTransitionGroup component={React.DOM.ul}>
...
</ReactTransitionGroup>

所有的 DOM 元件都在 React.DOM. 底下,然後 component 並不需要一定是 DOM 元件。
這可以是任何您想要的 React 元件甚至是您開發的。

作者

andyyou(YOU,ZONGYAN)

發表於

2014-09-13

更新於

2023-12-05

許可協議