最終日は Photo Album です。一覧のサムネイルを押下すると、サムネイルがその位置から拡大され、画像詳細を表示する UI です。
code: github / $ yarn 1225
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つのコンポーネントを利用しています。
- 一覧表示用サムネイル
- ズームアップサムネイル
- 拡大表示画像
いずれも画像パスは同じ。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} />
)