ReactのアニメーションにReact Poseが便利

Poseというライブラリがシンプルに書けて便利だったので、いくつか実装例を紹介します。また最後の方にstyled-componentsと組み合わせた時の体験もよかったので、その方法についても書きます。

ちなみにReact v16.3以上 がpeer dependencyとしてrequiredされているので残念ながら様々な事情により古いバージョンにバインドされている方は使えなさそうです。

ちなみに記事の内容はほぼドキュメントなぞってるだけなのでよければ本家も。
[Pose Documentation](https://popmotion.io/pose/learn/install/)

## 基本的なトランジション

それではまずは簡単なトランジションの書き方から。まずは設定を書きます。実際にトランジションさせるときは、この`visible`と`hidden`という名前を切り替えて行います。

```js
import React from 'react';
import posed from 'react-pose';

const props = {
  visible: { opacity: 1 },
  hidden: { opacity: 0 }
}
```

そして`posed`という関数でそのトランジションの設定を持ったコンポーネントを作成します。

```js
const Box = posed.div(props)
```

それではこのコンポーネントのトランジションを1秒ごとに切り替えてみましょう。

```js
export default class Example extends React.Component {
  state = { isVisible: false };

  componentDidMount() {
    setInterval(() => {
      this.setState({ isVisible: !this.state.isVisible });
    }, 1000);
  }

  return (
    <Box className="box" pose={this.state.isVisible ? 'visible' : 'hidden'} />
  );
}
```

ちなみにCSSはこんな感じでただのトマト色の四角い箱を描きます。

```css
.box {
  display: block;
  height: 200px;
  width: 200px;
  background-color: tomato;
}
```


できました。

![pose basic.gif](https://qiita-image-store.s3.amazonaws.com/0/150569/d4960dd2-4d5f-02a7-ebc6-84b52e1cdfd7.gif)


他にも出てくる時にx座標をずらして右からシュッと出したりみたいなのも簡単にできます。

```js
const props = {
  visible: { 
    opacity: 1,
    x: 0
  },
  hidden: { 
    opacity: 0,
    x: '100%'
  }
}
```

![pose basic2.gif](https://qiita-image-store.s3.amazonaws.com/0/150569/8d035178-a528-71e6-c545-c49d01edcbba.gif)

### `transition` プロパティの設定
CSSで書くような`transition` プロパティも指定することもできます。トランジションの長さだったりeaseを指定したい場合はこれを用います。

```js
const props = {
  visible: { 
    opacity: 1,
    transition: {
      ease: 'easeOut',
      duration: 300
    }
  },
  hidden: { 
    opacity: 0
  }
}
```

書き味はcssでアニメーション書く時とあまり変わらないので馴染みやすいと思います。

## 動的にアニメーションのプロパティを変更する
上述の`props`というオブジェクトに書いていた設定の値には関数を入れることができ、その関数はアニメーション実行時に評価されるため動的に値を変えることができます。またpropsを用いてコンポーネントからアニメーションの設定に関わる値を入れることができるので、そのやり方も合わせて見てみましょう。

まず普通のReactコンポーネント同様にpropsを書きます。`offset`というプロパティを追加しました。

```jsx

render() {
  return (
    <Box
      className="box"
      pose={this.state.isVisible ? 'visible' : 'hidden'}
      offset={1000}
    />
  );
}
```

次に設定の方で、propsから値を受け取りたいところに関数を書きます。関数を書くとアニメーションが実行される際に`props`が引数に入ってくるので、それを用いて値をセットしています。

```js
const props = {
  visible: {
    opacity: 1,
    x: 0
  },
  hidden: {
    opacity: 0,
    x: ({ offset }) => offset
  }
};
```

これで動かしてみます。

![pose basic3.gif](https://qiita-image-store.s3.amazonaws.com/0/150569/f0ca8f18-90ef-8758-192a-1e219536a9fe.gif)

ちゃんとpropsに指定した1000pxが渡ってビュンビュンしてるのが見て取れますね。

## Listのアニメーション
リストで子要素ごとに間隔空けてアニメーションしたいこと、あると思います。
delayChildren(親要素がアニメーションしてから子要素がアニメーションを開始するまでの時間)やstaggerChildren(子要素間のアニメーションのdelay)などのプロパティがあるので、それらを指定することによって簡単に実現できます。

まずは設定を書きます。

```js
const sidebarProps = {
  open: {
    x: '0%',
    delayChildren: 300,
    staggerChildren: 60
  },
  closed: {
    delay: 500,
    staggerChildren: 20,
    x: '-100%'
  }
};

const itemProps = {
  open: { opacity: 1, y: 0 },
  closed: { opacity: 0, y: 20 }
};
```

それを使ったコンポーネントを使ってあとは先ほどと同じ要領で`pose`を切り替えます。

```jsx

const SidePanel = posed.ul(sidebarProps);
const Item = posed.li(itemProps);

export class PoseList extends React.Component {
  state = { isOpen: false };

  componentDidMount() {
    setTimeout(this.toggle, 1000);
  }

  toggle = () => this.setState({ isOpen: !this.state.isOpen });

  render() {
    return (
      <div>
        <button onClick={this.toggle}>押す</button>
        <SidePanel
          onClick={this.toggle}
          className="sidebar"
          pose={this.state.isOpen ? 'open' : 'closed'}
        >
          <Item className="item" />
          <Item className="item" />
          <Item className="item" />
          <Item className="item" />
          <Item className="item" />
          <Item className="item" />
        </SidePanel>]
      </div>
    );
  }
}
```

![pose list.gif](https://qiita-image-store.s3.amazonaws.com/0/150569/6c1da4fa-daad-9b81-e308-b639229c6c5d.gif)

簡単ですね。ありがたや。

## Drag

ドラッグもオプションを追加するだけで実現できます。先ほどのBoxに`draggable` というプロパティを足してあげましょう。あとビュンビュン動かれるとドラッグしづらいので`setInterval`してる部分をコメントアウトします。

```js
const props = {
  draggable: true
};
```

これだけで好き放題ドラッグさせることが可能になりました。
![pose basic４.gif](https://qiita-image-store.s3.amazonaws.com/0/150569/cb339625-7326-da76-aee4-bd8cda5acb46.gif)

方向をx方向だけ(もしくはy方向だけ)に制限することや、動かせる幅を制限したりもできます。

```js
const props = {
  draggable: 'x',
  dragBounds: { left: -100, right: 100 }
};
```

x 軸に左右100pxだけドラッグ可能にした図↓

![pose basic5.gif](https://qiita-image-store.s3.amazonaws.com/0/150569/5bc22a90-ddab-8516-c784-b984dcccf18b.gif)

## styled-componentsと組み合わせる
最後に、Pose本体の使い方からは話がそれるんですが、styled-componentsとPoseで作ったコンポーネントを組み合わせてみようと思います。組み合わせると言っても特に難しいことは何もなく、やることは単なるコンポーネントのコンポジションです。

試しに上からニュッと出てくるドロップダウン作ってみました。

```jsx
import React from 'react';
import styled from 'styled-components';
import posed from 'react-pose';

const props = {
  visible: {
    opacity: 1,
    originY: 0,
    scaleY: 1
  },
  hidden: {
    opacity: 0,
    scaleY: 0
  }
};

const Dropdown = posed.ul(props);
const StyledDropdown = styled(Dropdown)`
  display: 'block';
  position: absolute;
  top: 20px;
  width: 250px;
  background-color: #fff;
  border-radius: 4px;
  box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.24);
  padding-left: 0;
`;

const Item = styled.li`
  list-style: none;
  border: none;
  border-bottom: 1px solid rgba(0, 0, 0, 0.12);
  font-size: 14px;
  color: #666;
  padding: 5px 10px;
  cursor: pointer;
  &:hover {
    background-color: rgba(0, 0, 0, 0.06);
  }
`;

export default class Example extends React.Component {
  state = { isVisible: false };

  toggle = () => {
    this.setState({ isVisible: !this.state.isVisible });
  };

  render() {
    return (
      <div>
        <button onClick={this.toggle}>押す</button>
        <StyledDropdown pose={this.state.isVisible ? 'visible' : 'hidden'}>
          <Item>エビデイ</Item>
          <Item>エビナイ</Item>
          <Item>トラックメイカー</Item>
        </StyledDropdown>
      </div>
    );
  }
}
```
![pose dropdown.gif](https://qiita-image-store.s3.amazonaws.com/0/150569/c3f9426a-3c45-082c-bdc5-3dc4c1884c74.gif)

styled-componentsでのアニメーションの書き味が別段悪いわけではないですが、Poseと組み合わせることによってより強力になるなと感じました。

感想
-----
いくつか例を書いてみましたが、この記事で触れたのは本当にベーシックな部分だけなのでご興味あれば本家のサイトを見てご自分で色々遊んで見てください！
[Pose](https://popmotion.io/pose/)

オサレなLPとかでやるようなゴリゴリのアニメーションが書けるかはわかりませんが、普通のウェブアプリでやるようなアニメーション・トランジションは全然シンプルに書けるなと思いました。