Reputation: 43
I use the wheel of fortune by Roko C. Buljan from here: how to draw a wheel of fortune?
This is my first experience with JS and canvas. I want to put long sentences in labels, but they go out of bounds.
I've tried using a flexible font, but that doesn't really work and isn't exactly what I need. I need the text inside each block to move to a new line and reduce the font if it goes out of bounds.
Can this be implemented in this code, or would I need to write everything from scratch?
const sectors = [
{color:"#f82", label:"parallellogram into parallellogram"},
{color:"#0bf", label:"10"},
{color:"#fb0", label:"StackStack StackStack"},
{color:"#0fb", label:"50"},
{color:"#b0f", label:"StackStackStackStackStackStack"},
{color:"#f0b", label:"Stack Stack"},
{color:"#bf0", label:"Stack"},
];
const rand = (m, M) => Math.random() * (M - m) + m;
const tot = sectors.length;
const EL_spin = document.querySelector("#spin");
const ctx = document.querySelector("#wheel").getContext('2d');
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
let angVel = 0; // Angular velocity
let ang = 0; // Angle in radians
const getIndex = () => Math.floor(tot - ang / TAU * tot) % tot;
function drawSector(sector, i) {
const ang = arc * i;
ctx.save();
// 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
ctx.translate(rad, rad);
ctx.rotate(ang + arc / 2);
ctx.textAlign = "right";
ctx.fillStyle = "#fff";
ctx.font = "bold 14px sans-serif";
ctx.fillText(sector.label, rad - 10, 10);
//
ctx.restore();
};
function rotate() {
const sector = sectors[getIndex()];
ctx.canvas.style.transform = `rotate(${ang - PI / 2}rad)`;
EL_spin.textContent = !angVel ? "SPIN" : sector.label;
EL_spin.style.background = sector.color;
}
function finishedSpinning() { // Called when the wheel stops spinning
const sector = sectors[getIndex()];
alert(sector.label);
}
function frame() {
if (!angVel) return;
const isSpinning = angVel > 0; // Check if the wheel is currently spinning
angVel *= friction; // Decrement velocity by friction
if (angVel < 0.002) angVel = 0; // Bring to stop
ang += angVel; // Update angle
ang %= TAU; // Normalize angle
rotate();
if (isSpinning && angVel === 0) { // If the wheel was spinning, but isn't anymore, it has just stopped
finishedSpinning();
}
}
function engine() {
frame();
requestAnimationFrame(engine)
}
// INIT
sectors.forEach(drawSector);
rotate(); // Initial rotation
engine(); // Start engine
EL_spin.addEventListener("click", () => {
if (!angVel) angVel = rand(0.25, 0.35);
});
#wheelOfFortune {
display: inline-block;
position: relative;
overflow: hidden;
}
#wheel {
display: block;
}
#spin {
font: 1.5em/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</div>
</div>
Solution: HTML5 canvas ctx.fillText won't do line breaks?
New problem - long words as in the first label
Upvotes: 3
Views: 106
Reputation: 43
Here's what I got, ready for any suggestions for improvement (mainly the part with text centering - line 89)
It would also be nice to add a shadow to the text to stand out against the background of bright colors
New problem - long words as in the first label
const sectors = [
{ color: "#0fb", label: "Параллелограмм в паралеллограмме" },
{ color: "#0bf", label: "Бесплатная настройка виджета" },
{ color: "#fb0", label: "Два пресета цветов по цене одного" },
{ color: "#0fb", label: "1строчка" },
{ color: "#b0f", label: "Год премиум поддержки в подарок в подарок" },
{ color: "#f0b", label: "Скидка 10% на любой пакет" },
{ color: "#bf0", label: "Виджет 'Juice Contact' в подарок" },
{ color: "#f82", label: "Скидка 5% на любой пакет" },
{ color: "#bf0", label: "" },
];
function printAtWordWrap(context, text, x, y, lineHeight, fitWidth) {
fitWidth = fitWidth || 0;
if (fitWidth <= 0) {
context.fillText(text, x, y);
return;
}
let words = text.split(" ");
let currentLine = 0;
let idx = 1;
while (words.length > 0 && idx <= words.length) {
const str = words.slice(0, idx).join(" ");
const w = context.measureText(str).width;
if (w > fitWidth) {
if (idx == 1) {
idx = 2;
}
context.fillText(
words.slice(0, idx - 1).join(" "),
x,
y + lineHeight * currentLine
);
currentLine++;
words = words.splice(idx - 1);
idx = 1;
} else {
idx++;
}
}
if (idx > 0)
context.fillText(words.join(" "), x, y + lineHeight * currentLine);
}
const rand = (m, M) => Math.random() * (M - m) + m;
const tot = sectors.length;
const EL_spin = document.querySelector("#spin");
const ctx = document.querySelector("#wheel").getContext("2d");
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
let angVel = 0; // Angular velocity
let ang = 0; // Angle in radians
const getIndex = () => Math.floor(tot - (ang / TAU) * tot) % tot;
// calcFontSize not used
const calcFontSize = () => {
const maxChars = Math.max(...sectors.map((ob) => ob.label.length));
const width = rad * 0.9 - 15;
const w = (width / maxChars) * 1.3;
const h = ((TAU * rad) / tot) * 0.5;
return Math.min(w, h);
};
function drawSector(sector, i) {
const ang = arc * i;
ctx.save();
// 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
ctx.translate(rad, rad);
ctx.rotate(ang + arc / 2);
ctx.textAlign = "center";
ctx.fillStyle = "#fff";
const fontSize = 15;
ctx.font = `bold ${fontSize}px sans-serif`;
// values for centering text - not ideal for now (need tuning)
const w = ctx.measureText(sector.label).width;
console.log(sector.label, w);
let y;
const lineWidth = 130; // width before line break
switch (true) {
case w < lineWidth:
y = fontSize / 3;
break;
case w >= lineWidth && w < 2 * lineWidth:
y = fontSize / 3 - 10;
break;
case w >= 2 * lineWidth && w < 3 * lineWidth:
y = fontSize / 3 - 20;
break;
case w >= 3 * lineWidth && w < 4 * lineWidth:
y = fontSize / 3 - 30;
break;
case w >= 4 * lineWidth:
y = fontSize / 3 - 40;
break;
default:
y = fontSize / 3;
}
printAtWordWrap(ctx, sector.label, rad * 0.7, y, 21, lineWidth);
ctx.restore();
}
function rotate() {
const sector = sectors[getIndex()];
ctx.canvas.style.transform = `rotate(${ang - PI / 2}rad)`;
EL_spin.textContent = !angVel
? "SPIN"
: ""; /* sector.label instead "" - if u want to display text inside spin element */
EL_spin.style.background = sector.color;
}
function finishedSpinning() {
// Called when the wheel stops spinning
const sector = sectors[getIndex()];
alert(sector.label);
}
function frame() {
if (!angVel) return;
const isSpinning = angVel > 0; // Check if the wheel is currently spinning
angVel *= friction; // Decrement velocity by friction
if (angVel < 0.002) angVel = 0; // Bring to stop
ang += angVel; // Update angle
ang %= TAU; // Normalize angle
rotate();
if (isSpinning && angVel === 0) {
// If the wheel was spinning, but isn't anymore, it has just stopped
finishedSpinning();
}
}
function engine() {
frame();
requestAnimationFrame(engine);
}
// INIT
sectors.forEach(drawSector);
rotate(); // Initial rotation
engine(); // Start engine
EL_spin.addEventListener("click", () => {
if (!angVel) angVel = rand(0.25, 0.35);
});
#wheelOfFortune {
display: inline-block;
position: relative;
overflow: hidden;
}
#wheel {
display: block;
}
#spin {
font: 1.5em/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;
}
<body style="background-color: darkcyan">
<div id="wheelOfFortune">
<canvas id="wheel" width="480" height="480"></canvas>
<div id="spin">SPIN</div>
</div>
</body>
Upvotes: 1