[ React ] 使いまわせるTransition + Lazyloadなimageコンポーネント


![result.gif](https://qiita-image-store.s3.amazonaws.com/0/229570/1a31a066-68a9-4d10-4ed9-ddacd75b75d0.gif)

こんな感じで画像をフワッと表示させたい。
もちろんLazyload対応で。
モダンな雰囲気を出すには必須です。

色々と使いまわせる為メモ。

##ライブラリのインストール

使うライブラリはこの3つ。

- [react-lazyload](https://github.com/twobin/react-lazyload)
- [recompose](https://github.com/acdlite/recompose)
- [react-transition-group](https://github.com/reactjs/react-transition-group)

```
npm i -S react-transition-group recompose react-lazyload
```
##imageタグのケース
###コンポーネントの作成
コンポーネントはHoCsで作っていきます。

```js:LazyloadImage.js

import React from 'react';
import LazyLoad from 'react-lazyload';
import { compose, withStateHandlers, pure } from 'recompose';
import { Transition } from 'react-transition-group';

const LazyLoadImage = compose(
  withStateHandlers(
    {
      isLoaded: false,
    },
    {
      onLoad: () => () => ({ isLoaded: true }),
    },
  ),
  pure,
)(({
  children,
  height,
  isLoaded,
  onLoad,
}) => {
  const styles = {
    placeholder: {
      backgroundColor: '#F3F3F3',
      height,
    },
    unLoadOuter: {
      opacity: 0.05,
      backgroundColor: '#000',
      height,
    },
    loadedOuter: {
      opacity: 1,
      transition: 'opacity 1s ease-in', // transitionはご自由に
    },
  };
  const childStyle = isLoaded ? {} : { visibility: 'hidden' };
  return (
    <LazyLoad
      offset={300}
      once
      placeholder={<div style={styles.placeholder} />}
    >
      <Transition in={isLoaded} timeout={1000}>
        {
          () => (
            <div style={isLoaded ? styles.loadedOuter : styles.unLoadOuter}>
              {children(onLoad, childStyle)}
            </div>
          )
        }
      </Transition>
    </LazyLoad>
  );
});

export default LazyLoadImage;
```

###使用

呼び出し側では読み込んだ`LazyLoadImage`のpropsに画像の`height`(必要に応じて`width`も。その際は少し作り変えてください。)を指定します。
この`height`はplaceholderの表示に使用します。

```js:App.js
<LazyLoadBackgroundImage height={512}> //画像のheight
  {
    onLoad => (
      <img
        src="..."
        onLoad={onLoad}
      />
    )
  }
</LazyLoadBackgroundImage>
```

これで完成

#backgroundImageのケース
上記までは`img`タグで表示する想定で書いていましたが、backgroundImageで使用したい場合は以下のように書きます。

###コンポーネントの作成

```js:LazyLoadBackgroundImage.js
import React from 'react';
import PropTypes from 'prop-types';
import LazyLoad from 'react-lazyload';
import { compose, withStateHandlers, lifecycle, pure } from 'recompose';
import { Transition } from 'react-transition-group';

const LazyLoadInner = compose(
  withStateHandlers(
    {
      isLoaded: false,
    },
    {
      onLoad: () => () => ({ isLoaded: true }),
    },
  ),
  lifecycle({
    componentDidMount() {
      const image = new Image(); // eslint-disable-line
      image.onload = () => {
        this.props.onLoad();
      };
      image.src = this.props.imageUrl;
    },
  }),
  pure,
)(({
  children,
  imageUrl,
  isLoaded,
  styles,
}) => {
  const childStyle = isLoaded ? {} : { opacity: 0 };
  return (
    <Transition in={isLoaded} timeout={1000}>
      {
        () => (
          <div style={isLoaded ? styles.loadedOuter : styles.unLoadOuter}>
            {children(imageUrl, childStyle)}
          </div>
        )
      }
    </Transition>
  );
});

const LazyLoadBackgroundImage = (({
  children,
  imageUrl,
  height,
}) => {
  const styles = {
    placeholder: {
      backgroundColor: '#F3F3F3',
      height,
    },
    unLoadOuter: {
      opacity: 0.05,
      height,
      backgroundColor: '#000',
    },
    loadedOuter: {
      opacity: 1,
      transition: 'opacity 1s ease-in',
    },
  };
  return (
    <LazyLoad
      height={height}
      offset={300}
      once
      placeholder={<div style={styles.placeholder} />}
    >
      <LazyLoadInner
        imageUrl={imageUrl}
        styles={styles}
      >
        {children}
      </LazyLoadInner>
    </LazyLoad>
  );
});

export default LazyLoadBackgroundImage;
```

さっきより少し複雑になります。
`img`にあるonLoadがbackgroundだと使えない為、コンポーネントの内部で`new Image()`からonLoadを発行させる必要があります。
なので、
①Lazyloadが指定領域にスクロールされvisibleに
②`new Image()`でonLoadの発行。
③ロード後placeholderが外され、トランジションと共に画像表示
といった流れになります。

###使用
呼び出し側はbackground領域のheight(placeholderの使用)とimageのURL(uri)をpropsで渡します。

```js:App.js
<LazyLoadBackgroundImage
    height={360} // background領域のheight
    imageUrl="..." // backgroundImageのURL
  >
    {
      (imageUrl, style) => (
        <div
          className="post-image"
          style={{ backgroundImage: `url("${imageUrl}")`, ...style }}
        />
      )
    }
  </LazyLoadBackgroundImage>
```

##所感
もっとうまくかけるところがある気がする。。。