Oblivion
Oblivion
Docs
Go back
Component by
OV
Oblivion
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
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>
  )
}
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-slot
Dependenciesnpm install framer-motion

Usage Example

tsx
import RollingNumberSlot from "@/components/oblivion/rolling-number-slot"

export default function Demo() {
  return (
    <div className="p-10 flex justify-center">
      <RollingNumberSlot />
    </div>
  )
}