Skip to content

Instantly share code, notes, and snippets.

@aldoyh
Created February 20, 2025 05:07
Show Gist options
  • Save aldoyh/5b3ec52a8f8edc326663822e8b794de0 to your computer and use it in GitHub Desktop.
Save aldoyh/5b3ec52a8f8edc326663822e8b794de0 to your computer and use it in GitHub Desktop.
Flip Card Clock
<div class="clock">
<div class="flip">
<div class="digital front" data-number="0"></div>
<div class="digital back" data-number="1"></div>
</div>
<div class="flip">
<div class="digital front" data-number="0"></div>
<div class="digital back" data-number="1"></div>
</div>
<em class="divider">:</em>
<div class="flip">
<div class="digital front" data-number="0"></div>
<div class="digital back" data-number="1"></div>
</div>
<div class="flip">
<div class="digital front" data-number="0"></div>
<div class="digital back" data-number="1"></div>
</div>
<em class="divider">:</em>
<div class="flip">
<div class="digital front" data-number="0"></div>
<div class="digital back" data-number="1"></div>
</div>
<div class="flip">
<div class="digital front" data-number="0"></div>
<div class="digital back" data-number="1"></div>
</div>
</div>
class Flipper {
isFlipping = false;
flipNode: Element;
frontNode: HTMLElement | null;
backNode: HTMLElement | null;
duration: number = 600;
constructor(node: Element, currentTime: string, nextTime: string) {
this.flipNode = node;
this.frontNode = node.querySelector(".front");
this.backNode = node.querySelector(".back");
this.setFrontTime(currentTime);
this.setBackTime(nextTime);
}
setFrontTime(time: string) {
this.frontNode!.dataset.number = time;
}
setBackTime(time: string) {
this.backNode!.dataset.number = time;
}
flipDown(currentTime: string, nextTime: string) {
if (this.isFlipping) {
return false;
}
this.isFlipping = true;
this.setFrontTime(currentTime);
this.setBackTime(nextTime);
this.flipNode.classList.add("running");
setTimeout(() => {
this.flipNode.classList.remove("running");
this.isFlipping = false;
this.setFrontTime(nextTime);
}, this.duration);
}
}
const getTimeFromDate = (date: Date) =>
date
.toTimeString()
.slice(0, 8)
.split(":")
.join("");
let flips = document.querySelectorAll(".flip");
let now = new Date();
let nowTimeStr = getTimeFromDate(new Date(now.getTime() - 1000));
let nextTimeStr = getTimeFromDate(now);
let flippers = Array.from(flips).map(
(flip, i) => new Flipper(flip, nowTimeStr[i], nextTimeStr[i])
);
setInterval(() => {
let now = new Date();
let nowTimeStr = getTimeFromDate(new Date(now.getTime() - 1000));
let nextTimeStr = getTimeFromDate(now);
for (let i = 0; i < flippers.length; i++) {
if (nowTimeStr[i] === nextTimeStr[i]) {
continue;
}
flippers[i].flipDown(nowTimeStr[i], nextTimeStr[i]);
}
}, 1000);
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.clock {
display: flex;
.divider {
font-size: 66px;
line-height: 102px;
font-style: normal;
}
.flip {
position: relative;
width: 60px;
height: 100px;
margin: 2px;
font-size: 66px;
line-height: 100px;
text-align: center;
background: white;
border: 1px solid black;
border-radius: 10px;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
-webkit-box-reflect: below 1px linear-gradient(transparent, rgba(0,0,0,0.4));
// basic
.digital {
&::before,
&::after {
position: absolute;
content: attr(data-number);
left: 0;
right: 0;
color: white;
background: black;
overflow: hidden;
perspective: 160px;
}
&::before {
top: 0;
bottom: 50%;
border-bottom: 1px solid #666;
border-radius: 10px 10px 0 0;
}
&::after {
top: 50%;
bottom: 0;
line-height: 0;
border-radius: 0 0 10px 10px;
}
}
// stack
.back::before,
.front::after {
z-index: 1;
}
.back::after {
z-index: 2;
}
.front::before {
z-index: 3;
}
// animation
.back::after {
transform-origin: center top;
transform: rotateX(0.5turn);
}
&.running {
.front::before {
transform-origin: center bottom;
animation: frontFlipDown 0.6s ease-in-out;
box-shadow: 0 -2px 6px rgba(255, 255, 255, 0.3);
backface-visibility: hidden;
}
.back::after {
animation: backFlipDown 0.6s ease-in-out;
}
}
}
}
@keyframes frontFlipDown {
to {
transform: rotateX(0.5turn);
}
}
@keyframes backFlipDown {
to {
transform: rotateX(0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment