Open3
requestAnimationFrameのユースケース

setStateでアニメーションレンダリングする際、requestAnimationFrameでステート更新のコールバックを包むと、アニメーションで事故が起きない

requestAnimationFrame() は、描画処理をアニメーションの適切なタイミングで実施してくれる。
例えば、以下のようなトーストを表示するアニメーション処理があったとする。
const Toast: React.FC<{ onClose: () => void }> = ({ onClose }) => {
const [closing, setClosing] = useState(false)
return (
<Container closing={closing}>
<Message>{'Not Archived'}</Message>
<UndoButton>Undo</UndoButton>
<CloseButton
onClick={() => {
setClosing(true)
setTimeout(() => {
onClose()
}, 200)
}}
>
✖️
</CloseButton>
</Container>
)
}
const ToastContainer = () => {
const [show, setShow] = useState(false)
return (
<div>
<ToggleButton onClick={() => setShow(true)}>Show Toast</ToggleButton>
{show && <Toast onClose={() => setShow(false)} />}
</div>
)
}
これは通常動作してくれるが、稀に以下のような、表示時のアニメーションが残ってしまうケースがある。
コンポーネントの表示に間髪入れず非表示処理を行うと、描画処理が前後するからか(?)表示アニメーションが最後に残る形になる。
これは、requestAnimationFrameを用いて防ぐことができる。
const Toast: React.FC<{ onClose: () => void }> = ({ onClose }) => {
const [closing, setClosing] = useState(false)
return (
<Container closing={closing}>
<Message>{'Not Archived'}</Message>
<UndoButton>Undo</UndoButton>
<CloseButton
onClick={() => {
- // setClosing(false)
+ requestAnimationFrame(() => setClosing(true))
setTimeout(() => {
onClose()
}, 200)
}}
>
✖️
</CloseButton>
</Container>
)
}
const ToastContainer = () => {
const [show, setShow] = useState(false)
return (
<div>
<ToggleButton onClick={() => setShow(true)}>Show Toast</ToggleButton>
{show && <Toast onClose={() => setShow(false)} />}
</div>
)
}

MDNには、
ブラウザーにアニメーションを行いたいことを知らせ、指定した関数を呼び出して次の再描画の前にアニメーションを更新することを要求します。
とある。
これだけだとしっくりこないが、要は「最適なタイミングでアニメーションを実行する(ブラウザの表示を邪魔しない)」ためのものであるらしい。
今回のように、非表示アニメーションの実行中に表示アニメーションが表示されてしまっていたのは、適切なフレームレートでの実行が保証されていなかったことにより発生していた現象かもしれない。