I have the following SVG element:
<svg
viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
aria-label="The Moon"
role="img"
unselectable="on"
tabindex="-1"
draggable="false"
class="absolute -z-10 top-full right-full"
data-phase="0.9033397192448547"
data-age="26.676153687326483"
>
<path d="m500,0 a10,10 0 1,1 0,1000 a10,10 0 1,1 0,-1000" fill="rgba(255, 255, 255)"></path>
<path d="m500,0 a10,10 0 1,1 0,1000 a10,10 0 1,1 0,-1000" fill="rgba(31, 41, 55, 0.60)"></path>
<path d="m500,0 a 6.133588769794187,10 0 1, 0 0,1000 a10,10 0 1, 1 0,-1000" fill="#fff"></path>
</svg>
However, I was wondering ... it's possible to draw these paths using:
ctx.beginPath()
const p = new Path2D(
`m${radius},0 a 6.133588769794187,10 0 1, 0 0,${radius * 2} a10,10 0 1, 1 0,${radius * -2}`
)
p.moveTo(x - radius / 2, y - radius / 2)
ctx.stroke(p)
ctx.fill(p)
ctx.closePath()
However, it doesn't seem to want to draw the path at the position [x,y] that I am specifying, it simply places it at the top left hand corner of my canvas ...
I can't seem to reference how to place a 2D path at a certain x and y coordinate ...
I've also tried this, but to equally dismal results:
ctx.strokeStyle = '#fff'
ctx.lineWidth = 1
ctx.fillStyle = '#fff'
const p = new Path2D()
p.moveTo(x - radius / 2, y - radius / 2)
p.addPath(
new Path2D(
`m${radius},0 a 6.133588769794187,10 0 1, 0 0,${radius * 2} a10,10 0 1, 1 0,${radius * -2}`
)
)
ctx.stroke(p)
ctx.fill(p)
ctx.closePath()
Any guiding light would be amazing!
CodePudding user response:
The relative commands of your path declaration won't work as you expected here.
They will be parsed at the creation of the inner Path2D, which doesn't have an end-point yet and thus they will all be relative to the initial 0,0
point.
Once this inner Path2D is created, the fact that the declaration was using relative methods will be lost, all these points are internally converted to absolute coordinates. So the last point of the final Path2D on which you'll call addPath(path)
won't matter.
To do what you want you have a few solutions:
- You could prepend a
Mx,y
to your path declaration:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeStyle = '#fff'
ctx.lineWidth = 1
ctx.fillStyle = '#fff'
const radius = 50;
const x = 150;
const y = 70;
const p = new Path2D(`M${x - radius / 2},${y - radius / 2}m${radius},0 a 6.133588769794187,10 0 1, 0 0,${radius * 2} a10,10 0 1, 1 0,${radius * -2}`);
ctx.stroke(p)
ctx.fill(p)
body { background: #333; }
<canvas></canvas>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
Though going this way you could even remove the following m
and merge it into the M
calculation:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeStyle = '#fff'
ctx.lineWidth = 1
ctx.fillStyle = '#fff'
const radius = 50;
const x = 150;
const y = 70;
const p = new Path2D(`M${x - radius / 2 radius},${y - radius / 2}a 6.133588769794187,10 0 1, 0 0,${radius * 2} a10,10 0 1, 1 0,${radius * -2}`);
ctx.stroke(p)
ctx.fill(p)
body { background: #333; }
<canvas></canvas>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
Or you could use the context transform matrix (CTM) to move where the path is actually painted:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeStyle = '#fff'
ctx.lineWidth = 1
ctx.fillStyle = '#fff'
const radius = 50;
const x = 150;
const y = 70;
const p = new Path2D(`m${radius},0 a 6.133588769794187,10 0 1, 0 0,${radius * 2} a10,10 0 1, 1 0,${radius * -2}`);
// set x, y through CTM
ctx.translate(x - radius / 2, y - radius / 2);
ctx.stroke(p)
ctx.fill(p)
// reset CTM to defaults
ctx.setTransform(1, 0, 0, 1, 0, 0);
body { background: #333; }
<canvas></canvas>
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
Or you could use the matrix
parameter of Path2D.addPath(path, matrix)
to translate the path directly:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeStyle = '#fff'
ctx.lineWidth = 1
ctx.fillStyle = '#fff'
const radius = 50;
const x = 150;
const y = 70;
const p1 = new Path2D(`m${radius},0 a 6.133588769794187,10 0 1, 0 0,${radius * 2} a10,10 0 1, 1 0,${radius * -2}`);
const p = new Path2D();
const matrix = { e: x - radius / 2 , f: y - radius / 2 };
p.addPath(p1, matrix);
ctx.stroke(p);
ctx.fill(p);
body { background: #333; }
<canvas></canvas>
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
Each of these solutions have their own advantages, the first ones being maybe easier to read, the CTM one being best in case you want to change the position of your Path2D every frame, and the last one being best if you need to fill the Path2D with patterns or gradients (which would also be affected by the CTM).