LoginSignup
1

More than 3 years have passed since last update.

posted at

updated at

Organization

Stickey Image Loader

スクロール位置に応じて画像を読み込み・解放する処理を、ここまでのスクロール Hooks テクニックで実装します。
code: github / $ yarn 1210

1210.jpg 1210-1.jpg

挙動の確認

画面表示領域に入ってから各画像はロード開始されますが、手元で確認するには直ぐにレスポンスが返ってくるため、あまり効果が分かりません。Chrome で低速環境デバッグを有効にしてから確認すると分かりやすいです。

image.png

memoize と render props

Hooks API はループや条件分岐では利用出来ない縛りがあります。この時、お馴染みの render props が役にたちます。

components/items.tsx
const View = (props: Props) => (
  <div className={props.className}>
    {Assets.map((asset, index) => (
      <Item
        key={index}
        imgPath={asset.imgPath}
        render={({
          imgSrc,
          loaded,
          loadCompleted,
          handleLoadCompleted
        }) => (
          <>
            <Photo
              imgSrc={imgSrc}
              loaded={loaded}
              loadCompleted={loadCompleted}
              onTransitionEnd={handleLoadCompleted}
            />
            <Description
              title={asset.title}
              body={asset.body}
            />
          </>
        )}
      />
    ))}
  </div>
)

こちらがループ処理にかけられる Item コンポーネントです。Custom Hooks を利用し、得られた state を memoize。状態が変化した時に限り rerender props します。

components/item/index.tsx
export default (props: Props) => {
  const {
    state,
    handleLoadStart,
    handleDispose,
    handleLoadCompleted
  } = useImageLoader({
    imgPath: props.imgPath
  })
  return useMemo(
    () => (
      <ScrollWrapper
        onEnter={handleLoadStart}
        onLeave={handleDispose}
      >
        {props.render({
          imgSrc: state.img.src,
          loaded: state.loaded,
          loadCompleted: state.loadCompleted,
          handleLoadCompleted
        })}
      </ScrollWrapper>
    ),
    [state]
  )
}

useImageLoader

画像の読み込み解放を行う Custom Hooks です。責務がはっきりしてるので、再利用性の高い Custom Hooks といえます。

components/item/useImageLoader.ts
const useImageLoader = (props: Props) => {
  const [state, update] = useState({
    img: new Image(),
    loaded: false,
    loadCompleted: false
  })
  const handleLoadStart = useCallback(
    () => {
      state.img.onload = () =>
        update(_state => ({ ..._state, loaded: true }))
      state.img.src = props.imgPath
    },
    [state.img]
  )
  const handleDispose = useCallback(() => {
    update(_state => ({
      ..._state,
      img: new Image(),
      loaded: false,
      loadCompleted: false
    }))
  }, [])
  const handleLoadCompleted = useCallback(() => {
    update(_state => ({
      ..._state,
      loadCompleted: true
    }))
  }, [])
  return {
    state,
    handleLoadStart,
    handleDispose,
    handleLoadCompleted
  }
}

最後に、ここから得られたハンドラを、スクロール処理を行う Hooks Component にバインドします。このコンポーネントは先日の投稿で利用しているものと殆ど同じです。毎度のものなので、本日は割愛します。

components/item/index.tsx
<ScrollWrapper
  onEnter={handleLoadStart}
  onLeave={handleDispose}
>
...
</ScrollWrapper>

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1