Animated Clock
A real-time clock with spring-animated digit transitions and 3D tilt effects.
Overview
A live clock component that displays the current time with smooth, spring-based digit transitions. Features mouse-tracking 3D tilt effects and glassmorphic styling for a premium appearance.
Features
- Real-time updates every second
- Spring physics for digit changes
- Mouse-tracking 3D perspective tilt
- 12-hour or 24-hour format support
- Blur and scale entrance animations
Preview
Installation
Method 1: CLI (Recommended)
Install via the ZenBlocks CLI to automatically handle dependencies:
npx shadcn@latest add https://zenblocks-three.vercel.app/r/animated-clock.jsonMethod 2: Manual
-
Install Dependencies
npm install framer-motion -
Copy the Source Code
Copy the code below into
components/zenblocks/animated-clock.tsx.Click to expand source
"use client"; import React, { useEffect, useState, useRef } from "react"; import { motion, AnimatePresence, useMotionValue, useSpring, useTransform } from "framer-motion"; import { cn } from "@/lib/utils"; interface DigitProps { value: string | number; className?: string; } const Digit = ({ value, className }: DigitProps) => { return ( <div className={cn("relative h-[1.2em] w-[0.6em] overflow-hidden flex items-center justify-center font-mono text-4xl md:text-6xl font-black tracking-tighter", className)}> <AnimatePresence mode="popLayout"> <motion.span key={value} initial={{ y: "100%", opacity: 0, filter: "blur(10px)", scale: 0.8 }} animate={{ y: "0%", opacity: 1, filter: "blur(0px)", scale: 1 }} exit={{ y: "-100%", opacity: 0, filter: "blur(10px)", scale: 0.8 }} transition={{ type: "spring", stiffness: 400, damping: 30, mass: 0.5, }} className="absolute inset-0 flex items-center justify-center" > {value} </motion.span> </AnimatePresence> </div> ); }; const Separator = ({ className }: { className?: string }) => ( <motion.div animate={{ opacity: [0.3, 1, 0.3] }} transition={{ duration: 1, repeat: Infinity, ease: "easeInOut" }} className={cn("text-4xl md:text-6xl font-bold px-1", className)} > : </motion.div> ); export interface AnimatedClockProps { className?: string; /** * If true, displays time in 24-hour format (HH:MM:SS). * If false, displays in 12-hour format. * @default true */ use24HourFormat?: boolean; } export const AnimatedClock = ({ className, use24HourFormat = true }: AnimatedClockProps) => { const [time, setTime] = useState(new Date()); const [mounted, setMounted] = useState(false); const containerRef = useRef<HTMLDivElement>(null); // 3D Motion Values const x = useMotionValue(0); const y = useMotionValue(0); // Smooth springs for tilt const rotateX = useSpring(useTransform(y, [-0.5, 0.5], [15, -15]), { stiffness: 100, damping: 20 }); const rotateY = useSpring(useTransform(x, [-0.5, 0.5], [-15, 15]), { stiffness: 100, damping: 20 }); useEffect(() => { setMounted(true); const timer = setInterval(() => { setTime(new Date()); }, 1000); return () => clearInterval(timer); }, []); const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => { if (!containerRef.current) return; const rect = containerRef.current.getBoundingClientRect(); // Normalize mouse position for 3D tilt (-0.5 to 0.5) const relativeX = (event.clientX - rect.left) / rect.width; const relativeY = (event.clientY - rect.top) / rect.height; x.set(relativeX - 0.5); y.set(relativeY - 0.5); }; const handleMouseLeave = () => { x.set(0); y.set(0); }; if (!mounted) { return ( <div className={cn( "group relative flex items-center justify-center gap-1 p-12 rounded-[3.5rem] bg-white/40 dark:bg-zinc-950/20 backdrop-blur-2xl border border-white/50 dark:border-white/5 shadow-2xl transition-all duration-500 w-[350px] h-[150px]", className )} /> ); } let hours = time.getHours(); if (!use24HourFormat) { hours = hours % 12 || 12; // Convert to 12h format } const hoursStr = hours.toString().padStart(2, "0"); const minutesStr = time.getMinutes().toString().padStart(2, "0"); const secondsStr = time.getSeconds().toString().padStart(2, "0"); return ( <motion.div ref={containerRef} onMouseMove={handleMouseMove} onMouseLeave={handleMouseLeave} style={{ rotateX, rotateY, transformStyle: "preserve-3d", }} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} className={cn( "group relative flex items-center justify-center gap-1 p-12 rounded-[3.5rem] bg-white/40 dark:bg-zinc-950/20 backdrop-blur-2xl border border-white/50 dark:border-white/5 shadow-2xl transition-all duration-500 w-fit cursor-default", className )} > <div className="flex items-center" style={{ transform: "translateZ(50px)" }}> <Digit value={hoursStr[0]} className="text-zinc-950 dark:text-white" /> <Digit value={hoursStr[1]} className="text-zinc-950 dark:text-white" /> </div> <Separator className="text-zinc-400 dark:text-zinc-600" /> <div className="flex items-center" style={{ transform: "translateZ(50px)" }}> <Digit value={minutesStr[0]} className="text-zinc-950 dark:text-white" /> <Digit value={minutesStr[1]} className="text-zinc-950 dark:text-white" /> </div> <Separator className="text-zinc-400 dark:text-zinc-600" /> <div className="flex items-center" style={{ transform: "translateZ(50px)" }}> <Digit value={secondsStr[0]} className="text-zinc-500 opacity-50" /> <Digit value={secondsStr[1]} className="text-zinc-500 opacity-50" /> </div> </motion.div> ); };
Usage
import { AnimatedClock } from "@/components/zenblocks/animated-clock";
<AnimatedClock />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| use24HourFormat | boolean | true | Display time in 24-hour format |
| className | string | - | Additional CSS classes |
Accessibility
- Time updates are not announced to screen readers (would be too verbose)
- Component uses semantic HTML structure
- Respects
prefers-reduced-motionfor tilt effects - High contrast maintained in both light and dark modes
Customization
12-Hour Format
<AnimatedClock use24HourFormat={false} />Custom Styling
<AnimatedClock className="scale-150" />Motion Behavior
- Digit transitions: Spring animation with
stiffness: 400, damping: 30for snappy changes - 3D tilt: Mouse position maps to
rotateXandrotateYtransforms (±15deg range) - Entrance: Fades in with upward motion on mount
The separator colons pulse with opacity changes for visual rhythm.
Performance Notes
- Uses
setIntervalfor time updates (cleaned up on unmount) - Tilt calculations use
useMotionValueanduseSpringfor smooth interpolation - Server-side rendering safe with hydration guard
Examples
Centered Display
<div className="flex items-center justify-center min-h-screen">
<AnimatedClock />
</div>Notes
- Clock automatically adjusts to user's local timezone
- Seconds display at reduced opacity for visual hierarchy
- Component is client-side only due to time updates