0

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!

umar
  • 1

0 Answers0