Ever wondered how to flip a coin in 3D right in your browser? 🪙 Dive into this step‑by‑step guide to build a sleek, animated coin‑flip app using just HTML, CSS, and JavaScript—no libraries required!
HTML Structure:
The HTML lays out a simple, semantic page: a <div class="container">
wraps everything for centering and styling. Inside, a .stats
section displays two <p>
elements with IDs (heads-count
and tails-count
) to show running tallies of flips. The .coin
element contains two faces—.heads
and .tails
—each with its own <img>
(using heads.svg
and tails.svg
). Finally, two buttons (“Flip Coin” and “Reset”) live in a .buttons
flex container. By giving IDs to the buttons and counts, we make them easy to reference in JavaScript.
<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Flip a coin</title> <link rel="stylesheet" href="style.css" /> </head> <body> <div class="container"> <div class="stats"> <p id="heads-count">Heads: 0</p> <p id="tails-count">Tails: 0</p> </div> <div class="coin" id="coin"> <div class="heads"> <img src="heads.svg" /> </div> <div class="tails"> <img src="tails.svg" /> </div> </div> <div class="buttons"> <button id="flip-button">Flip Coin</button> <button id="reset-button">Reset</button> </div> </div> <script src="script.js"></script> </body> </html>
CSS:
A global reset (* { margin:0; padding:0; box-sizing:border-box }
) and a Poppins font ensure consistency. The body
uses a two‑tone gradient background, split at 50% height. The .container
card is fixed at 400 px wide, centered via transform: translate(-50%, -50%)
, and given a soft drop shadow and 3D perspective. The coin itself is a 150 px square with transform-style: preserve-3d
so we can spin it in 3D space. Two keyframe animations—spin-heads
and spin-tails
—rotate the coin by 2160° or 1980° around the X‑axis over 3 seconds. .tails
is pre‑rotated 180° so that it faces away until the flip reveals it.
* { padding: 0; margin: 0; box-sizing: border-box; font-family: "Poppins", sans-serif; } body { height: 100%; background: linear-gradient(to top, #5d92ff 50%, #ffd55c 50%) fixed; } .container { background-color: #ffffff; width: 400px; padding: 50px; position: absolute; transform: translate(-50%, -50%); top: 50%; left: 50%; box-shadow: 30px 30px 40px rgba(0, 0, 0, 0.3); border-radius: 10px; -webkit-perspective: 300px; perspective: 300px; } .stats { text-align: right; color: #101020; font-weight: 500; line-height: 25px; } .coin { height: 150px; width: 150px; position: relative; margin: 50px auto; -webkit-transform-style: preserve-3d; transform-style: preserve-3d; } .coin img { width: 145px; } .heads, .tails { position: absolute; width: 100%; height: 100%; -webkit-backface-visibility: hidden; backface-visibility: hidden; } .tails { transform: rotateX(180deg); } @keyframes spin-tails { 0% { transform: rotateX(0); } 100% { transform: rotateX(1980deg); } } @keyframes spin-heads { 0% { transform: rotateX(0); } 100% { transform: rotateX(2160deg); } } .buttons { display: flex; justify-content: space-between; } button { width: 120px; padding: 10px 0; border: 2.5px solid #424ae0; border-radius: 5px; cursor: pointer; } #flip-button { background-color: #424ae0; color: #ffffff; } #flip-button:disabled { background-color: #e1e0ee; border-color: #e1e0ee; color: #101020; } #reset-button { background-color: #ffffff; color: #424ae0; }
Javascript:
In script.js
, we first grab references to the coin element, buttons, and count displays. We track heads
and tails
counts in two variables. On each “Flip Coin” click, we:
-
Clear any ongoing animation by setting
coin.style.animation = "none"
and forcing a reflow (void coin.offsetWidth
). -
Randomly choose heads or tails (
Math.random() < 0.5
). -
Apply the corresponding animation name (
spin-heads
orspin-tails
) for 3 s. -
Increment the appropriate counter.
-
Disable the flip button during the animation to prevent multiple overlapping flips.
-
Use
setTimeout
(3 s) to re-enable the button and update the on‑screen counts.
The “Reset” button simply clears the animation, zeroes both counters, and updates the display.
const coin = document.querySelector(".coin"); const flipBtn = document.querySelector("#flip-button"); const resetBtn = document.querySelector("#reset-button"); const headsCount = document.querySelector("#heads-count"); const tailsCount = document.querySelector("#tails-count"); let heads = 0; let tails = 0; flipBtn.addEventListener("click", () => { const isHeads = Math.random() < 0.5; coin.style.animation = "none"; void coin.offsetWidth; const animationName = isHeads ? "spin-heads" : "spin-tails"; coin.style.animation = `${animationName} 3s forwards`; // Update count if (isHeads) { heads++; } else { tails++; } disableButton(); setTimeout(updateStats, 3000); }); resetBtn.addEventListener("click", () => { coin.style.animation = "none"; heads = 0; tails = 0; updateStats(); }); function updateStats() { headsCount.textContent = `Heads: ${heads}`; tailsCount.textContent = `Tails: ${tails}`; } function disableButton() { flipBtn.disabled = true; setTimeout(() => { flipBtn.disabled = false; }, 3000); }
This project brings together fundamental web technologies in a compact, interactive demo. The HTML provides clear structure and hooks for styling and scripting. The CSS delivers both polished layout and engaging 3D animations. JavaScript adds interactivity, state management, and user‑friendly controls. Altogether, it’s a great example of how HTML, CSS, and JS collaborate to build a small but complete web application—perfect for sharpening your skills in DOM manipulation, CSS transforms, and event handling!