JSFiddle - React, Tailwind, and code Playground
by Josh Pullen
CSS
body {
font-family: "CMU Serif";
background: black;
}
.Transaction {
position: fixed;
top: 0;
left: 0;
transform: translate(-50%, -50%);
font-size: 5vmin;
color: white;
text-decoration: none;
white-space: nowrap;
}
.Transaction__hash {
color: rgba(255, 255, 255, 0.4);
}
.Transaction__out {
color: red;
}
.Transaction__in {
color: green;
}
.Transaction__value {
}
.Transaction--corner0 {
animation: transactionAnim0 6000ms ease-out forwards;
}
.Transaction--corner1 {
animation: transactionAnim1 6000ms ease-out forwards;
}
.Transaction--corner2 {
animation: transactionAnim2 6000ms ease-out forwards;
}
.Transaction--corner3 {
animation: transactionAnim3 6000ms ease-out forwards;
}
@keyframes transactionAnim0 {
0% { opacity: 1; top: -25%; left: -25%; }
70% { opacity: 1; }
90% { opacity: 0; pointer-events: all; }
100% { opacity: 0; top: 40%; left: 40%; pointer-events: none; }
}
@keyframes transactionAnim1 {
0% { opacity: 1; top: -25%; left: 125%; }
70% { opacity: 1; }
90% { opacity: 0; pointer-events: all; }
100% { opacity: 0; top: 40%; left: 60%; pointer-events: none; }
}
@keyframes transactionAnim2 {
0% { opacity: 1; top: 125%; left: 125%; }
70% { opacity: 1; }
90% { opacity: 0; pointer-events: all; }
100% { opacity: 0; top: 60%; left: 60%; pointer-events: none; }
}
@keyframes transactionAnim3 {
0% { opacity: 1; top: 125%; left: -25%; }
70% { opacity: 1; }
90% { opacity: 0; pointer-events: all; }
100% { opacity: 0; top: 60%; left: 40%; pointer-events: none; }
}
.TransactionRipple {
position: fixed;
top: 0;
left: 0;
width: 0;
height: 0;
pointer-events: none;
}
.TransactionRipple--corner0 {
top: 0;
left: 0;
}
.TransactionRipple--corner1 {
top: 0;
left: 100%;
}
.TransactionRipple--corner2 {
top: 100%;
left: 100%;
}
.TransactionRipple--corner3 {
top: 100%;
left: 0;
}
.TransactionRipple__ripple {
position: absolute;
top: 0;
left: 0;
width: 0vmin;
height:...
JavaScript
import { html, render, useState, useEffect } from "https://unpkg.com/htm/preact/standalone.module.js";
function getInputValue(data) {
return data.inputs.reduce((sum, input) => input.prev_out.value + sum, 0) / 100000000;
}
function usePeriodicRerender(period = 100) {
const [dummyValue, setDummyValue] = useState(true);
useEffect(() => {
const rerender = () => setDummyValue(value => !value);
const interval = setInterval(rerender, period);
return () => {
clearInterval(interval);
}
}, [period]);
}
function App() {
const [transactions, setTransactions] = useState([]);
const addTransaction = (data) => {
setTransactions(transactions => {
const prevTransaction = transactions.length > 0
? transactions[transactions.length - 1]
: null;
// Limit transactions to a rate of one every 200ms. Drop any
// that come in faster. (This also prevents a bug where switching
// to a different tab and returning later would display all the
// transactions that happened in the background all at once.)
if (prevTransaction && (Date.now() - prevTransaction.time) < 150) {
return transactions;
}
// Prevent bug where switching to a different tab and returning later
// displays lots of transactions all at once (by just skipping
// transactions while the page isn't visible)
if (document.visibilityState === "hidden") {
return transactions;
}
const prevCorner = prevTransaction ? prevTransaction.corner : 0;
// Only keep the most recent 30 transactions in memory
// (This should be enough so that old transactions are
// only deleted once they have already animated out
// of existence.)
return [
...transactions.slice(-29),
{
data,
corner: (prevCorner + 1) % 4,
time: Date.now()
}
];
});
}
const { error } = useLiveBTCData({
onTransaction: (data)...