React Nativeでコインの裏表を回転(3D)させるアニメーションを実装します。react-native-reanimatedを利用します。
まず表裏の画像をそれぞれ用意します。画像でなくともコンポーネントで表現してもOKです。
まずこの画像の一つを回転させてみます。React Nativeではスタイルのtransform
でrotateY
を指定することで、縦軸を中心とした3D回転を行うことができます。
reanimatedのSharedValueを用意しrotateY
の値を進捗に応じて動的に変更します。
const progress = useSharedValue(0)
const animatedStyle = useAnimatedStyle(
() => ({
transform: [{ rotateY: `${progress.value}deg` }],
}),
[progress],
)
useEffect(() => {
progress.value = withRepeat(withTiming(360, { duration: 3000 }), -1, false)
}, [])
<Animated.Image
source={...}
style={[styles.image, animatedStyle]}
/>
表裏が自然に回転しているように見せるには回転角度に応じて表裏の表示非表示を切り替える必要があります。0から90度までは表面、90度から270度までは裏面、270度から360度までは表面を表示する必要があります。
以下のスライダーで角度を変更してみるとわかりやすいです。
角度によって表示非表示を切り替えるにはopacity
を利用します。特定の角度のときは1で表示し、それ以外のときは0で非表示にします。また、裏面は表面に対して角度を-180度する必要があります。
const image1AnimatedStyle = useAnimatedStyle(
() => ({
opacity: progress.value <= 90 || progress.value >= 270 ? 1 : 0,
transform: [{ rotateY: `${progress.value}deg` }],
}),
[progress],
)
const image2AnimatedStyle = useAnimatedStyle(
() => ({
opacity: progress.value > 90 && progress.value < 270 ? 1 : 0,
transform: [{ rotateY: `${progress.value - 180}deg` }],
}),
[progress],
)
あとは180度回転した後ディレイを入れるようにし、リピートさせれば完成です。
全体のコードは以下です。
import { useEffect } from "react"
import { StyleSheet, View } from "react-native"
import Animated, {
useAnimatedStyle,
useSharedValue,
withDelay,
withRepeat,
withSequence,
withTiming,
} from "react-native-reanimated"
export function CoinRotateAnimation() {
const progress = useSharedValue(0)
const image1AnimatedStyle = useAnimatedStyle(
() => ({
opacity: progress.value <= 90 || progress.value >= 270 ? 1 : 0,
transform: [{ rotateY: `${progress.value}deg` }],
}),
[progress],
)
const image2AnimatedStyle = useAnimatedStyle(
() => ({
opacity: progress.value > 90 && progress.value < 270 ? 1 : 0,
transform: [{ rotateY: `${progress.value - 180}deg` }],
}),
[progress],
)
useEffect(() => {
progress.value = withRepeat(
withSequence(
withDelay(400, withTiming(180, { duration: 700 })),
withDelay(400, withTiming(360, { duration: 700 })),
),
-1,
true,
)
}, [])
return (
<View style={styles.container}>
<Animated.Image
source={{
uri: "/posts/2022-10-30-coin-rotate-animation.mdx/coinHead.png",
}}
resizeMode="contain"
style={[styles.image, image1AnimatedStyle]}
/>
<Animated.Image
source={{
uri: "/posts/2022-10-30-coin-rotate-animation.mdx/coinTail.png",
}}
resizeMode="contain"
style={[styles.image, image2AnimatedStyle]}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
width: 160,
height: 160,
justifyContent: "center",
alignItems: "center",
},
image: {
position: "absolute",
width: 120,
height: 120,
},
})
今回のすべてのコードはこちらから確認できます。