如何使切片可点击并很好地旋转文本

问题描述 投票:0回答:1

我正在创造一个命运之轮。当用户启动时,它会旋转,当它停止时,会弹出一个赢得切片的模式。一切都运行良好,但现在我想补充一点,当用户单击切片时,当轮子不旋转时,它会再次打开带有所单击切片的文本和 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">&times;</span>
          <div class="image-overlay"></div>
          <p id="winningText"></p>
        </div>
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>
javascript html css canvas html5-canvas
1个回答
0
投票

这里是使用

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>

© www.soinside.com 2019 - 2024. All rights reserved.