Skip to content

Instantly share code, notes, and snippets.

@veltman
Last active March 11, 2024 16:22
Show Gist options
  • Save veltman/f2b2a06d4ffa62f4d39d5ebac5ceeef0 to your computer and use it in GitHub Desktop.
Save veltman/f2b2a06d4ffa62f4d39d5ebac5ceeef0 to your computer and use it in GitHub Desktop.
Departures board
<!DOCTYPE html>
<meta charset="utf-8">
<link href="https://fonts.googleapis.com/css?family=Roboto+Condensed" rel="stylesheet">
<style>
body {
background-color: #222;
font: 116px "Roboto Condensed";
color: #ecdb41;
text-align: center;
}
div {
position: absolute;
top: 0;
left: 0;
right: 0;
}
span {
position: relative;
}
.container {
top: 44px;
left: 12px;
}
.flap {
width: 110px;
right: auto;
}
.half {
height: 62px;
overflow: hidden;
background: #444;
transform-style: preserve-3d;
animation-timing-function: ease-in;
animation-duration: 0.28s;
animation-iteration-count: infinite;
}
.fast .half {
animation-duration: 0.14s;
}
.divider {
top: 61px;
height: 2px;
background: #222;
}
.prev {
top: 62px;
}
.back,
.front {
backface-visibility: hidden;
}
.front {
transform-origin: center bottom;
}
.back {
top: 62px;
transform-origin: center top;
}
.front span,
.next span {
top: -6px;
}
.back span,
.prev span {
top: -68px;
}
.animated .front {
animation-name: flipFront;
}
.animated .back {
animation-name: flipBack;
}
@keyframes flipFront {
0% {
transform: rotateX(0deg);
background-color: #444;
}
100% {
transform: rotateX(180deg);
background-color: #1c1c1c;
}
}
@keyframes flipBack {
0% {
transform: rotateX(180deg);
background-color: #222;
}
100% {
transform: rotateX(0deg);
background-color: #444;
}
}
</style>
<div class="container"></div>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
let cities = [
"NEW YORK",
"MIAMI",
"CHICAGO",
"BOSTON",
"ATLANTA",
"SEATTLE",
"PARIS",
"MOSCOW",
"SHANGHAI",
"BANGKOK",
"TORONTO",
"BEIJING",
"ATHENS",
"BERLIN",
"LONDON",
"TOKYO",
"SEOUL",
"MUMBAI",
"CAIRO",
"ISTANBUL",
"SYDNEY",
"BRUSSELS"
].map(d => d.padEnd(8));
d3.shuffle(cities);
let cycle = {};
for (let i = 65; i < 90; i++) {
cycle[String.fromCharCode(i)] = String.fromCharCode(i + 1);
}
cycle["Z"] = " ";
cycle[" "] = "A";
let rows = d3.select(".container")
.selectAll(".row")
.data(cities.splice(0, 3))
.enter()
.append("div")
.attr("class", "row")
.style("top", (d, i) => i * 144 + "px");
let flaps = rows.selectAll("div")
.data(city => city.split(""))
.enter()
.append("div")
.attr("class", "flap")
.style("left", (d, i) => i * 118 + "px");
["next", "prev", "back", "front"].forEach(d => {
if (d === "front") {
flaps.append("div")
.attr("class", "divider");
}
flaps.append("div")
.attr("class", "half " + d)
.append("span")
.text(letter => letter);
});
cities.push(...rows.data());
flip();
function flip() {
d3.timeout(() => {
let q = d3.queue();
rows.each(function() {
d3.select(this)
.selectAll(".flap")
.each(function(fromLetter, i) {
let toLetter = cities[0][i],
flap = d3.select(this);
if (fromLetter !== toLetter) {
q.defer(flipLetter, flap.datum(toLetter), fromLetter, toLetter);
}
});
cities.push(cities.shift());
});
q.awaitAll(function(err) {
if (err) {
throw err;
}
flip();
});
}, 500);
}
function flipLetter(flap, fromLetter, toLetter, cb) {
let current = fromLetter,
next = cycle[fromLetter],
prevFlaps = flap.selectAll(".prev span, .front span"),
nextFlaps = flap.selectAll(".back span, .next span"),
fast;
flap.select(".front").on("animationiteration", function() {
if (next === toLetter) {
flap.classed("animated fast", false)
.selectAll("span")
.text(toLetter);
return cb();
}
if (!fast) {
fast = flap.classed("fast", true);
}
prevFlaps.text(next);
current = next;
next = cycle[next];
setTimeout(function() {
nextFlaps.text(next);
}, 0);
});
flap.classed("animated", true);
nextFlaps.text(next);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment