LoginSignup
3
1

More than 5 years have passed since last update.

最終日は 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} />
)
3
1
0

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
  3. You can use dark theme
What you can do with signing up
3
1