I'm currently facing an issue while implementing SVG animations using Framer Motion in my NextJS project. I'm trying to animate certain paths within an SVG element when they come into view (viewport). However, the SVG animations don't seem to be working as expected.
I've tried several approaches based on the Framer Motion documentation and other sources, but I'm still not able to get it working properly. Below is a snippet of the code I'm using:
"use client"
import React, { useEffect, useRef, useState } from "react"
import { motion } from "framer-motion"
import { useInView } from "react-intersection-observer"
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"
interface StatisticsProps {
totalCities: number
totalSchools: number
totalStudents: number
lastUpdated: string
}
const StatisticsSection: React.FC<StatisticsProps> = ({
totalCities,
totalSchools,
totalStudents,
lastUpdated,
}) => {
const [isStatsVisible, setIsStatsVisible] = useState(false)
const [isSvgAnimated, setIsSvgAnimated] = useState(false)
const [counter, setCounter] = useState({
cities: 0,
schools: 0,
students: 0,
})
const [statsRef, inView] = useInView({
triggerOnce: true,
threshold: 0.5,
})
useEffect(() => {
if (inView) {
setIsStatsVisible(true)
setIsSvgAnimated(true)
} else {
setIsSvgAnimated(false)
}
}, [inView])
useEffect(() => {
if (isStatsVisible) {
const interval = setInterval(() => {
setCounter((prevCounter) => {
const updatedCounter = {
cities:
prevCounter.cities < totalCities
? prevCounter.cities + 1
: prevCounter.cities,
schools:
prevCounter.schools < totalSchools
? prevCounter.schools + 1
: prevCounter.schools,
students:
prevCounter.students < totalStudents
? prevCounter.students + 1
: prevCounter.students,
}
return updatedCounter
})
}, 30)
return () => clearInterval(interval)
}
}, [isStatsVisible, totalCities, totalSchools, totalStudents])
return (
<section
id="statistik"
ref={statsRef}
className="container flex flex-col gap-6 py-8 md:max-w-[64rem] md:py-12 lg:py-24"
>
<div className="mx-auto flex w-full flex-col gap-4 md:max-w-[58rem]">
<h2 className="text-3xl font-bold leading-[1.1] sm:text-3xl md:text-5xl">
Statistik
</h2>
<p className="max-w-[85%] leading-normal text-muted-foreground sm:text-lg sm:leading-7">
Kami bangga dengan kemajuan yang telah kami capai. Berikut beberapa
data statistik yang merefleksikan dedikasi kami dalam meningkatkan
layanan{" "}
<span className="animate-text font-bold bg-gradient-to-r from-[#7928ca] via-purple-500 to-[#ff0080] bg-clip-text text-transparent">
Genilogi
</span>
.
</p>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-bold">Kota/Kabupaten</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-7 w-7 text-blue-500"
>
<motion.path
initial={{ pathLength: 0 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M21.54 15H17a2 2 0 0 0-2 2v4.54"
/>
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M7 3.34V5a3 3 0 0 0 3 3v0a2 2 0 0 1 2 2v0c0 1.1.9 2 2 2v0a2 2 0 0 0 2-2v0c0-1.1.9-2 2-2h3.17"
/>
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M11 21.95V18a2 2 0 0 0-2-2v0a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05"
/>
<circle
cx="12"
cy="12"
r="10"
fill="none"
stroke="currentColor"
strokeWidth="2"
/>
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{" "}
<motion.span
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
className="text-4xl font-semibold text-blue-500"
>
{counter.cities}
</motion.span>
</div>
<p className="text-xs text-muted-foreground">Dijangkau</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-bold">Sekolah</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-7 w-7 text-green-500"
>
<circle cx="12" cy="10" r="1" />
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M22 20V8h-4l-6-4-6 4H2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2Z"
/>
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M6 17v.01"
/>
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M6 13v.01"
/>
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M18 17v.01"
/>
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M18 13v.01"
/>
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M14 22v-5a2 2 0 0 0-2-2v0a2 2 0 0 0-2 2v5"
/>
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{" "}
<motion.span
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }}
className="text-4xl font-semibold text-green-500"
>
{counter.schools}
</motion.span>
</div>
<p className="text-xs text-muted-foreground">Telah melaksanakan</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-bold">Siswa</CardTitle>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-4xl font-semibold text-red-500"
>
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"
/>
<circle cx="9" cy="7" r="4" />
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M22 21v-2a4 4 0 0 0-3-3.87"
/>
<motion.path
initial={{ pathLength: 0, pathOffset: 1 }}
animate={{ pathLength: 1, pathOffset: 0 }}
transition={{ duration: 1 }}
d="M16 3.13a4 4 0 0 1 0 7.75"
/>
</svg>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{" "}
<motion.span
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.8 }}
className="text-4xl font-semibold text-red-500"
>
{counter.students}
</motion.span>
</div>
<p className="text-xs text-muted-foreground">Berkembang</p>
</CardContent>
</Card>
</div>
</section>
)
}
export default function SectionStatistics() {
const totalCities = 100
const totalSchools = 10
const totalStudents = 10
const lastUpdated = "28 Agustus 2023"
return (
<StatisticsSection
totalCities={totalCities}
totalSchools={totalSchools}
totalStudents={totalStudents}
lastUpdated={lastUpdated}
/>
)
}
I've attempted separating the path animations into separate variables, and setting initial values for animation properties. However, the animation is still not functioning as intended.
Can anyone help me identify this issue? Are there any further guidelines on how to properly animate SVG paths using Framer Motion? I would greatly appreciate any assistance.
Thank you!