Docs
Go back
Component by
OV
Oblivion0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
TSX
rolling-number-slot
"use client"
import React, { useState } from "react"
import { motion } from "framer-motion"
import { cn } from "@/lib/utils"
export const componentMeta = {
name: "Rolling Number Slot",
description: "A fun slot-machine effect for numbers, ensuring playful updates for dynamic metrics like pricing or random rolls.",
props: {
className: { type: "string", description: "Additional CSS classes", default: '""' }
}
}
// We double the numbers array 0-9 to allow looping for a true "spin" aesthetic.
const DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const HEIGHT = 72
function DigitColumn({ value, isSpinning, delayIndex }: { value: string, isSpinning: boolean, delayIndex: number }) {
const targetNumber = parseInt(value, 10)
// When spinning, we animate way down the list, and then snap back.
const spinTarget = -(HEIGHT * 10)
const finalTarget = -(targetNumber * HEIGHT)
return (
<div className="relative w-14 h-[72px] bg-zinc-950 border border-zinc-800 rounded-xl overflow-hidden flex items-center justify-center font-mono text-[42px] font-bold text-white shadow-[inset_0_4px_10px_rgba(0,0,0,0.8)]">
{/* Top Shadow Array */}
<div className="absolute inset-x-0 top-0 h-1/3 bg-gradient-to-b from-black/80 via-black/40 to-transparent z-10" />
<motion.div
animate={
isSpinning
? { y: [0, spinTarget] }
: { y: finalTarget }
}
transition={
isSpinning
? {
y: {
repeat: Infinity,
duration: 0.6,
ease: "linear",
// offset spin columns for that sequential feeling
delay: delayIndex * 0.05
}
}
: {
type: "spring",
damping: 15,
stiffness: 80,
mass: 1
}
}
className="absolute top-0 left-0 w-full flex flex-col"
>
{DIGITS.map((num, i) => (
<div key={i} className="h-[72px] w-full flex justify-center text-center items-center shadow-sm">
<span className="drop-shadow-[0_2px_2px_rgba(255,255,255,0.2)] bg-clip-text text-transparent bg-gradient-to-b from-white to-zinc-400">
{num}
</span>
</div>
))}
</motion.div>
{/* Bottom Shadow Array */}
<div className="absolute inset-x-0 bottom-0 h-1/3 bg-gradient-to-t from-black/80 via-black/40 to-transparent z-10" />
</div>
)
}
export default function RollingNumberSlot({ className }: { className?: string }) {
const [val, setVal] = useState(777)
const [spinning, setSpinning] = useState(false)
const handleSpinX = () => {
setSpinning(true)
setTimeout(() => {
// Random 3 digit number
setVal(Math.floor(100 + Math.random() * 900))
setSpinning(false)
}, 1500)
}
const strVal = val.toString().padStart(3, '0')
return (
<div className={cn("flex flex-col items-center justify-center p-12 gap-10", className)}>
{/* The Frame */}
<div className="flex items-center gap-2 bg-zinc-900 p-4 rounded-3xl shadow-[0_20px_50px_rgba(0,0,0,0.5),inset_0_2px_0_rgba(255,255,255,0.1)] border border-zinc-800">
{strVal.split("").map((char, i) => (
<DigitColumn key={i} value={char} isSpinning={spinning} delayIndex={i} />
))}
</div>
{/* The Lever/Button */}
<button
onClick={handleSpinX}
disabled={spinning}
className="relative group border-none outline-none"
>
{/* Shadow layer for 3D effect */}
<div className="absolute inset-0 bg-red-900 rounded-full translate-y-2 group-active:translate-y-0.5 transition-transform duration-75" />
{/* Top button layer */}
<div className="relative px-10 py-3 bg-red-600 border border-red-500 rounded-full text-white font-black tracking-[0.2em] transform group-active:translate-y-1.5 transition-transform duration-75 flex items-center gap-2 shadow-[inset_0_2px_4px_rgba(255,255,255,0.4)]">
<span>{spinning ? "ROLLING..." : "SPIN IT"}</span>
</div>
</button>
</div>
)
}"use client"
import React, { useState } from "react"
import { motion } from "framer-motion"
import { cn } from "@/lib/utils"
export const componentMeta = {
name: "Rolling Number Slot",
description: "A fun slot-machine effect for numbers, ensuring playful updates for dynamic metrics like pricing or random rolls.",
props: {
className: { type: "string", description: "Additional CSS classes", default: '""' }
}
}
// We double the numbers array 0-9 to allow looping for a true "spin" aesthetic.
const DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
const HEIGHT = 72
function DigitColumn({ value, isSpinning, delayIndex }: { value: string, isSpinning: boolean, delayIndex: number }) {
const targetNumber = parseInt(value, 10)
// When spinning, we animate way down the list, and then snap back.
const spinTarget = -(HEIGHT * 10)
const finalTarget = -(targetNumber * HEIGHT)
return (
<div className="relative w-14 h-[72px] bg-zinc-950 border border-zinc-800 rounded-xl overflow-hidden flex items-center justify-center font-mono text-[42px] font-bold text-white shadow-[inset_0_4px_10px_rgba(0,0,0,0.8)]">
{/* Top Shadow Array */}
<div className="absolute inset-x-0 top-0 h-1/3 bg-gradient-to-b from-black/80 via-black/40 to-transparent z-10" />
<motion.div
animate={
isSpinning
? { y: [0, spinTarget] }
: { y: finalTarget }
}
transition={
isSpinning
? {
y: {
repeat: Infinity,
duration: 0.6,
ease: "linear",
// offset spin columns for that sequential feeling
delay: delayIndex * 0.05
}
}
: {
type: "spring",
damping: 15,
stiffness: 80,
mass: 1
}
}
className="absolute top-0 left-0 w-full flex flex-col"
>
{DIGITS.map((num, i) => (
<div key={i} className="h-[72px] w-full flex justify-center text-center items-center shadow-sm">
<span className="drop-shadow-[0_2px_2px_rgba(255,255,255,0.2)] bg-clip-text text-transparent bg-gradient-to-b from-white to-zinc-400">
{num}
</span>
</div>
))}
</motion.div>
{/* Bottom Shadow Array */}
<div className="absolute inset-x-0 bottom-0 h-1/3 bg-gradient-to-t from-black/80 via-black/40 to-transparent z-10" />
</div>
)
}
export default function RollingNumberSlot({ className }: { className?: string }) {
const [val, setVal] = useState(777)
const [spinning, setSpinning] = useState(false)
const handleSpinX = () => {
setSpinning(true)
setTimeout(() => {
// Random 3 digit number
setVal(Math.floor(100 + Math.random() * 900))
setSpinning(false)
}, 1500)
}
const strVal = val.toString().padStart(3, '0')
return (
<div className={cn("flex flex-col items-center justify-center p-12 gap-10", className)}>
{/* The Frame */}
<div className="flex items-center gap-2 bg-zinc-900 p-4 rounded-3xl shadow-[0_20px_50px_rgba(0,0,0,0.5),inset_0_2px_0_rgba(255,255,255,0.1)] border border-zinc-800">
{strVal.split("").map((char, i) => (
<DigitColumn key={i} value={char} isSpinning={spinning} delayIndex={i} />
))}
</div>
{/* The Lever/Button */}
<button
onClick={handleSpinX}
disabled={spinning}
className="relative group border-none outline-none"
>
{/* Shadow layer for 3D effect */}
<div className="absolute inset-0 bg-red-900 rounded-full translate-y-2 group-active:translate-y-0.5 transition-transform duration-75" />
{/* Top button layer */}
<div className="relative px-10 py-3 bg-red-600 border border-red-500 rounded-full text-white font-black tracking-[0.2em] transform group-active:translate-y-1.5 transition-transform duration-75 flex items-center gap-2 shadow-[inset_0_2px_4px_rgba(255,255,255,0.4)]">
<span>{spinning ? "ROLLING..." : "SPIN IT"}</span>
</div>
</button>
</div>
)
}Save to favoritesCopy to Figma
React
Rolling Number Slot
A fun slot-machine effect for numbers, ensuring playful updates for dynamic metrics like pricing or random rolls.
Installation
$ npx @cosmoo/oblivion add rolling-number-slotDependencies
npm install framer-motionUsage Example
tsx
import RollingNumberSlot from "@/components/oblivion/rolling-number-slot"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<RollingNumberSlot />
</div>
)
}import RollingNumberSlot from "@/components/oblivion/rolling-number-slot"
export default function Demo() {
return (
<div className="p-10 flex justify-center">
<RollingNumberSlot />
</div>
)
}