HTML/CSS
JavaScript
Spin the Wheel
It is commonly used in promotions, giveaways, decision-making tools, and gamified experiences.
<!DOCTYPE HTML>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="utf-8">
<title>Wheel of Fortune Bingo</title>
<style type="text/css">
body {
font-family: Helvetica, Arial, sans-serif;
}
text {
font-family: Helvetica, Arial, sans-serif;
font-size: 10px;
pointer-events: none;
}
#chart {
background-image: url("https://experiencenetcorecloud.com/assets/images/spinbg.png");
background-repeat: no-repeat;
background-size: 130px;
background-position: center 185px;
}
.close {
position: absolute;
color: red;
right: 2px;
top: 2px;
font-family: Arial;
font-size: 18px;
line-height: 14px;
cursor: pointer;
border: 1px solid red;
border-radius: 14px;
padding: 3px;
}
.spinner-wappers {
width: 100%;
height: 100%;
display: block;
background: #f2f2f2;
}
.spinner {
margin: 0 auto;
display: block;
position: relative;
width: 400px;
height: 365px;
}
#formContainer {
display: none;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1000;
}
.form-wrapper {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 15px;
border-radius: 5px;
width: 300px;
max-width: 90%;
}
.form-title {
text-align: left;
margin-bottom: 15px;
font-size: 12px;
font-weight: bold;
}
.form-group {
margin-bottom: 10px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
text-align: left;
font-size: 10px;
}
.success-message h2 {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
text-align: center;
}
.form-group input {
width: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
text-align: left;
font-size: 10px;
}
.form-group .error {
color: red;
font-size: 8px;
margin-top: 5px;
display: none;
}
.spin-submit-btn {
background-color: #BF2526!Important;
color: white!Important;
border: none!Important;
padding: 10px 20px!Important;
width: 100%!Important;
border-radius: 4px!Important;
cursor: pointer!Important;
font-size: 14px!Important;
font-weight: bold!Important;
}
.spin-submit-btn:hover {
background-color: #9E1E1F!Important;
}
.success-message {
display: none;
text-align: center;
}
.prize-title {
font-size: 18px;
color: #BF2526;
margin-bottom: 10px;
}
.prize-info p {
font-size: 10px;
color: #000;
}
.coupon-code {
background-color: #f5f5f5;
border: 2px dashed #BF2526;
padding: 10px 15px;
font-size: 20px;
font-weight: bold;
letter-spacing: 1px;
margin: 15px auto;
max-width: 200px;
border-radius: 4px;
}
.spinner {
animation: none ! Important;
}
.spinner *:focus-visible {
outline: none !important;
box-shadow: none !important;
}
.coupon-instructions {
font-size: 14px;
color: #555;
margin-top: 10px;
}
.disabled {
opacity: 0.5;
cursor: not-allowed !important;
}
#spinButton {
cursor: pointer;
}
</style>
</head>
<body>
<div class="spinner-wappers">
<div class="spinner">
<div id="chart"></div>
</div>
</div>
<div id="formContainer">
<div class="form-wrapper">
<div class="form-content">
<div class="close" id="close-form">X</div>
<div class="form-title">Congratulations! You've won: <span id="prizeText"></span></div>
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" placeholder="Enter your name">
<div class="error" id="nameError">Please enter your name</div>
</div>
<div class="form-group">
<label for="email">Email: <span style="color: red;">*</span></label>
<input type="email" id="email" placeholder="Enter your email">
<div class="error" id="emailError">Please enter a valid email address</div>
</div>
<div class="form-group">
<label for="phone">Phone: <span style="color: red;">*</span></label>
<input type="tel" id="phone" placeholder="Enter your phone number">
<div class="error" id="phoneError">Please enter a valid phone number</div>
</div>
<button type="button" class="spin-submit-btn" id="claimBtn">Claim Offer Now</button>
</div>
<div class="success-message" id="successMessage">
<div class="close" id="close-success">X</div>
<h2>Thank You!</h2>
<div class="prize-info">
<p>Your prize: <span class="prize-title" id="finalPrize"></span></p>
<div class="coupon-code" id="couponCode"></div>
<p class="coupon-instructions">Use this code during checkout to redeem your offer.</p>
</div>
</div>
</div>
</div>
</body>
</html>
Note: In the console, type 'allow pasting,' and then you can paste the code below.
var script = document.createElement('script');
script.src = "https://d3js.org/d3.v3.min.js";
script.type = "text/javascript";
document.head.appendChild(script);
script.onload = function() {
console.log("D3.js v" + d3.version + " loaded successfully!");
};
var padding = {top: 20, right:10, bottom: 100, left: 10},
w = 400 - padding.left - padding.right,
h = 365 - padding.top - padding.bottom,
r = Math.min(w, h)/2,
rotation = 0,
oldrotation = 0,
picked = 100000,
oldpick = [],
isSpinning = false,
color = d3.scale.ordinal()
.range([
"#8BC53E",
"#0A9343",
"#24A9E0",
"#652C90",
"#9E1E62",
"#BE1D2D",
]);
var data = [
{"label":"100 points", "value": 1, "coupon": "SORRY2025"},
{"label":"Buy 1 get 1 free", "value": 2, "coupon": "BOGO2025"},
{"label":"Buy 3 get 1 free", "value": 3, "coupon": "B3G1FREE"},
{"label":"Flat 50% Off", "value": 4, "coupon": "HALF2025"},
{"label":"Extra 10% Off", "value": 5, "coupon": "EXTRA10"},
{"label":"Extra 5% Off", "value": 6, "coupon": "EXTRA05"},
];
var currentPrize = "";
var currentCoupon = "";
var svg = d3.select('#chart')
.append("svg")
.data([data])
.attr("width", w + padding.left + padding.right)
.attr("height", h + padding.top + padding.bottom);
var container = svg.append("g")
.attr("class", "chartholder")
.attr("transform", "translate(" + (w/2 + padding.left) + "," + (h/2 + padding.top) + ")");
var vis = container
.append("g");
var finalVal = 'test';
var pie = d3.layout.pie().sort(null).value(function(d){return 1;});
// declare an arc generator function
var arc = d3.svg.arc().outerRadius(r);
// select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("g")
.attr("class", "slice");
arcs.append("path")
.attr("fill", function(d, i){ return color(i); })
.attr("d", function (d) { return arc(d); });
// add the text
arcs.append("text").attr("transform", function(d){
d.innerRadius = 0;
d.outerRadius = r;
d.angle = (d.startAngle + d.endAngle)/2;
return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")translate(" + (d.outerRadius -10) +")";
})
.attr("fill", "#fff")
.style({"font-weight":"bold"})
.attr("text-anchor", "end")
.text(function(d, i) {
return data[i].label;
});
// Define the spin button group
var spinButtonGroup = container.append("g")
.attr("id", "spinButton");
// Draw spin rectangle button
spinButtonGroup.append("rect")
.attr("x", -50)
.attr("y", 165)
.attr("rx", 5)
.attr("width", 100)
.attr("height", 40)
.style({"fill":"#000"});
// Spin text
spinButtonGroup.append("text")
.attr("x", 0)
.attr("y", 193)
.attr("text-anchor", "middle")
.text("SPIN")
.style({"font-weight":"bold","font-size":"20px","fill":"#fff"});
// Add click event to spin button
spinButtonGroup.style("cursor", "pointer")
.on("click", function() {
if (!isSpinning) {
spin();
}
});
function spin(){
// Set spinning state to true
isSpinning = true;
// Disable the button
spinButtonGroup.classed("disabled", true)
.style("pointer-events", "none");
// Check if all slices have been seen
console.log("OldPick: " + oldpick.length, "Data length: " + data.length);
if(oldpick.length == data.length){
console.log("done");
return;
}
var ps = 360/data.length,
pieslice = Math.round(1440/data.length),
rng = Math.floor((Math.random() * 1440) + 360);
rotation = (Math.round(rng / ps) * ps);
picked = Math.round(data.length - (rotation % 360)/ps);
picked = picked >= data.length ? (picked % data.length) : picked;
if(oldpick.indexOf(picked) !== -1){
d3.select(this).call(spin);
return;
} else {
oldpick.push(picked);
}
rotation += 90 - Math.round(ps/2);
vis.transition()
.duration(3000)
.attrTween("transform", rotTween)
.each("end", function(){
// Mark question as seen
d3.select(".slice:nth-child(" + (picked + 1) + ") path")
.attr("stroke-width", "3px")
.attr("stroke", "#fff");
oldrotation = rotation;
// Get the value of the selected segment
var prizeWon = data[picked];
currentPrize = prizeWon.label;
currentCoupon = prizeWon.coupon;
finalVal = JSON.stringify(prizeWon);
console.log("You won: " + currentPrize + " with coupon: " + currentCoupon);
// Show the form after 3 seconds
setTimeout(function() {
document.getElementById('prizeText').textContent = currentPrize;
document.getElementById('formContainer').style.display = 'block';
}, 1000);
// Reset the spinning state
isSpinning = false;
// Don't re-enable the button after spinning to prevent multiple spins
// We'll keep it disabled
});
}
// Make arrow
svg.append("g")
.attr("transform", "translate(" + (w - padding.left) + "," + ((h/2)+padding.top) + ")")
.append("path")
.attr("d", "M-" + (r*.15) + ",0L0," + (r*.08) + "L0,-" + (r*.08) + "Z")
.style({"fill":"#000"});
// Draw spin circle
container.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 10)
.attr("stroke", "#fff")
.attr("stroke-width", 2)
.style({"fill":"#BF2526"});
function rotTween(to) {
var i = d3.interpolate(oldrotation % 360, rotation);
return function(t) {
return "rotate(" + i(t) + ")";
};
}
function getRandomNumbers(){
var array = new Uint16Array(1000);
var scale = d3.scale.linear().range([360, 1440]).domain([0, 100000]);
if(window.hasOwnProperty("crypto") && typeof window.crypto.getRandomValues === "function"){
window.crypto.getRandomValues(array);
console.log("works");
} else {
// No support for crypto, get crappy random numbers
for(var i=0; i < 1000; i++){
array[i] = Math.floor(Math.random() * 100000) + 1;
}
}
return array;
}
// close validation and submission
document.getElementById('close-form').addEventListener('click', function() {
let elements = document.querySelectorAll('#formContainer');
elements.forEach(element => {
element.style.display = 'none';
});
// Reset spinning state
isSpinning = false;
// Re-enable the spin button
d3.select("#spinButton").classed("disabled", false).style("pointer-events", "auto");
});
document.getElementById('close-success').addEventListener('click', function() {
let elements = document.querySelectorAll('#formContainer');
elements.forEach(element => {
element.style.display = 'none';
});
// Reset spinning state
isSpinning = false;
// Re-enable the spin button
d3.select("#spinButton").classed("disabled", false).style("pointer-events", "auto");
});
// Form validation and submission
document.getElementById('claimBtn').addEventListener('click', function() {
var name = document.getElementById('name').value;
var email = document.getElementById('email').value;
var phone = document.getElementById('phone').value;
var isValid = true;
// Validate name (optional)
if (name.trim() === '') {
document.getElementById('nameError').style.display = 'none';
}
// Validate email (required)
if (email.trim() === '' || !isValidEmail(email)) {
document.getElementById('emailError').style.display = 'block';
isValid = false;
} else {
document.getElementById('emailError').style.display = 'none';
}
// Validate phone (required)
if (phone.trim() === '' || !isValidPhone(phone)) {
document.getElementById('phoneError').style.display = 'block';
isValid = false;
} else {
document.getElementById('phoneError').style.display = 'none';
}
if (isValid) {
// Collect and process form data
var formData = {
name: name,
email: email,
phone: phone,
prize: currentPrize,
coupon: currentCoupon
};
console.log("Form submitted with data:", formData);
// Show success message
document.querySelector('.form-content').style.display = 'none';
document.getElementById('finalPrize').textContent = currentPrize;
document.getElementById('couponCode').textContent = currentCoupon;
document.getElementById('successMessage').style.display = 'block';
}
});
function isValidEmail(email) {
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function isValidPhone(phone) {
var phoneRegex = /^\+?[0-9]{10,15}$/;
return phoneRegex.test(phone);
}