Я делаю меню для своей игры Судоку, чтобы выбирать числа для выбранных ячеек.
Мне удалось сделать круглое анимированное меню с X элементами холста, каждый из которых представляет одно число (+ один пуст). Затем я поворачиваю все эти элементы с помощью преобразования CSS, чтобы создать идеальный круг.
И тут возникает проблема - между каждым элементом некрасивые прозрачные штрихи, которые никак не получается убрать.
Вот как я это рисую:
Мое приложение немного сложное, но мне удалось создать демо:
let parent = document.getElementsByClassName('menu')[0];
let elSize = parent.getBoundingClientRect().width;
let upscale = 2;
let total = 10;
let length = elSize / 2;
for (let i = 0; i < total; i++) {
// create new canvas
let val = document.createElement('canvas');
val.classList.add("value");
let deg = 360 / total;
//set sizes and rotation
val.height = length * upscale;
val.width = elSize * upscale;
val.style.width = elSize + "px";
val.style.height = length + "px";
val.style.setProperty("--rotation", (i / total * 360) + "deg");
// get context
let ctx = val.getContext("2d");
ctx.fillStyle = "blue";
ctx.imageSmoothingEnabled = true;
// full circle center
let center = {
x: length * upscale,
y: length * upscale
}
//function to fill the circle part (step 1 and 2 on the image)
const fillWedge = (cx, cy, radius, startAngle, endAngle, fillcolor, stroke = false) => {
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.closePath();
if (stroke) {
ctx.lineWidth = 1;
ctx.strokeStyle = fillcolor;
ctx.stroke();
} else {
ctx.fillStyle = fillcolor;
ctx.fill();
}
}
const degToAngle = (deg) => {
let start = -Math.PI / 2;
let fullCircle = Math.PI * 2;
return (start + fullCircle * (deg / 360));
}
ctx.save();
ctx.imageSmoothingEnabled = false;
ctx.globalCompositeOperation = "source-out"; {
//smaller circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale / 3;
let startAngle = -((deg + 1) / 2) % 360;
let endAngle = ((deg + 1) / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
//make it semi-transparent
ctx.globalAlpha = 0.8; {
//bigger circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale;
let startAngle = -(deg / 2) % 360;
let endAngle = (deg / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
ctx.restore();
//draw text
if (i !== 0) {
ctx.save();
ctx.translate(length * upscale, length / 3 * upscale);
ctx.rotate(-(i / total * 360) / 180 * Math.PI);
ctx.font = "600 " + (18 * upscale) + 'px Consolas';
ctx.textAlign = "center";
ctx.fillStyle = "white";
ctx.fillText((i) + "", 0, 5 * upscale);
ctx.restore()
}
//add element to menu
parent.appendChild(val);
}html {
background: url(https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.cienradios.com%2Fwp-content%2Fuploads%2Fsites%2F13%2F2020%2F06%2FShrek-portada.jpg&f=1&nofb=1) no-repeat center;
display: flex;
justify-content: center;
align-content: center;
}
.menu {
width: 400px;
aspect-ratio: 1;
position: relative;
border-radius: 50%;
}
.menu .value {
--rotation: 0deg;
position: absolute;
top: 0;
bottom: 50%;
left: 50%;
transform-origin: bottom;
transform: translate(-50%, 0) rotate(var(--rotation));
}
p {
color: white;
}<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "UTF-8">
<title>Sudoku Test</title>
</head>
<body>
<div class = "menu">
</div>
<p>
See, it's semi-transparent and you can clearly see lines between elements
<br>
<b>How to fix it?!</b>
</p>
</body>
</html>Любой способ удалить эти пробелы? Я знаю, что это слишком подробный вопрос, но я действительно не могу его исправить. Спасибо.
@Kaiido Потому что я анимирую каждую ячейку с помощью CSS. Поэтому при отображении меню воспроизводится всплывающая анимация. Сделать это с одним холстом было бы довольно сложно. Можно как-нибудь убрать сглаживание?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Это вызвано сглаживанием. Поскольку вы рисуете по диагоналям, форма не попадает на полные границы пикселей. Таким образом, чтобы сгладить линии, браузер сделает те пиксели, которые должны были быть окрашены лишь частично, более прозрачными. При наложении эти прозрачные пиксели не будут в сумме точно соответствовать полной непрозрачности (0,5 непрозрачности + 0,5 непрозрачности = 0,75 непрозрачности, а не 1). Итак, вы увидите эти строки.
Удаление сглаживания здесь не поможет, потому что альтернативой было бы заполнение в стиле марширующего квадрата, но это привело бы либо к полным дырам в некоторых местах, либо к перекрывающимся пикселям, которые были бы видны, поскольку ваши фигуры не полностью непрозрачный.
Обычно дешевый трюк для решения этой проблемы состоит в том, чтобы обвести пару пикселей вокруг фигуры того же цвета, что и заливка. Но еще раз, поскольку ваши фигуры заполнены полупрозрачными цветами, этот трюк не сработает.
Вы можете схитрить, нарисовав все свои фигуры с полной непрозрачностью и применив прозрачность к обычному контейнеру. Но это означает, что вашим текстам потребуется собственный холст и собственный контейнер (иначе они тоже будут прозрачными).
let parent = document.getElementsByClassName('menu')[0];
const textsParent = document.getElementsByClassName('texts')[0];
let elSize = parent.getBoundingClientRect().width;
let upscale = 2;
let total = 10;
let length = elSize / 2;
for (let i = 0; i < total; i++) {
// create new canvas
let val = document.createElement('canvas');
val.classList.add("value");
let deg = 360 / total;
//set sizes and rotation
val.height = length * upscale;
val.width = elSize * upscale;
val.style.width = elSize + "px";
val.style.height = length + "px";
val.style.setProperty("--rotation", (i / total * 360) + "deg");
// get context
let ctx = val.getContext("2d");
ctx.fillStyle = "blue";
// full circle center
let center = {
x: length * upscale,
y: length * upscale
}
//function to fill the circle part (step 1 and 2 on the image)
const fillWedge = (cx, cy, radius, startAngle, endAngle, fillcolor, stroke = false) => {
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.closePath();
ctx.lineWidth = 2;
ctx.strokeStyle = fillcolor;
ctx.stroke();
ctx.fillStyle = fillcolor;
ctx.fill();
}
const degToAngle = (deg) => {
let start = -Math.PI / 2;
let fullCircle = Math.PI * 2;
return (start + fullCircle * (deg / 360));
}
ctx.save();
ctx.imageSmoothingEnabled = false;
{
//smaller circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale / 3;
let startAngle = -((deg + 1) / 2) % 360;
let endAngle = ((deg + 1) / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
{
//bigger circle
let cx = center.x;
let cy = center.y;
let radius = length * upscale;
let startAngle = -(deg / 2) % 360;
let endAngle = (deg / 2) % 360;
fillWedge(cx, cy, radius, degToAngle(startAngle), degToAngle(endAngle), ctx.fillStyle);
}
ctx.restore();
//draw text
if (i !== 0) {
// we need a new canvas just for the text
const val2 = val.cloneNode();
const ctx = val2.getContext("2d");
ctx.save();
ctx.translate(length * upscale, length / 3 * upscale);
ctx.rotate(-(i / total * 360) / 180 * Math.PI);
ctx.font = "600 " + (18 * upscale) + 'px Consolas';
ctx.textAlign = "center";
ctx.fillStyle = "white";
ctx.fillText((i) + "", 0, 5 * upscale);
ctx.restore()
textsParent.appendChild(val2);
}
//add element to menu
parent.appendChild(val);
}html {
background: url(https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdn.cienradios.com%2Fwp-content%2Fuploads%2Fsites%2F13%2F2020%2F06%2FShrek-portada.jpg&f=1&nofb=1) no-repeat center;
display: flex;
justify-content: center;
align-content: center;
}
.menu, .texts {
width: 400px;
aspect-ratio: 1;
position: relative;
border-radius: 50%;
}
.menu .value, .texts canvas {
--rotation: 0deg;
position: absolute;
top: 0;
bottom: 50%;
left: 50%;
transform-origin: bottom;
transform: translate(-50%, 0) rotate(var(--rotation));
}
.menu { opacity: 0.8 }
.text-container {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-content: center;
} <div class = "menu">
</div>
<div class = "text-container">
<div class = "texts">
</div>
</div>Поэтому вместо этого лучше всего полностью реорганизовать код, чтобы вместо этого использовать один холст. Если вы скажете браузеру отрисовывать все ваши фигуры в одном подпути, он сможет идеально разместить трассировку там, где она должна быть, и сможет отрисовать все это без какой-либо промежуточной линии:
const canvas = document.querySelector("canvas");
canvas.width = canvas.height = 500;
const ctx = canvas.getContext("2d");
const parts = 10;
const theta = Math.PI / (parts / 2);
const trace = (cx, cy, r1, r2, t) => {
ctx.moveTo(cx + r2, cy);
ctx.lineTo(cx + r1, cy);
ctx.arc(cx, cy, r1, 0, t);
ctx.arc(cx, cy, r2, t, 0, true);
};
ctx.translate(250, 250);
ctx.rotate(-Math.PI / 2 - theta);
// draw all but the last part
for (let i = 0; i < parts - 1; i++) {
ctx.rotate(theta);
trace(0, 0, 50, 200, theta);
}
ctx.globalAlpha = 0.8;
ctx.fillStyle = "blue";
ctx.fill(); // in a single pass
// draw the last part in red
ctx.fillStyle = "red";
ctx.rotate(theta);
ctx.beginPath();
trace(0, 0, 50, 200, theta);
ctx.fill();canvas {
/* checkered effect from https://stackoverflow.com/a/51054396/3702797 */
--tint:rgba(255,255,255,0.9);background-image:linear-gradient(to right,var(--tint),var(--tint)),linear-gradient(to right,black 50%,white 50%),linear-gradient(to bottom,black 50%,white 50%);background-blend-mode:normal,difference,normal;background-size:2em 2em;
}<canvas></canvas>Спасибо! Не могли бы вы описать, что вы делаете в методе trace, чтобы меньший круг исчез без globalCompositeOperation? Я хотел бы использовать ваш первый метод, но у меня не получается сделать заливку + обводку с правильным внешним видом, поэтому я проверил второй метод, и вы делаете это по-другому.
Ну, он буквально перемещается в точку на расстоянии r2 от центра, проводит линию вниз до r1, затем внутреннюю дугу и, наконец, внешнюю дугу, в порядке против часовой стрелки. Но вам следует избегать первого метода (на основе вашего), вы столкнетесь со многими проблемами на этом пути. Если они не подразумевают 3D-преобразования, ваши CSS-анимации могут быть легко выполнены в 2D-контексте.
Это сглаживание, мало что можно сделать с этим дизайном кода. Есть ли причины, по которым вы используете много холстов вместо одного? Используя один холст, вы можете сделать весь свой путь одним подпутем и избежать этой проблемы: jsfiddle.net/503ruyea