Creating a Vinyl Record Animation in React Using Framer Motion Bring the charm of vinyl to your React projects with a sleek spinning record animation!
Last weekend, while scrolling through Twitter, I came across a video of a spinning movie CD. (Original post: https://x.com/matthischmid/status/1858196401311654116 ) Coincidentally, I was listening to Linkin Park’s latest album at the time. Linkin Park holds a special place in my heart—they were the first band I ever listened to in a foreign language, and I’ve been a fan since I was seven.
I still feel a pang of sadness whenever I think of Chester 💔, but I’m also grateful that Emily continues to carry the torch. Their music has always been a source of inspiration for me.
Anyway, back to the topic! Inspired by that CD animation, I decided to create a spinning vinyl record component in React using Framer Motion . Let’s build it together!
This will be a simple component, so we’ll only need to install one package.
npm i framer - motion
Create a new file in the components
folder and name it vinyl-record.tsx
.
Copy the code below into your new file.
"use client" ;
import { useState } from "react" ;
import { motion, AnimatePresence } from "framer-motion" ;
import Image from "next/image" ;
const DiscVariants = {
hidden: {
top: "-10%" ,
x: "-50%" ,
opacity: 0.9 ,
},
visible: {
top: "-50%" ,
x: "-50%" ,
opacity: 1 ,
},
};
const CoverVariants = {
initial: { scale: 1 },
hover: {
scale: 1.05 ,
transition: {
type: "spring" ,
stiffness: 300 ,
damping: 30 ,
},
},
};
export default function VinylRecord () {
const [ isHovered , setIsHovered ] = useState ( false );
return (
< div className = "flex items-center justify-center min-h-[80vh] bg-gray-100 p-4" >
< div className = "relative w-64 h-64" >
< AnimatePresence >
{ /* Vinyl Disc */ }
< motion.div
className = "absolute left-1/2 rounded-full z-0 w-60 h-60"
variants = {DiscVariants}
initial = "hidden"
animate = {isHovered ? "visible" : "hidden" }
transition = {{
type: "spring" ,
stiffness: 300 ,
damping: 30 ,
}}
style = {{
background:
"radial-gradient(circle at center, #0a0a0a 0%, #000000 80%)" ,
boxShadow: "inset 0 0 10px rgba(255, 255, 255, 0.5)" ,
}}
>
{ /* Grooves Pattern */ }
< div
className = "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-1/2 h-1/2 rounded-full"
style = {{
background:
"repeating-radial-gradient(circle at center, #333 0, #333 1px, transparent 1px, transparent 4px)" ,
}}
/>
{ /* Center Label */ }
< div className = "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-1/4 h-1/4 bg-gray-400 rounded-full flex items-center justify-center" >
< motion.div
className = "w-1/3 h-1/3 bg-white rounded-full"
animate = {{ rotate: isHovered ? 360 : 0 }}
transition = {{
duration: 3 ,
ease: "linear" ,
repeat: Infinity ,
}}
/>
</ div >
</ motion.div >
</ AnimatePresence >
{ /* Record Cover */ }
< motion.div
className = "relative w-full h-full rounded shadow-xl z-10 overflow-hidden"
variants = {CoverVariants}
initial = "initial"
whileHover = "hover"
onMouseEnter = {() => setIsHovered ( true )}
onMouseLeave = {() => setIsHovered ( false )}
>
< Image
src = "https://linkinpedia.com/w/images/thumb/f/fe/Album-From_Zero.png/800px-Album-From_Zero.png"
alt = "Album Cover"
fill
sizes = "(max-width: 256px) 100vw, 256px"
className = "object-cover"
priority
/>
</ motion.div >
</ div >
</ div >
);
}
That’s it! You can customize the size, animations, and image to match your preferences.