当ブログにアニメーション付きのハートのLikeボタンを追加しました。
累計をカウントしたり、誰が押したかをトラッキングする等はしていません。ただ押したら通知がいくという、筆者のモチベーションを上げるためだけのものです。
各記事の下部に配置しているため、よいと感じた場合にぜひ押してみてください👍
アニメーションの実装を簡単に説明します。React Native Skia、React Native Reanimatedで実装しています。
ボタンの形はシンプルなハートです。
Skiaでベジェ曲線を使って描画しています。3次ベジェ曲線については以前の記事react-native-skiaで三次ベジェ曲線を描くで解説しています。
Pathの子要素にShadow
を配置し、ドロップシャドウを追加しています。
const path = usePathValue(p => {
p.moveTo(props.x + centerX, centerY * 0.5 + props.y)
// left
p.cubicTo(
centerX - props.size * 0.1 + props.x,
centerY * 0.1 + props.y,
centerX - props.size * 0.5 + props.x,
centerY * 0.5 + props.y,
centerX + props.x,
centerY * 1.2 + props.y,
)
// right
p.cubicTo(
centerX + props.size * 0.5 + props.x,
centerY * 0.5 + props.y,
centerX + props.size * 0.1 + props.x,
centerY * 0.1 + props.y,
centerX + props.x,
centerY * 0.5 + props.y,
)
p.close()
})
<Path path={path} style="fill">
<Shadow dx={0} dy={3} blur={0} color="rgba(235, 150, 50, 0.3)" />
</Path>
ボタンを押し待ちの時に軽くアピールするために、拡大縮小を小刻みに繰り返すアニメーションを設定します。
withSpring
を使いscaleの値を変化させます。
const heartScale = useSharedValue(1)
heartScale.value = withSpring(
1.175,
{
mass: 0.3,
stiffness: 600,
damping: 2,
velocity: 0.3,
}
)
各パラメータの意味は以下です。
mass
: 質量。重いほど遅くなる。stiffness
: 硬さ。大きいほど速くなる。damping
: 減衰。大きいほど速くなる。velocity
: 速度。大きいほど速くなる。ボタンをプレスするとハートが少し小さくなり、離すとパルスした後にハートが消えていきます。
const heartScale = useSharedValue(1)
const heartOpacity = useSharedValue(1)
const handlePressIn = useCallback(() => {
heartScale.value = withTiming(0.9, { duration: 300 })
}, [heartScale])
const handlePressOut = useCallback(() => {
heartOpacity.value = withTiming(0, { duration: 2500 })
heartScale.value = withSpring(
1.3,
{
mass: 0.4,
stiffness: 700,
damping: 2.5,
velocity: 0,
}
)
}, [heartScale])
ハートが消えていくと同時に、パーティクル(小さな円)が散らばって消えます。
これは小さな円の<Particle />
コンポーネントを用意し、各Particleにアニメーション進捗のSharedValueを渡します。各Particleでは中心からの角度と距離を元に、アニメーション進捗に応じて位置を変化させます。
const particleMoveProgress = useSharedValue(0)
const particleGroups = Array.from({ length: 6 }).map((_, index) => {
const angle = (index * Math.PI * 2) / 6
const distance = 28
const particleAngleOffset = Math.PI / 15
const particles = [
{
angleRadian: angle - particleAngleOffset / 2,
distanceFromCenter: distance,
color: colors[index % colors.length],
},
{
angleRadian: angle + particleAngleOffset / 2,
distanceFromCenter: distance,
color: colors[(index + 1) % colors.length],
},
]
return { particles }
})
<Group>
{particleGroups.map((group, groupIndex) => (
<Group key={`group-${groupIndex}`}>
{group.particles.map((particle, particleIndex) => (
<Particle
key={`particle-${groupIndex}-${particleIndex}`}
color={particle.color}
centerOrigin={center}
initialPosition={particle}
progress={particleMoveProgress}
/>
))}
</Group>
))}
</Group>
これらを組み合わせた完成形がこの下に配置しているハートボタンです!
今回の全コードはこちらを参照してください。