LoginSignup
1
0

More than 3 years have passed since last update.

react-three-fiberとreact-springでアニメーション無限ループのオンオフをする方法

Last updated at Posted at 2020-07-29

react-spring v8では素直に無限ループをする方法が用意されていない。(たぶん)
whileで無限ループを作ってしまうしかないようだ。
Best practice for infinite loops?

v9ではloop propが用意されてるらしいのでそちらを使うと良さそう。
How can one-way animation looping be implemented? #956

ちなみにv9.0.0-rc3を使おうとしたところ、変なところでハマってうまくいかなかった。
コンポーネントの再レンダーのタイミングでしかアニメーションが更新されないような現象。
内部的にはreact-springはちゃんと動いているけど、コンポーネントの再レンダーのトリガーにできてない感じの挙動。
これが解決できればv9系を使う方が幸せっぽいのでできた人は情報求む。

閑話休題。

一応while(true)で無限ループは作れるし動くんだけど、それを使ってるコンポーネントが再レンダーされるとブラウザがクラッシュする。
無限ループしてる関数がゾンビ的な挙動をしているのではないかと推測する。

再レンダー時にクラッシュするコード
const { opacity } = useSpring({
    from: { opacity: 0 },
    // フワッと現れて、その後おぼろげにフワフワする感じの不透明度アニメーション
    to: async (next, cancel) => {
        await next({ opacity: 1 })
        while (true) {
            await next({ opacity: 0.2 })
            await next({ opacity: 0.5 })
        }
    }
})

ちなみに今回はこのアニメーションのオンオフを親コンポーネントからコントロールしたい。
例えばactive:booleanみたいなpropで、trueならさっきのふわふわアニメーションを頭から再生して、falseなら{opacity:0}に落ち着くみたいな挙動をさせたい。

やりたいのはこんな感じ(まだ再レンダー時にクラッシュする)
const { active } = props
const { opacity } = useSpring({
    from: { opacity: 0 },
    to: active ?
        // active==trueならふわふわさせる
        async (next, cancel) => {
            await next({ opacity: 1 })
            // ※※※ここが大事※※※
            // activeの値がキャプチャされてしまうので、
            // あとでfalseになってもループを抜けられない
            while (active) {
                await next({ opacity: 0.2 })
                await next({ opacity: 0.5 })
            }
        } :
        // active==falseなら消える
        { opacity: 0 }
})

最終的にうまくいったコードはこちら。
方針としては、
1. useSpringに関数を渡すことで、toの再設定関数(この例ではsetAnimation)を取得する
2. activeを依存関係に持つuseEffectを使用することで、toの再設定をactiveの変更時に限定する
(v9系ではuseSpringに依存関係を持たせてよりスマートに書けるようになるっぽい)
3. whileでactiveRef=useRef(active)を見るように変更することで、activeの値の変更をキャッチできる
-> これにより、whileループがゾンビ化するのを防げる

const { active } = props
const activeRef = useRef(active)
// useSpringは関数を渡されると、SpringValueだけじゃなく更新関数やstop関数も返す
const [{ opacity }, setAnimation] = useSpring(() => ({
    // toの値はあとで設定するので初期値のみ指定
    opacity: 0
}))
useEffect(() => {
    // activeが変更されたことをwhileに通知する
    activeRef.current = active
    setAnimation({
        to: active ?
            // active==trueならふわふわさせる
            async (next, cancel) => {
                await next({ opacity: 1 })
                // activeRef.current==falseならループを抜ける
                while (activeRef.current) {
                    await next({ opacity: 0.2 })
                    await next({ opacity: 0.5 })
                }
            } :
            // active==falseなら消える
            { opacity: 0 }
    })
},
// useEffectはactiveが変わった時に呼ばれる
[active])

たぶん、タイミングによっては{ opacity: 0 }setAnimationした後にwhile内のawait next()が効いて{ opacity: 0.5 }で止まっちゃう現象が起こるような気もする。
ちゃんと対応するならPromiseをrejectさせるみたいなことをやると良いのだと思う。
今回はとりあえず動いてそうなので無指する。

1
0
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
1
0