Hướng dẫn code vòng quay may mắn
Tạo vòng quay may mắn bằng HTML, CSS và JavaScript với giao diện trực quan, hiệu ứng quay mượt và kết quả ngẫu nhiên. Bạn có thể tùy chỉnh số ô, nội dung phần thưởng, màu sắc, tốc độ quay và xử lý kết quả sau khi quay. Các bước chính: HTML: Tạo vòng quay
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vòng Quay May Mắn - MUATHEMEWPGIARE.COM</title>
<style>
/* =========================================================
MUATHEMEWPGIARE.COM
LUCKY WHEEL
HTML + CSS + JAVASCRIPT
========================================================= */
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
font-family: Arial, Helvetica, sans-serif;
background:
radial-gradient(circle at 50% 0%, #42109a 0%, transparent 45%),
linear-gradient(135deg, #090014, #16002f 55%, #08000f);
color: #fff;
}
/* APP */
.lucky-wheel-app {
width: 100%;
max-width: 900px;
margin: 0 auto;
padding: 45px 20px;
text-align: center;
}
/* HEADER */
.lucky-header {
margin-bottom: 30px;
}
.lucky-header .brand {
display: inline-block;
margin-bottom: 10px;
font-size: 13px;
font-weight: 800;
letter-spacing: 3px;
color: #d9b6ff;
text-transform: uppercase;
}
.lucky-header h1 {
margin: 0;
font-size: clamp(28px, 5vw, 48px);
font-weight: 900;
line-height: 1.1;
background: linear-gradient(90deg, #fff, #d79cff, #fff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.lucky-header p {
margin: 14px auto 0;
max-width: 620px;
color: #c7b8d8;
font-size: 15px;
line-height: 1.7;
}
/* WHEEL AREA */
.wheel-area {
position: relative;
width: min(86vw, 560px);
aspect-ratio: 1;
margin: 35px auto;
}
/* GLOW */
.wheel-area::before {
content: "";
position: absolute;
inset: -25px;
border-radius: 50%;
background: rgba(125, 0, 255, .18);
filter: blur(35px);
z-index: 0;
}
/* CANVAS */
#wheelCanvas {
position: relative;
width: 100%;
height: 100%;
display: block;
border-radius: 50%;
z-index: 2;
box-shadow:
0 0 0 8px #210046,
0 0 0 12px #8736ff,
0 0 45px rgba(150, 60, 255, .65),
0 25px 70px rgba(0,0,0,.6);
}
/* POINTER */
.wheel-pointer {
position: absolute;
z-index: 10;
top: -5px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 22px solid transparent;
border-right: 22px solid transparent;
border-top: 48px solid #fff;
filter:
drop-shadow(0 0 8px #fff)
drop-shadow(0 5px 8px rgba(0,0,0,.7));
}
.wheel-pointer::after {
content: "";
position: absolute;
width: 13px;
height: 13px;
left: -6.5px;
top: -43px;
border-radius: 50%;
background: #ff2bd6;
box-shadow: 0 0 15px #ff2bd6;
}
/* CENTER BUTTON */
.wheel-center {
position: absolute;
z-index: 8;
width: 92px;
height: 92px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background:
radial-gradient(circle, #fff 0%, #eee 45%, #c9c9c9 100%);
border: 7px solid #ffcc00;
box-shadow:
0 0 0 5px #56159b,
0 0 25px rgba(255, 204, 0, .8),
inset 0 4px 10px rgba(255,255,255,.8);
color: #3b006d;
font-size: 14px;
font-weight: 900;
text-transform: uppercase;
user-select: none;
}
/* SPIN BUTTON */
.spin-button {
position: relative;
border: 0;
outline: none;
padding: 18px 50px;
border-radius: 999px;
cursor: pointer;
font-size: 17px;
font-weight: 900;
color: #fff;
background:
linear-gradient(135deg, #7b2cff, #d20cff);
box-shadow:
0 10px 30px rgba(142, 33, 255, .4),
inset 0 1px rgba(255,255,255,.35);
transition:
transform .2s ease,
box-shadow .2s ease,
opacity .2s ease;
}
.spin-button:hover {
transform: translateY(-3px) scale(1.02);
box-shadow:
0 15px 40px rgba(173, 55, 255, .6),
inset 0 1px rgba(255,255,255,.45);
}
.spin-button:active {
transform: translateY(1px) scale(.98);
}
.spin-button:disabled {
cursor: not-allowed;
opacity: .55;
}
/* RESULT */
.result-box {
margin: 30px auto 0;
max-width: 500px;
padding: 22px;
border-radius: 20px;
background: rgba(255,255,255,.06);
border: 1px solid rgba(255,255,255,.1);
backdrop-filter: blur(12px);
box-shadow: 0 15px 50px rgba(0,0,0,.25);
}
.result-label {
color: #bca9ce;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 2px;
}
.result-prize {
margin-top: 7px;
font-size: clamp(24px, 5vw, 38px);
font-weight: 900;
color: #ffd84d;
text-shadow:
0 0 15px rgba(255,216,77,.5);
}
/* FOOTER */
.lucky-footer {
margin-top: 35px;
color: #88799a;
font-size: 12px;
letter-spacing: .5px;
}
.lucky-footer strong {
color: #c18cff;
}
/* MOBILE */
@media(max-width:600px) {
.lucky-wheel-app {
padding: 30px 14px;
}
.wheel-area {
width: min(88vw, 430px);
}
.wheel-center {
width: 72px;
height: 72px;
font-size: 11px;
border-width: 5px;
}
.wheel-pointer {
border-left-width: 17px;
border-right-width: 17px;
border-top-width: 38px;
}
.spin-button {
width: 100%;
max-width: 330px;
}
}
</style>
</head>
<body>
<div class="lucky-wheel-app">
<header class="lucky-header">
<div class="brand">
MUATHEMEWPGIARE.COM
</div>
<h1>🎁 Vòng Quay May Mắn</h1>
<p>
Quay thử vận may và nhận ngay phần thưởng hấp dẫn.
Giao diện hiện đại, responsive và chạy hoàn toàn bằng HTML,
CSS và JavaScript.
</p>
</header>
<div class="wheel-area">
<div class="wheel-pointer"></div>
<canvas id="wheelCanvas"></canvas>
<div class="wheel-center">
QUAY
</div>
</div>
<button
type="button"
class="spin-button"
id="spinButton"
>
🎯 QUAY NGAY
</button>
<div class="result-box">
<div class="result-label">
Kết quả
</div>
<div
class="result-prize"
id="resultPrize"
>
Chúc bạn may mắn!
</div>
</div>
<div class="lucky-footer">
Vòng quay được phát triển bởi
<strong>MUATHEMEWPGIARE.COM</strong>
</div>
</div>
<script>
/* =========================================================
MUATHEMEWPGIARE.COM
LUCKY WHEEL JAVASCRIPT
========================================================= */
(function () {
"use strict";
/* -----------------------------------------------------
CẤU HÌNH PHẦN THƯỞNG
----------------------------------------------------- */
const prizes = [
"GIẢM 5%",
"GIẢM 10%",
"GIẢM 15%",
"GIẢM 20%",
"GIẢM 25%",
"GIẢM 30%",
"GIẢM 35%",
"GIẢM 40%"
];
/* -----------------------------------------------------
MÀU VÒNG QUAY
----------------------------------------------------- */
const colors = [
"#7B2CFF",
"#FF2D95",
"#FF6B00",
"#FFC400",
"#00C853",
"#00B8D9",
"#304FFE",
"#9C27B0"
];
/* -----------------------------------------------------
DOM
----------------------------------------------------- */
const canvas =
document.getElementById("wheelCanvas");
const ctx =
canvas.getContext("2d");
const spinButton =
document.getElementById("spinButton");
const resultPrize =
document.getElementById("resultPrize");
/* -----------------------------------------------------
BIẾN
----------------------------------------------------- */
let rotation = 0;
let spinning = false;
let animationFrame = null;
const TWO_PI = Math.PI * 2;
/* -----------------------------------------------------
CANVAS RESPONSIVE
----------------------------------------------------- */
function resizeCanvas() {
const rect =
canvas.getBoundingClientRect();
const size =
Math.min(rect.width, rect.height);
const dpr =
Math.max(1, window.devicePixelRatio || 1);
canvas.width =
size * dpr;
canvas.height =
size * dpr;
ctx.setTransform(
dpr,
0,
0,
dpr,
0,
0
);
drawWheel(size);
}
/* -----------------------------------------------------
VẼ VÒNG QUAY
----------------------------------------------------- */
function drawWheel(size) {
const center =
size / 2;
const radius =
size / 2 - 7;
ctx.clearRect(
0,
0,
size,
size
);
/* vòng ngoài */
ctx.beginPath();
ctx.arc(
center,
center,
radius,
0,
TWO_PI
);
ctx.fillStyle =
"#fff";
ctx.fill();
/* các ô */
const segmentAngle =
TWO_PI / prizes.length;
for (
let i = 0;
i < prizes.length;
i++
) {
const startAngle =
rotation +
i * segmentAngle -
Math.PI / 2;
const endAngle =
startAngle +
segmentAngle;
/* segment */
ctx.beginPath();
ctx.moveTo(
center,
center
);
ctx.arc(
center,
center,
radius,
startAngle,
endAngle
);
ctx.closePath();
ctx.fillStyle =
colors[i % colors.length];
ctx.fill();
/* viền */
ctx.strokeStyle =
"rgba(255,255,255,.8)";
ctx.lineWidth = 2;
ctx.stroke();
/* text */
const middleAngle =
startAngle +
segmentAngle / 2;
ctx.save();
ctx.translate(
center,
center
);
ctx.rotate(
middleAngle
);
ctx.textAlign =
"right";
ctx.textBaseline =
"middle";
let fontSize =
Math.max(
12,
Math.min(
22,
size / 22
)
);
ctx.font =
"900 " +
fontSize +
"px Arial";
ctx.fillStyle =
"#ffffff";
ctx.shadowColor =
"rgba(0,0,0,.45)";
ctx.shadowBlur = 4;
ctx.fillText(
prizes[i],
radius - 18,
0
);
ctx.restore();
}
/* vòng trong */
ctx.beginPath();
ctx.arc(
center,
center,
radius * .12,
0,
TWO_PI
);
ctx.fillStyle =
"#ffffff";
ctx.fill();
ctx.beginPath();
ctx.arc(
center,
center,
radius * .075,
0,
TWO_PI
);
ctx.fillStyle =
"#741EFF";
ctx.fill();
/* viền ngoài */
ctx.beginPath();
ctx.arc(
center,
center,
radius,
0,
TWO_PI
);
ctx.strokeStyle =
"#ffffff";
ctx.lineWidth = 6;
ctx.stroke();
}
/* -----------------------------------------------------
RANDOM
----------------------------------------------------- */
function random() {
if (
window.crypto &&
window.crypto.getRandomValues
) {
const buffer =
new Uint32Array(1);
window.crypto.getRandomValues(
buffer
);
return (
buffer[0] /
4294967296
);
}
return Math.random();
}
/* -----------------------------------------------------
EASING
QUAY NHANH → CHẬM DẦN
----------------------------------------------------- */
function easeOutCubic(t) {
return 1 -
Math.pow(
1 - t,
3
);
}
/* -----------------------------------------------------
SPIN
----------------------------------------------------- */
function spin() {
if (spinning) {
return;
}
spinning = true;
spinButton.disabled = true;
resultPrize.textContent =
"Đang quay...";
/*
* Chọn kết quả TRƯỚC KHI animation bắt đầu.
*/
const winnerIndex =
Math.floor(
random() * prizes.length
);
const segmentAngle =
TWO_PI / prizes.length;
/*
* Tâm của ô chiến thắng.
*/
const winnerCenter =
winnerIndex *
segmentAngle +
segmentAngle / 2;
/*
* Pointer nằm ở vị trí 12 giờ.
*
* Tính góc cần quay để ô winner
* nằm đúng dưới pointer.
*/
const targetRotation =
TWO_PI -
winnerCenter;
/*
* Thêm 5 - 7 vòng quay.
*/
const extraSpins =
5 +
Math.floor(
random() * 3
);
const currentNormalized =
rotation % TWO_PI;
let delta =
targetRotation -
currentNormalized;
if (delta < 0) {
delta += TWO_PI;
}
delta +=
extraSpins * TWO_PI;
/*
* Thời gian animation
*/
const duration =
5200;
const startRotation =
rotation;
const startTime =
performance.now();
function animate(now) {
const elapsed =
now -
startTime;
const progress =
Math.min(
elapsed / duration,
1
);
const eased =
easeOutCubic(
progress
);
rotation =
startRotation +
delta * eased;
const size =
canvas.getBoundingClientRect().width;
drawWheel(size);
if (progress < 1) {
animationFrame =
requestAnimationFrame(
animate
);
} else {
rotation =
startRotation +
delta;
drawWheel(size);
finishSpin(
winnerIndex
);
}
}
animationFrame =
requestAnimationFrame(
animate
);
}
/* -----------------------------------------------------
KẾT THÚC
----------------------------------------------------- */
function finishSpin(
winnerIndex
) {
spinning = false;
spinButton.disabled = false;
resultPrize.textContent =
"🎉 " +
prizes[winnerIndex];
/*
* Hiệu ứng nút.
*/
spinButton.animate(
[
{
transform:
"scale(1)"
},
{
transform:
"scale(1.08)"
},
{
transform:
"scale(1)"
}
],
{
duration: 450,
easing: "ease-out"
}
);
}
/* -----------------------------------------------------
EVENTS
----------------------------------------------------- */
spinButton.addEventListener(
"click",
spin
);
/* Cho phép click vào tâm để quay */
document
.querySelector(".wheel-center")
.addEventListener(
"click",
spin
);
window.addEventListener(
"resize",
resizeCanvas
);
/* -----------------------------------------------------
INIT
----------------------------------------------------- */
resizeCanvas();
})();
</script>
</body>
</html>
Không có CSS code
Không có JavaScript code
Ctrl1 HTML
Ctrl2 CSS
Ctrl3 JS
Ctrl↵ Reload