1

I'm trying to create a wheel of fortune with images instead of text to display the prices.

I am using ctx.fillText(sector.label, rad - 50, 10); to fill all prices with their text label, but I'd like to replace the text with an image intead!

However, when I do ctax.drawImage(sector.image, rad - 50, 10), all the images gets rendered on the same spot.

View JSFiddle

I tried to add a loadImage function like so:

  const loadImage = (ctx, sector, ang) => {
      const img = new Image();
      img.onload = function () {
        ctx.drawImage(img, rad - 50, 10); // Or at whatever offset you like
      };
      img.src = sector.image;
      };

which works, but it applies all the images on the same spot.

Why is it that ctx.fillText() positions the text correctly, but images are not?

I saw How to draw image on wheel of fortune?, but I'd like to use a canvas instead of css.

nudua
  • 23
  • 3

1 Answers1

0

The issue is that your images are loading asynchronously. That means that by the time they are placed on the canvas, the transformation has been changed. They will all end up in the last slice.

A possible solution is to pass the current displacement and rotation to the drawing function. You can then reapply the transform required for the image, independent of the loading order.

Here's an example implementation:

const sectors = [
  {color:"#b0f", label:"100",  image:"https://i.pinimg.com/originals/0f/74/64/0f7464a556edc8b48d43a8bb604dbc33.png"},
  {color:"#f0b", label:"5",  image:"https://i.pinimg.com/originals/2a/19/13/2a1913368715b9a2dc1ac75e0f1fd67c.png"},
  {color:"#bf0", label:"500",  image:"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTwhD7Fr24H5ogfjKHDukeqFd4BN2H2f8KPPP3WEa3LQRw_HhLWHSHw_Nr7CSWkME6XQXY&usqp=CAU"},
];

// Generate random float in range min-max:
const rand = (m, M) => Math.random() * (M - m) + m;

const tot = sectors.length;
const elSpin = document.querySelector("#spin");
const ctx = document.querySelector("#wheel").getContext`2d`;

ctx.canvas.width = 600;
ctx.canvas.height = 600;
    
const dia = ctx.canvas.width;

    
const rad = dia / 2;
const PI = Math.PI;
const TAU = 2 * PI;
const arc = TAU / sectors.length;
const friction = 0.991;  // 0.995=soft, 0.99=mid, 0.98=hard
const angVelMin = 0.002; // Below that number will be treated as a stop
let angVelMax = 0; // Random ang.vel. to acceletare to 
let angVel = 0;    // Current angular velocity
let ang = 0;       // Angle rotation in radians
let isSpinning = false;
let isAccelerating = false;

//* Get index of current sector */
const getIndex = () => Math.floor(tot - ang / TAU * tot) % tot;

const loadImage = (ctx, sector, rad, rot) => {
  const img = new Image();
  img.onload = function () {
    ctx.save();
    ctx.resetTransform();

    ctx.translate(rad, rad);
    ctx.rotate(rot);
    ctx.drawImage(img, 150, -25, 50, 50); // Or at whatever offset you like
    ctx.restore();
  };
  img.src = sector.image;
};
    
//* Draw sectors and prizes texts to canvas */
const drawSector = (sector, i) => {
  const ang = arc * i;
  // COLOR
  ctx.beginPath();
  ctx.fillStyle = sector.color;
  ctx.moveTo(rad, rad);
  ctx.arc(rad, rad, rad, ang, ang + arc);
  ctx.lineTo(rad, rad);
  ctx.fill();
  
  // TEXT
  const rot = ang + arc / 2;
  ctx.save();
  ctx.translate(rad, rad);
  ctx.rotate(rot);
  ctx.textAlign = "right";
  ctx.fillStyle = "#fff";
  ctx.font = "bold 30px sans-serif";
  ctx.fillText(sector.label, rad - 10, 10);
  
  // IMG
  loadImage(ctx, sector, rad, rot);

  ctx.restore();
};

//* CSS rotate CANVAS Element */
const rotate = () => {
  const sector = sectors[getIndex()];
  ctx.canvas.style.transform = `rotate(${ang - PI / 2}rad)`;
  elSpin.textContent = !angVel ? "SPIN" : sector.label;
  elSpin.style.background = sector.color;
};

const frame = () => {

  if (!isSpinning) return;

  if (angVel >= angVelMax) isAccelerating = false;

  // Accelerate
  if (isAccelerating) {
    angVel ||= angVelMin; // Initial velocity kick
    angVel *= 1.06; // Accelerate
  }
  
  // Decelerate
  else {
    isAccelerating = false;
    angVel *= friction; // Decelerate by friction  

    // SPIN END:
    if (angVel < angVelMin) {
      isSpinning = false;
      angVel = 0; 
    }
  }

  ang += angVel; // Update angle
  ang %= TAU;    // Normalize angle
  rotate();      // CSS rotate!
};

const engine = () => {
  frame();
  requestAnimationFrame(engine)
};

elSpin.addEventListener("click", () => {
  if (isSpinning) return;
  isSpinning = true;
  isAccelerating = true;
  angVelMax = rand(0.25, 0.40);
});

// INIT!
sectors.forEach(drawSector);
rotate(); // Initial rotation
engine(); // Start engine!
#wheelOfFortune {
  display: inline-flex;
  position: relative;
  /* height: 720px;
  width: 720px; */
  overflow: hidden;
}

#wheel {
  display: block;
}

#spin {
  font: 1.5rem/0 sans-serif;
  user-select: none;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  position: absolute;
  top: 50%;
  left: 50%;
  width: 30%;
  height: 30%;
  margin: -15%;
  background: #fff;
  color: #fff;
  box-shadow: 0 0 0 8px currentColor, 0 0px 15px 5px rgba(0, 0, 0, 0.6);
  border-radius: 50%;
  transition: 0.8s;
}

#spin::after {
  content: '';
  position: absolute;
  top: -17px;
  border: 10px solid transparent;
  border-bottom-color: currentColor;
  border-top: none;
}
<div id="wheelOfFortune">
  <canvas id="wheel" width="300" height="300"></canvas>
  <div id="spin">SPIN asd asd asd as dasd as dasd asd asd as d</div>
</div>
user3297291
  • 22,592
  • 4
  • 29
  • 45