LoginSignup
1

More than 3 years have passed since last update.

posted at

updated at

Organization

Photo Album

最終日は Photo Album です。一覧のサムネイルを押下すると、サムネイルがその位置から拡大され、画像詳細を表示する UI です。

code: github / $ yarn 1225

1225.jpg 1225-1.jpg 1225-2.jpg

usePhotoDetail

Custom Hooks 内訳です。いつもの様に、State・Option・MemoizedStyle・handler で構成しています。

components/usePhotoDetail.ts
const usePhotoDetail = (props: Props) => {
  const [state, update] = useState<State>(defaultState())
  const options = useMemo(...)
  const bgStyle = useMemo(...)
  const photoStyle = useMemo(...)
  const handleTouchMove = useCallback(...)
  const handleOpen = useCallback(...)
  const handleClose = useCallback(...)
  return {
    isOpen: state.isOpen,
    isOpened: state.isOpened,
    bgStyle,
    photoStyle,
    handleOpen,
    handleClose
  }
}

サムネイル押下時の処理が混み入っています。押下サムネイルからズームアップする様に、時間差で状態を更新し CSS アニメーションを発火します。

components/usePhotoDetail.ts
const handleOpen = useCallback(
  (e: MouseEvent<HTMLElement>) => {
    // 押下された要素矩形を取得
    const rect = ...
    // アニメーション要素矩形初期値をセット
    update(...)
    // CSS アニメーション発火
    setTimeout(() => {
      update(_state => ({...}))
    }, 16) 
    // 詳細 View 表示状態にセット
    setTimeout(() => {
      update(_state => ({...}))
    }, options.transitionDuration)
  },
  [state.bgRect, state.photoRect, options]
)

Memoized Component

画像ズームアップの挙動に 3つのコンポーネントを利用しています。

  1. 一覧表示用サムネイル
  2. ズームアップサムネイル
  3. 拡大表示画像

いずれも画像パスは同じ。iOS safari では、画像キャッシュが残っていてもコンポーネントマウント時に再描画がされ、ちらつきが発生します。そのため、useMemo で MemoizedComponent を保持し、再利用することでこれを回避することが出来ます。

components/index.tsx
const View = (props: Props) => {
  const {
    isOpen,
    isOpened,
    bgStyle,
    photoStyle,
    handleOpen,
    handleClose
  } = usePhotoDetail({ transitionDuration })
  const photoComponent = useMemo(
    () => <Photo imgPath={props.imgPath} />,
    [props.imgPath]
  )
  return (
    <div className={props.className}>
      <div className="photo" onClick={handleOpen}>
        {photoComponent}
      </div>
      {isOpen && (
        <Detail
          title={props.title}
          body={props.body}
          bgStyle={bgStyle}
          photoStyle={photoStyle}
          photoComponent={photoComponent}
          isOpened={isOpened}
          transitionDuration={transitionDuration}
          handleClose={handleClose}
        />
      )}
    </div>
  )
}

Photo コンポーネントはこんなに薄いです。分割してるのは、上記理由の通りです。

comonents/item/photo.tsx
const View = (props: Props) => (
  <p className={props.className} />
)

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