Help us improve
Share bugs, ideas, or general feedback.
Animates SVG paths with Framer Motion pathLength and CSS stroke-dasharray, morphs shapes, and creates vector effects for icons, logos, and data visualizations in React/TSX files.
npx claudepluginhub markus41/claude --plugin react-animation-studioHow this skill is triggered — by the user, by Claude, or both
Slash command
/react-animation-studio:svg-animationsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Expert knowledge for SVG path animations, morphing shapes, and vector graphics effects that create stunning visual experiences with resolution-independent graphics.
Implements SVG icon, logo, illustration, and path stroke animations using CSS, Framer Motion, or GSAP. Covers motion accessibility with prefers-reduced-motion and fallback behavior.
Generates professional Lottie animations from static SVGs, replacing After Effects for motion graphics. Supports bezier curves, shape modifiers, character rigs, and frame-by-frame animation.
Animates DOM elements, CSS properties, SVGs, and JavaScript objects using Anime.js timelines, stagger effects, path morphing, and keyframe sequences for complex web animations.
Share bugs, ideas, or general feedback.
Expert knowledge for SVG path animations, morphing shapes, and vector graphics effects that create stunning visual experiences with resolution-independent graphics.
Activate this skill when:
**/*.svg files**/*.tsx containing SVG elements**/icons/*.tsx with animated icons**/components/**/Animated*.tsx with SVGpathLength, stroke-dasharrayimport { motion } from 'framer-motion';
function DrawPath() {
return (
<svg viewBox="0 0 100 100" className="w-24 h-24">
<motion.path
d="M10 50 Q 50 10, 90 50 T 90 90"
fill="transparent"
stroke="#3B82F6"
strokeWidth="4"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 2, ease: 'easeInOut' }}
/>
</svg>
);
}
.svg-path {
stroke-dasharray: 1000; /* Total path length */
stroke-dashoffset: 1000; /* Start hidden */
animation: draw 2s ease-in-out forwards;
}
@keyframes draw {
to {
stroke-dashoffset: 0;
}
}
// Get actual path length for accurate animation
useEffect(() => {
const path = document.querySelector('.animated-path') as SVGPathElement;
if (path) {
const length = path.getTotalLength();
console.log('Path length:', length);
}
}, []);
import { motion } from 'framer-motion';
function AnimatedCheck({ isVisible }: { isVisible: boolean }) {
return (
<svg viewBox="0 0 24 24" className="w-6 h-6">
{/* Circle background */}
<motion.circle
cx="12"
cy="12"
r="10"
fill="#22C55E"
initial={{ scale: 0 }}
animate={{ scale: isVisible ? 1 : 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
/>
{/* Checkmark path */}
<motion.path
d="M6 12l4 4 8-8"
fill="transparent"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
initial={{ pathLength: 0 }}
animate={{ pathLength: isVisible ? 1 : 0 }}
transition={{ delay: 0.2, duration: 0.3, ease: 'easeOut' }}
/>
</svg>
);
}
import { motion } from 'framer-motion';
function MenuIcon({ isOpen }: { isOpen: boolean }) {
const variant = isOpen ? 'open' : 'closed';
const topLineVariants = {
closed: { rotate: 0, y: 0 },
open: { rotate: 45, y: 8 },
};
const middleLineVariants = {
closed: { opacity: 1 },
open: { opacity: 0 },
};
const bottomLineVariants = {
closed: { rotate: 0, y: 0 },
open: { rotate: -45, y: -8 },
};
return (
<svg viewBox="0 0 24 24" className="w-6 h-6">
<motion.line
x1="4" y1="6" x2="20" y2="6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
variants={topLineVariants}
animate={variant}
transition={{ duration: 0.3 }}
style={{ transformOrigin: 'center' }}
/>
<motion.line
x1="4" y1="12" x2="20" y2="12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
variants={middleLineVariants}
animate={variant}
transition={{ duration: 0.2 }}
/>
<motion.line
x1="4" y1="18" x2="20" y2="18"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
variants={bottomLineVariants}
animate={variant}
transition={{ duration: 0.3 }}
style={{ transformOrigin: 'center' }}
/>
</svg>
);
}
import { motion } from 'framer-motion';
function Spinner() {
return (
<svg viewBox="0 0 50 50" className="w-12 h-12">
{/* Background circle */}
<circle
cx="25"
cy="25"
r="20"
fill="none"
stroke="#E5E7EB"
strokeWidth="4"
/>
{/* Animated arc */}
<motion.circle
cx="25"
cy="25"
r="20"
fill="none"
stroke="#3B82F6"
strokeWidth="4"
strokeLinecap="round"
initial={{ pathLength: 0, rotate: 0 }}
animate={{
pathLength: [0, 0.5, 0],
rotate: 360,
}}
transition={{
pathLength: {
duration: 1.5,
repeat: Infinity,
ease: 'easeInOut',
},
rotate: {
duration: 1.5,
repeat: Infinity,
ease: 'linear',
},
}}
style={{ transformOrigin: 'center' }}
/>
</svg>
);
}
import { motion, useAnimation } from 'framer-motion';
const shapes = {
circle: 'M50,10 A40,40 0 1,1 50,90 A40,40 0 1,1 50,10',
square: 'M10,10 L90,10 L90,90 L10,90 Z',
triangle: 'M50,10 L90,90 L10,90 Z',
};
function MorphingShape() {
const [currentShape, setCurrentShape] = useState<keyof typeof shapes>('circle');
return (
<svg viewBox="0 0 100 100" className="w-32 h-32">
<motion.path
fill="#3B82F6"
animate={{ d: shapes[currentShape] }}
transition={{ duration: 0.5, ease: 'easeInOut' }}
/>
</svg>
);
}
import gsap from 'gsap';
import { MorphSVGPlugin } from 'gsap/MorphSVGPlugin';
gsap.registerPlugin(MorphSVGPlugin);
function GSAPMorph() {
useEffect(() => {
gsap.to('#shape', {
duration: 1,
morphSVG: '#targetShape',
ease: 'power2.inOut',
});
}, []);
return (
<svg>
<path id="shape" d="M10,10 L90,10 L90,90 L10,90 Z" />
<path id="targetShape" d="M50,10 A40,40 0 1,1 50,90 A40,40 0 1,1 50,10" style={{ visibility: 'hidden' }} />
</svg>
);
}
function AnimatedLogo() {
const controls = useAnimation();
const logoVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2,
},
},
};
const pathVariants = {
hidden: { pathLength: 0, opacity: 0 },
visible: {
pathLength: 1,
opacity: 1,
transition: { duration: 1, ease: 'easeInOut' },
},
};
const fillVariants = {
hidden: { fillOpacity: 0 },
visible: {
fillOpacity: 1,
transition: { delay: 0.5, duration: 0.5 },
},
};
return (
<motion.svg
viewBox="0 0 200 60"
className="w-48"
variants={logoVariants}
initial="hidden"
animate="visible"
>
{/* Letter outlines */}
<motion.path
d="M10,50 L10,10 L40,10 L40,30 L20,30 L20,50"
fill="none"
stroke="#3B82F6"
strokeWidth="2"
variants={pathVariants}
/>
{/* Fill after stroke */}
<motion.path
d="M10,50 L10,10 L40,10 L40,30 L20,30 L20,50"
fill="#3B82F6"
variants={fillVariants}
/>
</motion.svg>
);
}
function AnimatedBarChart({ data }: { data: number[] }) {
const maxValue = Math.max(...data);
return (
<svg viewBox="0 0 300 200" className="w-full h-48">
{data.map((value, index) => {
const barHeight = (value / maxValue) * 160;
const barWidth = 40;
const gap = 20;
const x = index * (barWidth + gap) + 20;
const y = 180 - barHeight;
return (
<motion.rect
key={index}
x={x}
y={180}
width={barWidth}
height={0}
fill="#3B82F6"
rx="4"
initial={{ height: 0, y: 180 }}
animate={{ height: barHeight, y }}
transition={{
duration: 0.8,
delay: index * 0.1,
ease: [0.25, 0.1, 0.25, 1],
}}
/>
);
})}
</svg>
);
}
function CircleProgress({ progress }: { progress: number }) {
const radius = 40;
const circumference = 2 * Math.PI * radius;
return (
<svg viewBox="0 0 100 100" className="w-24 h-24 -rotate-90">
{/* Background circle */}
<circle
cx="50"
cy="50"
r={radius}
fill="none"
stroke="#E5E7EB"
strokeWidth="8"
/>
{/* Progress circle */}
<motion.circle
cx="50"
cy="50"
r={radius}
fill="none"
stroke="#3B82F6"
strokeWidth="8"
strokeLinecap="round"
initial={{ strokeDasharray: circumference, strokeDashoffset: circumference }}
animate={{ strokeDashoffset: circumference * (1 - progress) }}
transition={{ duration: 1, ease: 'easeOut' }}
/>
{/* Percentage text */}
<motion.text
x="50"
y="50"
textAnchor="middle"
dominantBaseline="middle"
className="text-2xl font-bold fill-current rotate-90"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
{Math.round(progress * 100)}%
</motion.text>
</svg>
);
}
will-change: transform for GPU accelerationpathLength property for easy animations<g> for batch transformsCreated by Brookside BI as part of React Animation Studio