Introduction
In today's digital age, engaging your community with interactive tools is essential for fostering participation and excitement. Whether you're hosting a giveaway, conducting a poll, or organizing a competition, having a visually appealing and interactive raffle wheel can significantly enhance user experience. In this article, I'll walk you through the process of creating Modern Raffle 2024, an interactive raffle wheel built with HTML, CSS, and JavaScript. We'll cover everything from setting up the structure to adding animations and integrating social sharing features.
๐ Technologies Used
To bring this project to life, I leveraged the following technologies:
- HTML5: For structuring the webpage and creating interactive elements.
- CSS3: To style the application with modern design principles, including glassmorphism, animations, and responsive layouts.
- JavaScript: To add interactivity, handle user inputs, and manage the raffle wheel's logic and animations.
- Canvas API: For drawing and animating the raffle wheel.
- Font Awesome: To incorporate vector icons for a polished look.
- Google Fonts: Utilized the Inter font for a clean and modern typography.
- Buy Me a Coffee: Integrated a donation button to support the project.
๐ ๏ธ Project Structure
The project is organized into three main files:
-
index.html
: Contains the HTML structure of the application. -
styles.css
: Holds all the CSS styles for layout and design. -
script.js
: Includes the JavaScript code that powers the interactivity and animations.
Additionally, a footer section is incorporated to promote my website, LinkedIn, Twitter, and include a Buy Me a Coffee button for support.
๐ HTML (index.html
)
The HTML structure sets up the main components of the raffle application, including input sections for participants and prizes, the raffle wheel, a modal for announcing winners, and a footer for promotions.
html
<!DOCTYPE html>
Modern Raffle 2024
<!-- Font Awesome for Icons -->
<!-- Google Fonts for Enhanced Typography -->
๐ Modern Raffle 2024 ๐
<div class="input-section">
<h2>Add Participants</h2>
<div class="input-group">
<input type="text" id="username" placeholder="Username">
<button id="addUserBtn"><i class="fas fa-user-plus"></i> Add</button>
</div>
<ul id="userList"></ul>
</div>
<div class="input-section">
<h2>Select a Prize</h2>
<div class="input-group">
<input type="text" id="prize" placeholder="Prize Description">
<button id="setPrizeBtn"><i class="fas fa-gift"></i> Set Prize</button>
</div>
<p id="selectedPrize">Selected Prize: None</p>
</div>
<div class="wheel-container">
<div class="wheel-wrapper">
<canvas id="raffleWheel" width="500" height="500"></canvas>
<div class="pointer">
<i class="fas fa-arrow-down"></i>
</div>
</div>
<button id="spinBtn"><i class="fas fa-play"></i> Spin the Wheel</button>
</div>
<div id="winnerModal" class="modal">
<div class="modal-content">
<span class="close-button">×</span>
<h2>๐ Congratulations! ๐</h2>
<p id="winnerText"></p>
<button id="shareBtn"><i class="fab fa-twitter"></i> Share on Twitter</button>
</div>
</div>
</div>
<!-- Footer Section -->
<footer class="footer">
<div class="footer-container">
<div class="footer-links">
<a href="https://gladiatorsbattle.com" target="_blank" aria-label="Website">
<i class="fas fa-globe"></i> Website
</a>
<a href="https://www.linkedin.com/in/pierre-romain-lopez/" target="_blank" aria-label="LinkedIn">
<i class="fab fa-linkedin"></i> LinkedIn
</a>
<a href="https://x.com/GladiatorsBT" target="_blank" aria-label="Twitter">
<i class="fab fa-twitter"></i> Twitter
</a>
</div>
<div class="footer-donate">
<!-- Buy Me a Coffee Button -->
<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js"
data-name="bmc-button"
data-slug="GladiatorsBattle"
data-color="#FFDD00"
data-emoji=""
data-font="Cookie"
data-text="Buy me a coffee"
data-outline-color="#000000"
data-font-color="#000000"
data-coffee-color="#ffffff">
</script>
</div>
</div>
</footer>
<script src="script.js"></script>
๐จ CSS (styles.css)
The CSS file is meticulously crafted to ensure a modern and premium look, incorporating glassmorphism, smooth animations, responsive design, and accessibility features. Below is the complete CSS with detailed explanations of enhancements and fixes.
/* Reset and Base Styles */
- { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Inter', sans-serif; }
body {
background: linear-gradient(135deg, #1e3c72, #2a5298);
color: #ffffff;
display: flex;
flex-direction: column; /* Stack children vertically /
justify-content: flex-start; / Start from the top /
align-items: center;
min-height: 100vh;
/ Remove overflow hidden to allow footer visibility */
overflow-x: hidden;
}
/* Container Styles /
.container {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
padding: 40px;
border-radius: 20px;
text-align: center;
width: 90%;
max-width: 900px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.37);
border: 1px solid rgba(255, 255, 255, 0.18);
animation: fadeIn 1s ease-in-out;
flex: 1; / Allow container to grow and push footer down */
display: flex;
flex-direction: column;
align-items: center;
}
/* Fade-in Animation */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Heading Styles */
h1 {
margin-bottom: 30px;
font-size: 3rem;
font-weight: 700;
text-shadow: 3px 3px 6px rgba(0,0,0,0.3);
}
/* Input Sections */
.input-section {
margin-bottom: 40px;
width: 100%;
}
.input-section h2 {
margin-bottom: 15px;
font-size: 1.75rem;
font-weight: 600;
}
/* Input Groups */
.input-group {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
.input-group input {
padding: 12px 20px;
width: 60%;
border: none;
border-radius: 30px;
background: rgba(255, 255, 255, 0.1);
color: #ffffff;
font-size: 1rem;
outline: none;
transition: background 0.3s ease, box-shadow 0.3s ease;
}
.input-group input::placeholder {
color: #dddddd;
}
.input-group input:focus {
background: rgba(255, 255, 255, 0.2);
box-shadow: 0 0 10px rgba(255, 127, 80, 0.5);
}
.input-group button {
padding: 12px 25px;
border: none;
border-radius: 30px;
background-color: #ff7f50;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease;
}
.input-group button:hover {
background-color: #ff5722;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
}
/* User List */
userList {
list-style: none;
max-height: 120px;
overflow-y: auto;
text-align: left;
padding: 0 20%;
width: 100%;
}
userList li {
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
font-size: 1rem;
}
/* Selected Prize */
selectedPrize {
font-size: 1.2rem;
font-weight: 500;
margin-top: 10px;
}
/* Wheel Container */
.wheel-container {
position: relative;
margin-bottom: 40px;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.wheel-wrapper {
position: relative;
width: 100%;
max-width: 500px;
margin: 0 auto 20px;
}
/* Canvas Styles */
canvas {
width: 100%;
height: auto;
border-radius: 50%;
box-shadow: 0 0 20px rgba(0,0,0,0.5);
background: #000;
transition: transform 4s cubic-bezier(0.33, 1, 0.68, 1);
}
/* Pointer Styles */
.pointer {
position: absolute;
top: -20px;
left: 50%;
transform: translateX(-50%);
font-size: 2rem;
color: #ffeb3b;
animation: bounce 2s infinite;
}
@keyframes bounce {
0%, 100% { transform: translateX(-50%) translateY(0); }
50% { transform: translateX(-50%) translateY(-10px); }
}
/* Spin Button */
spinBtn {
padding: 15px 35px;
border: none;
border-radius: 50px;
background-color: #32cd32;
color: #fff;
font-size: 1.25rem;
font-weight: 600;
cursor: pointer;
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
margin: 0 auto;
}
spinBtn:hover {
background-color: #28a428;
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0,0,0,0.4);
}
spinBtn:active {
transform: translateY(0);
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
z-index: 100;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.8);
animation: fadeInModal 0.5s ease;
}
@keyframes fadeInModal {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-content {
background-color: rgba(30, 30, 30, 0.95);
margin: 10% auto;
padding: 30px;
border-radius: 15px;
width: 90%;
max-width: 600px;
text-align: center;
box-shadow: 0 8px 25px rgba(0,0,0,0.5);
position: relative;
animation: slideDown 0.5s ease;
}
@keyframes slideDown {
from { transform: translateY(-50px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.close-button {
color: #bbb;
position: absolute;
top: 15px;
right: 20px;
font-size: 28px;
font-weight: bold;
cursor: pointer;
transition: color 0.3s ease;
}
.close-button:hover,
.close-button:focus {
color: #fff;
}
.modal-content h2 {
margin-bottom: 20px;
font-size: 2rem;
font-weight: 700;
}
.modal-content p {
font-size: 1.25rem;
margin-bottom: 25px;
}
shareBtn {
padding: 12px 25px;
background-color: #1DA1F2;
border: none;
border-radius: 30px;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: background-color 0.3s ease, transform 0.2s ease;
}
shareBtn:hover {
background-color: #0d95e8;
transform: translateY(-2px);
}
/* Footer Styles /
.footer {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
padding: 20px 0;
border-top: 1px solid rgba(255, 255, 255, 0.2);
width: 100%;
/ Ensure footer stays below content */
flex-shrink: 0;
}
.footer-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-width: 900px;
margin: 0 auto;
padding: 0 20px;
}
.footer-links {
display: flex;
gap: 20px;
margin-bottom: 15px;
}
.footer-links a {
color: #ffffff;
font-size: 1rem;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
transition: color 0.3s ease, transform 0.2s ease;
}
.footer-links a:hover {
color: #ff7f50;
transform: translateY(-2px);
}
.footer-links a i {
font-size: 1.2rem;
}
.footer-donate {
margin-top: 10px;
}
/* Responsive Design for Footer */
@media (min-width: 600px) {
.footer-container {
flex-direction: row;
justify-content: space-between;
}
.footer-links {
margin-bottom: 0;
}
}
/* Scrollbar Styles for User List */
userList::-webkit-scrollbar {
width: 6px;
}
userList::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
userList::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
userList::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}
/* Button Focus States for Accessibility */
.input-group button:focus,
spinBtn:focus,
shareBtn:focus {
outline: 2px solid #ff7f50;
outline-offset: 2px;
}
๐ JavaScript (script.js)
The JavaScript code handles the core functionality of the raffle wheel, including adding participants, setting prizes, spinning the wheel, determining the winner, and enabling social sharing.
// Selecting DOM Elements
const addUserBtn = document.getElementById('addUserBtn');
const usernameInput = document.getElementById('username');
const userList = document.getElementById('userList');
const setPrizeBtn = document.getElementById('setPrizeBtn');
const prizeInput = document.getElementById('prize');
const selectedPrize = document.getElementById('selectedPrize');
const spinBtn = document.getElementById('spinBtn');
const winnerModal = document.getElementById('winnerModal');
const closeBtn = document.querySelector('.close-button');
const winnerText = document.getElementById('winnerText');
const shareBtn = document.getElementById('shareBtn');
// State Variables
let users = [];
let prize = "None";
let isSpinning = false;
// Wheel Configuration
const canvas = document.getElementById('raffleWheel');
const ctx = canvas.getContext('2d');
const wheelRadius = canvas.width / 2;
const colors = ['#FF5733', '#33FF57', '#3357FF', '#F333FF', '#FF33A8', '#33FFF6', '#FFC300', '#DAF7A6'];
let startAngle = 0;
let arc = 0;
// Initialize Wheel
function initializeWheel() {
if (users.length === 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
return;
}
arc = (2 * Math.PI) / users.length;
drawWheel();
}
// Draw the Raffle Wheel
function drawWheel() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < users.length; i++) {
const angle = startAngle + i * arc;
ctx.fillStyle = colors[i % colors.length];
ctx.beginPath();
ctx.moveTo(wheelRadius, wheelRadius);
ctx.arc(wheelRadius, wheelRadius, wheelRadius, angle, angle + arc, false);
ctx.closePath();
ctx.fill();
// Draw User Names
ctx.save();
ctx.fillStyle = '#FFFFFF';
ctx.translate(wheelRadius, wheelRadius);
ctx.rotate(angle + arc / 2);
ctx.textAlign = "right";
ctx.font = 'bold 16px Inter, sans-serif';
ctx.fillText(users[i], wheelRadius - 20, 10);
ctx.restore();
}
// Draw Pointer
drawPointer();
}
// Draw the Pointer Arrow
function drawPointer() {
const pointerSize = 20;
ctx.fillStyle = '#FFEB3B';
ctx.beginPath();
ctx.moveTo(wheelRadius - pointerSize, 0);
ctx.lineTo(wheelRadius + pointerSize, 0);
ctx.lineTo(wheelRadius, -pointerSize * 1.5);
ctx.closePath();
ctx.fill();
}
// Add User Event
addUserBtn.addEventListener('click', addUser);
usernameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') addUser();
});
// Function to Add a User
function addUser() {
const username = usernameInput.value.trim();
if (username === "") {
showAlert("Please enter a valid username.");
return;
}
if (users.includes(username)) {
showAlert("This username is already added.");
return;
}
users.push(username);
updateUserList();
usernameInput.value = '';
initializeWheel();
}
// Update the User List UI
function updateUserList() {
userList.innerHTML = '';
users.forEach(user => {
const li = document.createElement('li');
li.textContent = user;
userList.appendChild(li);
});
}
// Set Prize Event
setPrizeBtn.addEventListener('click', setPrize);
prizeInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') setPrize();
});
// Function to Set the Prize
function setPrize() {
const prizeInputValue = prizeInput.value.trim();
if (prizeInputValue === "") {
showAlert("Please enter a valid prize.");
return;
}
prize = prizeInputValue;
selectedPrize.textContent = Selected Prize: ${prize}
;
prizeInput.value = '';
}
// Spin Button Event
spinBtn.addEventListener('click', spinWheel);
// Function to Spin the Wheel
function spinWheel() {
if (isSpinning) return;
if (users.length === 0) {
showAlert("Please add at least one user.");
return;
}
if (prize === "None") {
showAlert("Please set a prize.");
return;
}
isSpinning = true;
spinBtn.disabled = true;
const spinAngleStart = Math.random() * 10 + 10;
const spinTimeTotal = Math.random() * 3000 + 4000;
let spinTime = 0;
function rotateWheel() {
spinTime += 30;
if (spinTime >= spinTimeTotal) {
stopRotateWheel();
return;
}
const spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal);
startAngle += (spinAngle * Math.PI / 180);
drawWheel();
requestAnimationFrame(rotateWheel);
}
rotateWheel();
}
// Function to Stop the Wheel and Announce Winner
function stopRotateWheel() {
const degrees = startAngle * 180 / Math.PI + 90;
const arcd = arc * 180 / Math.PI;
const index = Math.floor((360 - (degrees % 360)) / arcd) % users.length;
const winner = users[index];
showWinner(winner);
isSpinning = false;
spinBtn.disabled = false;
}
// Easing Function for Smooth Animation
function easeOut(t, b, c, d) {
t /= d;
t--;
return c * (t * t * t + 1) + b;
}
// Function to Display Alerts
function showAlert(message) {
alert(message);
}
// Function to Show Winner in Modal
function showWinner(winner) {
winnerText.textContent = ${winner} has won ${prize}! ๐
;
winnerModal.style.display = "block";
}
// Close Modal Events
closeBtn.addEventListener('click', () => {
winnerModal.style.display = "none";
});
window.addEventListener('click', (event) => {
if (event.target === winnerModal) {
winnerModal.style.display = "none";
}
});
// Share on Twitter
shareBtn.addEventListener('click', shareOnTwitter);
// Function to Share Winner on Twitter
function shareOnTwitter() {
const text = encodeURIComponent(๐ Congratulations to ${winnerText.textContent}! They have won ${prize}! ๐ฎ #Giveaway #Community
);
const url = encodeURIComponent('https://gladiatorsbattle.com');
const twitterUrl = https://twitter.com/intent/tweet?text=${text}&url=${url}
;
window.open(twitterUrl, '_blank');
}
// Initial Wheel Setup
initializeWheel();
๐งฉ Step-by-Step Development
1. Setting Up the HTML Structure
I began by outlining the basic structure of the application in index.html. This includes:
- Header: Displays the title of the raffle.
- Input Sections: Two main sections for adding participants and selecting a prize.
- Raffle Wheel: A canvas element where the wheel is drawn and animated.
- Modal: A popup that announces the winner.
- Footer: Promotes my website, LinkedIn, Twitter, and includes a Buy Me a Coffee button.
2. Styling with CSS
Using styles.css, I implemented a modern and sleek design:
- Glassmorphism: Achieved with semi-transparent backgrounds and backdrop-filter for a frosted glass effect.
- Animations: Added fadeIn for the container and bounce for the pointer to enhance interactivity.
- Responsive Design: Ensured that the layout adapts gracefully across various screen sizes using Flexbox and media queries.
- Accessibility: Incorporated focus states and high-contrast colors for better usability.
3. Adding Interactivity with JavaScript
In script.js, the core functionalities were implemented:
- Adding Participants: Users can input and add unique usernames to the raffle.
- Setting Prizes: Define the prize that participants can win.
- Drawing the Raffle Wheel: Utilized the Canvas API to draw a wheel divided into segments, each representing a participant.
- Spinning the Wheel: Implemented smooth spinning animations with easing functions to determine the winner.
- Announcing the Winner: Displayed the winner in a modal with an option to share the result on Twitter.
4. Integrating the Footer for Promotion
The footer serves as a promotional tool, connecting users to my online presence and providing a means to support the project financially:
- Social Links: Direct links to my website, LinkedIn, and Twitter profiles.
- Buy Me a Coffee: Encourages users to support my work through donations.
- ๐งฑ Challenges Faced and Solutions
1. Creating a Dynamic and Interactive Raffle Wheel
Challenge: Drawing and animating a responsive raffle wheel where each segment corresponds to a participant.
Solution: Leveraged the Canvas API to programmatically draw segments based on the number of participants. Each segment is assigned a distinct color for visual differentiation. JavaScript handles the rotation logic, ensuring smooth animations and accurately determining the winning segment based on the final angle.
2. Ensuring Smooth Animations and Performance
Challenge: Maintaining fluid animations, especially on devices with limited processing power.
Solution: Optimized JavaScript code to minimize computational overhead during animations. Utilized requestAnimationFrame for synchronized and efficient rendering of animation frames. Additionally, kept the canvas size reasonable and optimized the drawing functions to enhance performance.
3. Maintaining Accessibility and Usability
Challenge: Making the application accessible to users with different needs and ensuring an intuitive user experience.
Solution: Incorporated focus states for all interactive elements, ensuring keyboard navigability. Chose high-contrast color schemes to enhance readability. Structured the HTML semantically to assist screen readers in conveying information effectively.
๐ Results and Feedback
Since launching Modern Raffle 2024, the feedback has been overwhelmingly positive. Users appreciate the intuitive interface, the visually appealing design, and the seamless interactivity of the raffle wheel. The ability to share results on Twitter has also helped increase the visibility of giveaways, attracting more participants and fostering a sense of community engagement.
๐ Useful Links
View the Project in Action: Modern Raffle 2024 on https://codepen.io/HanGPIIIErr/pen/eYqqEeW
Website: https://gladiatorsbattle.com
LinkedIn: https://www.linkedin.com/in/pierre-romain-lopez/
Twitter: https://x.com/GladiatorsBT
Buy Me a Coffee: https://www.buymeacoffee.com/GladiatorsBattle
๐ Conclusion
Creating Modern Raffle 2024 was an enriching experience that combined modern web design principles with interactive functionalities. By leveraging HTML, CSS, and JavaScript, I developed a tool that not only serves its primary purpose of managing raffles but also engages users with its sleek design and smooth interactivity.
I encourage you to explore the project on CodePen, try it out for your own giveaways, and customize it to fit your specific needs. Whether you're a developer looking to enhance your portfolio or a community manager aiming to boost engagement, this raffle wheel is a versatile and effective solution.
Feel free to connect with me on my Website, LinkedIn, Twitter, or support my work through Buy Me a Coffee.
Happy Coding and Best of Luck with Your Raffles! ๐ฎโจ
Top comments (7)
This is kinda cool, you can showcase this project on devcanvas.
head to devcanvas.art to get started!
would love to see you there ๐!
ok!
Failed to load resource: the server responded with a status of 429 ()Understand this errorAI
12.04d29021.js:9 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'message')
at 12.04d29021.js:9:4224
at HTMLFormElement.l (forms.a09d450a.js:1:1456)
when i tryed to sign up
I fixed the issue, i recently enabled using magic link by default, and i forgot to ignore password validation on submit ๐
allright, i will try to put in on your plateform tonight or tomorow!
Thanks for trying it out, also notifying me about the issue ๐ซถ๐พ
Here is the version in my website, coupled with firebase, user can register into the raffle, and so
gladiatorsbattle.com/raffle
automatised :)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.