我正在创造一个命运之轮。当用户启动时,它会旋转,当它停止时,会弹出一个赢得切片的模式。一切都运行良好,但现在我想补充一点,当用户单击切片时,当轮子不旋转时,它会再次打开带有所单击切片的文本和 bgImage 的模式。我怎样才能做到这一点,数学不是我最强的部分哈哈?
我还希望切片的文本始终以相同的方式旋转,我怎样才能实现这一点?
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const cx = canvas.width / 2;
const cy = canvas.height / 2;
const wheelRadius = 96 * (canvas.width / 200);
let spinning = false;
let stopRequested = false;
let useImagesForSlices = false;
const backgroundImage = new Image();
const patternCanvas = document.createElement("canvas");
const patternContext = patternCanvas.getContext("2d");
let currentRotation = 0;
let startingRotation = 3;
let loadedImages = {};
const results = [
{
name: "Lyoner Wurst aus dem Saarland",
image:
"https://www.berlin.de/binaries/asset/image_assets/8215661/ratio_4_3/1686824224/800x600/",
},
{
name: "asda",
image:
"https://www.berlin.de/binaries/asset/image_assets/8215661/ratio_4_3/1686824224/800x600/",
},
{
name: "aewqe",
image:
"https://www.berlin.de/binaries/asset/image_assets/8215661/ratio_4_3/1686824224/800x600/",
},
{
name: "fsdfq",
image:
"https://www.berlin.de/binaries/asset/image_assets/8215661/ratio_4_3/1686824224/800x600/",
},
];
let imagesToLoad = results.length;
results.forEach((result, index) => {
let img = new Image();
img.onload = () => {
loadedImages[result.name] = img;
if (--imagesToLoad === 0) {
drawWheel();
}
};
img.src = result.image;
});
function startWheel() {
if (!spinning) {
spinning = true;
stopRequested = false;
drawStartButton();
setupTimer();
window.requestAnimationFrame(step);
}
}
function stopWheel() {
if (spinning) {
stopRequested = true;
const resultIndex = Math.floor(Math.random() * results.length);
drawStartButton();
endAngle = randomNumber(
((2 * Math.PI) / results.length) * (results.length - resultIndex - 1) +
stepSize,
((2 * Math.PI) / results.length) * (results.length - resultIndex) -
stepSize
);
}
}
let maxCount, count, count2, timerStart, angle, endAngle;
const tps = 60;
const stepSize = (2 * Math.PI) / tps;
function setupTimer() {
count = 0;
maxCount = Math.random() * 5 * tps + 2 * tps;
angle = -((2 * Math.PI) / results.length / 2);
}
function wrapText(context, text, x, y, maxWidth, lineHeight) {
var words = text.split(" ");
var line = "";
for (var n = 0; n < words.length; n++) {
var testLine = line + words[n] + " ";
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
context.fillText(line, x, y);
line = words[n] + " ";
y += lineHeight;
} else {
line = testLine;
}
}
context.fillText(line, x, y);
}
function step(timestamp) {
if (timerStart === undefined) timerStart = timestamp;
const elapsed = timestamp - timerStart;
if (elapsed > 1000 / tps) {
timerStart = timestamp;
// If stop has been requested, begin slowing down
if (stopRequested) {
const angleInCircle = angle % (2 * Math.PI);
const diff =
mod(
endAngle -
(angleInCircle > endAngle
? angleInCircle - 2 * Math.PI
: angleInCircle),
2 * Math.PI
) /
(2 * Math.PI);
angle += Math.max(0.005, Math.min(1, diff + 0.1) * stepSize);
// Check if it should completely stop
// Check if it should completely stop
// Check if it should completely stop
if (
Math.abs((angle % (2 * Math.PI)) - endAngle) < stepSize &&
angle % (2 * Math.PI) > endAngle
) {
spinning = false;
drawStartButton();
// Calculate the angle per slice
let anglePerSlice = (2 * Math.PI) / results.length;
// Calculate the index of the winning slice
let winningSliceIndex =
results.length -
Math.floor((angle % (2 * Math.PI)) / anglePerSlice) -
1;
// Display the result
showModal(results[winningSliceIndex]);
return;
}
} else {
// Continue spinning at a constant speed
angle += stepSize;
}
drawWheel(angle);
}
window.requestAnimationFrame(step);
}
function drawArrow() {
ctx.fillStyle = "#d10019"; // Set the color of the arrow
ctx.beginPath();
// Define the points of the triangle
const arrowX = canvas.width / 2;
const arrowBaseY = 18; // Y position for the base of the arrow, closer to the top
const arrowTipY = arrowBaseY + 50; // Y position for the tip, lower than the base
const arrowWidth = 40;
// Draw the triangle (arrow)
ctx.moveTo(arrowX, arrowTipY); // Tip of the arrow
ctx.lineTo(arrowX - arrowWidth / 2, arrowBaseY); // Top left of the arrow base
ctx.lineTo(arrowX + arrowWidth / 2, arrowBaseY); // Top right of the arrow base
ctx.lineTo(arrowX, arrowTipY); // Back to the tip of the arrow
ctx.closePath();
ctx.fill();
}
function drawWheel(rotatedBy) {
startingRotation = rotatedBy;
const textRadius = wheelRadius * 0.7;
ctx.save();
ctx.translate(cx, cy);
ctx.rotate((rotatedBy || (-2 * Math.PI) / results.length / 2) - Math.PI / 2);
for (let i = 0; i < results.length; i++) {
// Draw the segment
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.arc(
0,
0,
wheelRadius,
((2 * Math.PI) / results.length) * i,
((2 * Math.PI) / results.length) * (i + 1),
false
);
ctx.closePath();
// Clip to the current segment
ctx.save();
ctx.clip();
// Draw the image for this segment
if (useImagesForSlices && loadedImages[results[i].name]) {
// Draw the image for this segment
let img = loadedImages[results[i].name];
ctx.drawImage(
img,
-wheelRadius,
-wheelRadius,
wheelRadius * 2,
wheelRadius * 2
);
} else {
// Draw a solid color for this segment
ctx.fillStyle = "blue"; // Change to any color you like
ctx.fill();
}
ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; // Semi-transparent black
ctx.fillRect(-wheelRadius, -wheelRadius, wheelRadius * 2, wheelRadius * 2);
ctx.restore();
ctx.lineWidth = 5;
ctx.strokeStyle = "white";
ctx.stroke();
ctx.save();
ctx.font = "20px Arial";
ctx.fillStyle = "white";
ctx.translate(
textRadius * Math.cos(((2 * Math.PI) / results.length) * (i + 0.5)),
textRadius * Math.sin(((2 * Math.PI) / results.length) * (i + 0.5))
);
ctx.rotate(((2 * Math.PI) / results.length) * i - 0.5 * Math.PI);
ctx.textAlign = "center";
const maxWidth = wheelRadius / 1.5;
const lineHeight = 20;
wrapText(ctx, results[i].name, 0, 10, maxWidth, lineHeight);
ctx.restore();
}
ctx.restore();
drawStartButton();
ctx.restore();
drawArrow();
}
function randomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function mod(n, m) {
return ((n % m) + m) % m;
}
function isClickOnBackgroundImage(x, y) {
// Define the area where the background image is located
// This depends on how you've drawn your background image.
// Assuming the image is at the center of the canvas:
let centerX = canvas.width / 2;
let centerY = canvas.height / 2;
let radius = 50; // Assuming the image has a radius of 50 pixels
let dx = x - centerX;
let dy = y - centerY;
return dx * dx + dy * dy <= radius * radius;
}
canvas.addEventListener("click", function (event) {
let rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.left;
let y = event.clientY - rect.top;
if (isClickOnBackgroundImage(x, y)) {
if (spinning) {
stopWheel();
} else {
startWheel();
}
}
});
function drawStartButton() {
ctx.save();
ctx.translate(cx, cy);
// Create a gradient for a 3D effect
let gradient = ctx.createRadialGradient(0, 0, 10, 0, 0, 50);
gradient.addColorStop(0, "#ff6666"); // Start with white at the center
gradient.addColorStop(0.4, "#ff3333"); // Transition to red
gradient.addColorStop(0.7, "darkred"); // Dark red for depth
gradient.addColorStop(1, "black");
// Draw the red circle with a shadow for depth
ctx.beginPath();
ctx.arc(0, 0, 50, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 5;
ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
ctx.fill();
ctx.stroke();
// Reset shadow for the text
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;
// Draw "Start" or "Stop" text based on the wheel's state
ctx.font = "20px Arial";
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
let buttonText = spinning ? "Stop" : "Start";
ctx.fillText(buttonText, 0, 0);
ctx.restore();
}
function showModal(winningSlice) {
if (useImagesForSlices) {
document.getElementById(
"winningImage"
).style.backgroundImage = `url('${winningSlice.image}')`;
} else {
document.getElementById("winningImage").style.background = "blue";
}
document.getElementById("winningText").textContent = winningSlice.name;
document.getElementById("resultModal").style.display = "block";
document.body.style.overflow = "hidden";
}
document.getElementById("closeModal").addEventListener("click", closeModal);
document
.getElementById("resultModal")
.addEventListener("click", function (event) {
if (event.target === this) {
closeModal();
}
});
function closeModal() {
document.getElementById("resultModal").style.display = "none";
// Re-enable scrolling on the body
document.body.style.overflow = "";
}
#resultModal {
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
}
.modal-content {
margin: 15% auto;
padding: 20px;
width: 80%;
}
#winningImage {
width: 100%;
height: 500px; /* Set a height for the div */
position: relative; /* This enables absolute positioning for child elements */
background-size: cover;
background-position: center;
animation: scaleAndFadeIn 1s ease;
}
.image-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5); /* Dark tint */
z-index: 1; /* Ensure it's above the background image but below the text */
}
#winningText {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
font-size: 40px;
color: white;
display: flex;
align-items: center; /* Centers text vertically */
justify-content: center; /* Centers text horizontally */
text-align: center;
z-index: 2; /* Ensures the text is above the overlay */
margin: 0; /* Remove default margins */
padding: 15px; /* Add some padding */
}
#closeModal {
position: absolute;
top: 10px;
right: 15px;
color: white; /* White color for visibility */
font-size: 30px; /* Increase the size */
font-weight: bold;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.6); /* Semi-transparent background */
border-radius: 15px; /* Rounded corners */
padding: 5px 10px; /* Some padding for better touch area */
z-index: 3; /* Ensure it's above all other content */
}
#closeModal:hover {
background-color: rgba(0, 0, 0, 0.8); /* Darken background on hover */
}
@keyframes scaleAndFadeIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>Lucky Spin</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
<div id="resultModal" style="display: none">
<div class="modal-content">
<div id="winningImage">
<span id="closeModal" style="cursor: pointer">×</span>
<div class="image-overlay"></div>
<p id="winningText"></p>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
这里是使用
isPointInPath
检测切片中点击的示例代码
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const cx = canvas.width / 2;
const cy = canvas.height / 2;
let currentRotation = 0;
const slices = [{name: "red"}, {name: "blue"}, {name: "cyan"}, {name: "green"}];
function drawWheel(rotatedBy) {
const arc = 2 * Math.PI / slices.length
for (let i = 0; i < slices.length; i++) {
path = new Path2D()
path.moveTo(cx, cy);
path.arc(cx, cy, 100, arc * i + rotatedBy, arc * (i + 1) + rotatedBy);
slices[i].path = path
ctx.fillStyle = slices[i].name;
ctx.fill(path);
}
}
canvas.addEventListener("click", function(e) {
let r = canvas.getBoundingClientRect();
for (let i = 0; i < slices.length; i++) {
if (ctx.isPointInPath(slices[i].path, e.clientX - r.left, e.clientY - r.top))
console.log(slices[i].name)
}
});
function draw() {
currentRotation += 0.02
ctx.clearRect(0,0, canvas.width, canvas.height)
drawWheel(currentRotation)
}
setInterval(draw, 50)
<canvas id="canvas" width="200" height="200"></canvas>