Overview
This view is less complicated than the ring view. It has one constant animation and 1 button animation on the left in which we animate the pause button into a play button.
Our starting point will be this static, working version. The countdown uses a setInterval function inside a useEffect to go from 60 to 0, then it resets the timer. One thing worth mentioning is that we use tabular-nums for our numbers, which keeps them monospaced and their sizes consistent. Otherwise, they would slightly shift each time the number changes.
"use client";
import { useMemo, useState } from "react";
import { Timer } from "./timer";
export default function DynamicIsland() {
const [view, setView] = useState("timer");
const content = useMemo(() => {
switch (view) {
case "ring":
return <Ring />;
case "timer":
return <Timer />;
case "idle":
return <div className="h-7" />;
}
}, [view]);
return (
<div>
<div className="flex h-[160px] justify-center">
<div className="h-fit min-w-[100px] overflow-hidden rounded-full bg-black">
{content}
</div>
</div>
</div>
);
} Countdown animation
This is a great use case for AnimatePresence, as each number is rendered separately with a unique key. Let’s wrap our countArray with AnimatePresence and set initial={false} and mode="popLayout". We don’t want to animate on the initial render, and we want the elements to exit and enter simultaneously, which is why we use the popLayout mode.
When it comes to the animation itself, we want the numbers to come from the bottom and disappear to the top, both with a slight blur and bounce.
<motion.div
className="inline-block tabular-nums"
key={n + i}
initial={{ y: "12px", filter: "blur(2px)", opacity: 0 }}
animate={{ y: "0", filter: "blur(0px)", opacity: 1 }}
exit={{ y: "-12px", filter: "blur(2px)", opacity: 0 }}
transition={{ type: "spring", bounce: 0.35 }}
>
{n}
</motion.div><motion.div
className="inline-block tabular-nums"
key={n + i}
initial={{ y: "12px", filter: "blur(2px)", opacity: 0 }}
animate={{ y: "0", filter: "blur(0px)", opacity: 1 }}
exit={{ y: "-12px", filter: "blur(2px)", opacity: 0 }}
transition={{ type: "spring", bounce: 0.35 }}
>
{n}
</motion.div>The combination of AnimatePresence and our animation gives us the following result: