HTML5 Canvas sub-pixel position rendering issue - javascript

I'm trying to draw multiple rectangles positioned very densely in a straight line so they form a single shape like so:
const canvas = document.getElementById("c");
const ctx = canvas.getContext("2d");
const a = 87;
const l = 300;
const col = 'rgba(0,0,64)';
const q = 4; // density - change
ctx.moveTo(300, 100);
ctx.lineTo(
300 + (Math.cos(degtorad(a)) * l),
100 + (Math.sin(degtorad(a)) * l)
);
ctx.strokeStyle = col;
ctx.lineWidth = 3;
ctx.stroke();
stack(l, a, col);
function stack(length, angle, color) {
ctx.fillStyle = color;
for (let i = 1; i < length * q; i++) {
let x = 100 + (Math.cos(degtorad(angle)) * i / q);
let y = 100 + (Math.sin(degtorad(angle)) * i / q);
console.log(`x:${x}, y:${y}`);
ctx.beginPath();
ctx.rect(x, y, 150, 100);
ctx.fill();
}
}
function degtorad(angle) {
return angle * (Math.PI / 180.0);
}
https://jsfiddle.net/fallenartist/qeo7a1gx/
However, I don't like the jagged edge at all! It looks like there are issues with canvas rendering at sub-pixel coordinates. Notice how rendering of edges of absolutely positioned rectangles differ from a straight line:
Can anyone explain why is this? And how it can be improved?

Related

How to rotate a canvas circle continuously?

https://codepen.io/saketkr7/pen/KKmzXOV
I have a circle canvas in which n items are placed, I want to rotate all items placed? How can I do that?
[![`
const canvas = document.getElementById('myCanvas');
var items = ['a', 'b' , 'c', 'd', 'e', 'g'];
const ctx = canvas.getContext('2d');
var n = 6;
var numElements = 8;
var angle = 0;
var step = (2*Math.PI) / numElements;
var rotateAngle = 36 * Math.PI / 180;
for(var i = 0; i < numElements; i++) {
var x = 500/2 + 100 * Math.cos(angle);
var y = 500/2 + 100 * Math.sin(angle);
console.log(x, y);
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.stroke();
angle += step;
}
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="500" height="500">
Your browser does not support the HTML canvas tag.
</canvas>
</body>
</html>
`]1]1
requestAnimationFrame
Use requestAnimationFrame(callback) (rAF) to render the animation.
The callback function is responsible for rendering each animation frame. In this case it will clear the canvas and draw the circles.
The callback function gets the time in milliseconds (1/1000th seconds). You can use that to set the rotation angle. The first example uses the time and the constant rate to define the number of rotations per second.
In the callback function you need to request the new frame by calling rAF.
To start the animation request the first frame.
Rendering
Modify your code so that it is a function that can be called for each frame of the animation. In the example your modified code is in the function drawCircles(angle) where angle is the current rotation in radians.
Pass it an argument that is the current rotation.
Example
The snippet below does what is described above.
const ctx = canvas.getContext('2d');
const rate = 0.2; // Number of rotations per second
function drawCircles(angle) {
var i = 0;
const numElements = 8;
const step = (2 * Math.PI) / numElements;
ctx.beginPath();
while (i < numElements) {
const x = ctx.canvas.width / 2 + 100 * Math.cos(angle + i * step);
const y = ctx.canvas.height / 2 + 100 * Math.sin(angle + i * step);
ctx.moveTo(x + 10, y);
ctx.arc(x, y, 10, 0, 2 * Math.PI);
i++;
}
ctx.stroke();
}
requestAnimationFrame(renderLoop); // rAF to start animation
function renderLoop(time) { // rAF callback
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawCircles(((time * Math.PI * 2) / 1000) * rate);
requestAnimationFrame(renderLoop); // request next frame
}
<canvas id = "canvas" width="220" height="220"></canvas>
On many devices the frame rate will be very stable, you can use a fixed rate time to get a smoother animation. As shown in next snippet. Be aware that time will drift if device drops frames.
const ctx = canvas.getContext('2d');
const rate = 0.2; // APPROX Number of rotations per second
var frame = 0; // counts frames
function drawCircles(angle) {
var i = 0;
const numElements = 8;
const step = (2 * Math.PI) / numElements;
ctx.beginPath();
while (i < numElements) {
const x = ctx.canvas.width / 2 + 100 * Math.cos(angle + i * step);
const y = ctx.canvas.height / 2 + 100 * Math.sin(angle + i * step);
ctx.moveTo(x + 10, y);
ctx.arc(x, y, 10, 0, 2 * Math.PI);
i++;
}
ctx.stroke();
}
requestAnimationFrame(renderLoop); // rAF to start animation
function renderLoop() { // rAF callback
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
time = frame++ * (1000 / 60); // Assumes 60 fps
drawCircles(((time * Math.PI * 2) / 1000) * rate);
requestAnimationFrame(renderLoop); // request next frame
}
<canvas id = "canvas" width="220" height="220"></canvas>
Update
Re comments
The #MDN.API.CanvasRenderingContext2D# is not best for 3D. The best option is to use WebGL However there is a steep learning curve for WebGL.
3D can be done on the 2D API but you need to implement all the 3D rendering code in JS which will be orders of magnitude slower than WebGl.
3D via 2D API
The example below uses the 2D canvas to render a rotating ring of toon-shaded spheres rotating in 3D.
It is the most basic example and will not support cameras, lights, textures, etc..
const ctx = canvas.getContext('2d');
const rate = 0.2; // APPROX Number of rotations per second
const numCircles = 18;
const perspectiveRange = 300; // dist between front and back planes
const ringRadius = 60; // in pixels
const circleRadius = 10; // in pixels. Radius of circle at z === 0
const colors = [["#B11", "#F22"], ["#DB0", "#FF0"]];
var frame = 0; // counts frames
function drawCircles(angle, rotY) { // rotZ rotates around Y axis (in and out of screen)
var i = 0;
ctx.fillStyle = "#FF0";
const step = (2 * Math.PI) / numCircles;
const circles = [];
// The transform for y rotation
const dx = Math.cos(rotY);
const dy = Math.sin(rotY);
// get 3D location of each circle
while (i < numCircles) {
const x = ringRadius * Math.cos(angle + i * step);
const y = ringRadius * Math.sin(angle + i * step);
circles.push({x: x * dx, y, z: x * dy, colIdx: i % 2});
i++;
}
// sort circles from back to front
circles.sort((a, b) => b.z - a.z);
// center on canvas
ctx.setTransform(1,0,0,1, ctx.canvas.width / 2, ctx.canvas.height / 2);
// draw 3D circles with perspective
for (const c of circles) {
const col = colors[c.colIdx];
// Calculate perspective scale. The further from the front the
// smaller the perspective scale
const p = (perspectiveRange - c.z) / perspectiveRange;
// Scale radius, x, y pos and line with by perspective scale
const r = Math.abs(p * circleRadius);
const x = p * c.x;
const y = p * c.y;
ctx.lineWidth = 1.5 * p;
// shaded color
ctx.fillStyle = col[0];
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
// highlight color
ctx.fillStyle = col[1];
ctx.beginPath();
ctx.arc(x - r * 0.1, y - r * 0.1, r * 0.8, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = "#FFFA";
ctx.beginPath();
ctx.arc(x - r * 0.3, y - r * 0.3, r * 0.3, 0, 2 * Math.PI);
ctx.fill();
}
// reset canvas transform
ctx.setTransform(1,0,0,1,0, 0);
}
requestAnimationFrame(renderLoop); // rAF to start animation
function renderLoop() { // rAF callback
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
time = frame++ * (1000 / 60); // Assumes 60 fps
const ang = ((time * Math.PI * 2) / 1000) * rate
drawCircles(ang, ang / 2);
requestAnimationFrame(renderLoop); // request next frame
}
<canvas id = "canvas" width="180" height="180"></canvas>

Javascript canvas behaving strangely in for loop

I'm trying to create a canvas graphic by rotating ellipses around a central point. When I do this however, the for loop creates a strange image. There are sporadic gaps in between ellipses, and the colors are strange. At first I thought I was calculating the angles wrong, but if that was the case, the gaps should not be sporadic as I am incrementing them in equal increments each iteration of the for loop. Then I thought maybe I was looping around too many times, but if that was the case, only the colors should be off and not the gaps. This makes me think it's an asynchronous problem where maybe the context didn't finish shifting before the next iteration started drawing the next circle, but that's just a guess. What could be wrong and how do I fix it?
Here's a jsfiddle, there's only 6 ellipses when there should be 10, what's happening?
https://jsfiddle.net/vj7csL3b/
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="200" height="200"></canvas>
<script>
const canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
let color = "#0000ff";
let numberOfEllipses = 10;
let lineThickness = 3;
let middleW = 150;
let middleL = 150;
let eWidth = 50;
let eHeight = 50;
let fill = "rgba(0,0,255,0.2)";
let shift = 50;
let rotate = ((Math.PI / 180) * 360) / 10;
//console.log(middleW, middleL, dfc);
for (let i = 1; i < 10 + 1; i++) {
//translate to point of rotation, rotate and translate back
ctx.translate(middleW, middleL);
ctx.rotate(rotate * i);
ctx.translate(-1 * middleW, -1 * middleL);
ctx.beginPath();
// draws ellipse shifted from the middle we are rotating around.
ctx.ellipse(
middleW + shift,
middleL + shift,
eWidth,
eHeight,
0,
0,
2 * Math.PI
);
ctx.strokeStyle = color;
ctx.lineWidth = lineThickness;
ctx.fillStyle = fill;
ctx.stroke();
ctx.fill();
}
</script>
</body>
</html>
The circles were actually overlapping over each other.Altering the rotation angle and shift will give the output that u expect.
const canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
let color = "#0000ff";
let numberOfEllipses = 10;
let lineThickness = 3;
let middleW = 150;
let middleL = 150;
let eWidth = 50;
let eHeight = 50;
let fill = "rgba(0,0,255,0.2)";
let shift = 60;
let rotate = ((Math.PI / 180) * 360) / 60;
//console.log(middleW, middleL, dfc);
for (let i = 1; i < 10 + 1; i++) {
//console.log("paiting circle" + i);
ctx.translate(middleW, middleL);
ctx.rotate(rotate * i);
ctx.translate(-1 * middleW, -1 * middleL);
ctx.beginPath();
ctx.ellipse(
middleW + shift,
middleL + shift,
eWidth,
eHeight,
0,
0,
2 * Math.PI
);
ctx.strokeStyle = color;
ctx.lineWidth = lineThickness;
ctx.fillStyle = fill;
ctx.stroke();
ctx.fill();
}

How to centre align my line drawing using Canvas?

Problem
I am trying to put this line drawing in the center of my canvas, when I try to use the moveTo(100, 400) for the x-axis, it does not change the horizontal start position to 100. If I try the same thing with the y-axis it will move the the line along the x-axis.
I also need help with drawing the y-axis numbers 1 - 9 vertically along the y-axis it seems to only align horizontally.
EDIT!: I have manually stroked each point on the y-axis so I have the numbers on there, now I just want to know how to move the graph to center!!
Script
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.linecap = 'round';
// draw a scale with the numbers on it
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF9900';
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.moveTo(100, 400);
for (i = 0; i <= 6; i+=1) {
//put a stroke mark
ctx.lineTo(100*i,400);
ctx.lineTo(100*i,405); //markers
ctx.lineTo(100*i,400);
// write the number 10px below
ctx.strokeStyle = '#000000';
// default size is 10px
ctx.strokeText(i, 100*i, 415);
ctx.strokeStyle = '#FF9900';
}
// draw a vertical scale with lines on it
ctx.moveTo(0, -100);
for (b = 0; b <= 9; b+=1) {
//put a stroke mark
ctx.lineTo(0,44.5*b);
ctx.lineTo(5,44.5*b);
ctx.lineTo(0,44.5*b);
// write the number 10px below
ctx.strokeStyle = '#000000';
// default size is 10px
}
ctx.strokeStyle = '#000000'
ctx.strokeText(1, 8, 365);
ctx.strokeText(2, 8, 320.5);
ctx.strokeText(3, 8, 276);
ctx.strokeText(4, 8, 231.5);
ctx.strokeText(5, 8, 187);
ctx.strokeText(6, 8, 142.5);
ctx.strokeText(7, 8, 98);
ctx.strokeText(8, 8, 53.5);
ctx.strokeText(9, 8, 9);
ctx.strokeStyle = '#FF9900';
ctx.stroke();
<!DOCTYPE html>
<html>
<head>
<title>Canvas Axis calibration</title>
<link rel="stylesheet" type="text/css" href="base.css"/>
</head>
<body>
<canvas id="myCanvas" width="1600" height="500"style="border:1px solid #c3c3c3;">
Canvas is not playing!
</canvas>
</body>
</html>
moveTo() just set starting point for your line, it's not draw actual line. Use lineTo() for draw actual line. so moveTo() is from or where you begin and lineTo() is where you go. So starting point for x axis must be moveTo(800, 0).
var c = document.getElementById("myCanvas"),
ctx = c.getContext("2d"),
lineWidth = 2,
xNumber = 6,
yNumber = 9,
xCenter = c.width / 2,
yCenter = 44.5 * yNumber + 44.5
ctx.linecap = 'round';
// draw a scale with the numbers on it
ctx.lineWidth = lineWidth;
ctx.strokeStyle = '#FF9900';
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.moveTo(xCenter, yCenter);
for (i = 0; i <= xNumber; ++i) {
//put a stroke mark
ctx.lineTo((xCenter + (100 * i)), yCenter);
ctx.lineTo((xCenter + (100 * i)), (yCenter + 5)); //markers
ctx.lineTo((xCenter + (100 * i)), yCenter);
// write the number 10px below
ctx.strokeStyle = '#000000';
// default size is 10px
ctx.strokeText(i, (xCenter + (100 * i)), (yCenter + 15));
}
ctx.strokeStyle = '#FF9900';
ctx.stroke()
// draw a vertical scale with lines on it
ctx.beginPath()
ctx.moveTo(xCenter, yCenter);
for (b = 0; b <= yNumber; ++b) {
//put a stroke mark
if(b === 0) continue;
ctx.lineTo(xCenter, (yCenter - (44.5 * b)));
ctx.lineTo((xCenter - 5), (yCenter - (44.5 * b)));
ctx.lineTo(xCenter, (yCenter - (44.5 * b)));
ctx.strokeStyle = '#000000';
ctx.strokeText(b, (xCenter - 15), (yCenter - (44.5 * b)));
}
ctx.strokeStyle = '#FF9900';
ctx.stroke();
<!DOCTYPE html>
<html>
<head>
<title>Canvas Axis calibration</title>
<link rel="stylesheet" type="text/css" href="base.css"/>
</head>
<body>
<canvas id="myCanvas" width="1600" height="500"style="border:1px solid #c3c3c3;">
Canvas is not playing!
</canvas>
</body>
</html>
CanvasRenderingContext2D has a method for that: translate(). It simply sets a coordinate-shift which is going to be applied to everything you draw afterwards:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.linecap = 'round';
ctx.lineWidth = 2;
ctx.fillStyle = 'blue';
ctx.translate((1600-500)/2,0); // <-----------
ctx.strokeStyle = '#000000';
ctx.beginPath();
ctx.moveTo(100, 400);
for (var i = 0; i <= 6; i+=1) {
ctx.lineTo(100*i,400);
ctx.lineTo(100*i,405);
ctx.lineTo(100*i,400);
ctx.strokeText(i, 100*i, 415);
}
ctx.moveTo(0, -100);
for (var b = 0; b <= 9; b+=1) {
ctx.lineTo(0,44.5*b);
ctx.lineTo(5,44.5*b);
ctx.lineTo(0,44.5*b);
if(b<9)
ctx.strokeText(b+1, 8, 365-44.5*b);
}
ctx.strokeStyle = '#FF9900';
ctx.stroke();
<!DOCTYPE html>
<html>
<head>
<title>Canvas Axis calibration</title>
<link rel="stylesheet" type="text/css" href="base.css"/>
</head>
<body>
<canvas id="myCanvas" width="1600" height="500"style="border:1px solid #c3c3c3;">Canvas is not playing!</canvas>
</body>
</html>
Here I assumed the drawing is 500 units wide, which does not seem to be entirely correct, but you will certainly see the result of translate(). The effect of translate() can be reset with setTransform(1, 0, 0, 1, 0, 0) call (if you are familiar with homogeneous coordinates and transformation matrices, note it has a heavily modified order, see in docs). It is actually a matrix which can do all kinds of 2D transformations (translation, rotation, scaling, skewing), translate() is just a convenience function (the equivalent call probaby would be setTransform(1,0,0,1,(1600-500)/2,0), but I have not tried).
Minor changes:
added the var-s into the loops: otherwise variables become global ones which is usually not a problem for a loop variable like i, but generally considered bad practice
reduced to a single stroke() and two strokeStyle-s. The thing is that lines, arcs and the like are drawn with the settings which are set at the very moment when you call stroke(), it does not matter what happened in between. So color is black for most of the time, as strokeText() is immediate, and color becomes that beige/whatever one just for the stroke()
moved the second set of labels into the corresponding loop. I am not sure if the loop is entirely correct, as 9 labels and 9 line segments are visible, but 10 line segments are drawn.
Think local origins
When you get a building plan you don't get a giant sheet of paper with the plan way in the corner because you are building in the burbs, you want to move some windows out of the summer sun, you don't redraw the plan with new coordinates for each wall.
No you get the plan that fits a small sheet, on the plan is a location and orientation. The position of the walls are fix to the local coordinates of the plan.
Same goes for drawing in 2D. You can define a box as 4 points around an origin. [[-10,-10],[10,-10],[10,10],[-10,10]] and when you draw it you set its location and orientation, you dont change the position of each point to the new location.
Draw local coordinate in the world via setTransform
In the 2D API the position and orientation is set via a transform.
function drawPath(x,y, points) { // only position changes
ctx.setTransform(1,0,0,1,x,y); // set the location
ctx.beginPath();
for(const [x,y] of points) {
ctx.lineTo(x,y);
}
ctx.stroke();
}
const box = [[-10,-10],[10,-10],[10,10],[-10,10]];
drawPath(100, 100, box);
And with scale and rotate
function drawPath(x,y,scale, rotate, points) {
const xdx = Math.cos(rotate) * scale;
const xdy = Math.sin(rotate) * scale;
ctx.setTransform(xdx, xdy, -xdy, xdx, x, y); // set the location
ctx.beginPath();
for(const [x,y] of points) {
ctx.lineTo(x,y);
}
ctx.stroke();
}
drawPath(100, 100, 2, 0.5, box);
const box = [[-10,-10],[10,-10],[10,10],[-10,10]];
const W = canvas.width;
const H = canvas.height;
const ctx = canvas.getContext("2d");
ctx.font = "2opx arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "red";
const rand = v => Math.random() * v;
drawRandom();
function drawPath(x, y,scale, rotate, points) {
const xdx = Math.cos(rotate) * scale;
const xdy = Math.sin(rotate) * scale;
ctx.setTransform(xdx, xdy, -xdy, xdx, x, y); // set the location
ctx.fillText("Hi",0,0);
ctx.beginPath();
for(const [x,y] of points) {
ctx.lineTo(x, y);
}
ctx.closePath();
ctx.setTransform(1, 0, 0, 1, 0, 0); // Resets so line width remains 1 px
ctx.stroke();
}
function drawRandom() {
drawPath(rand(W), rand(H), rand(2) + 0.5, rand(Math.PI * 2), box);
setTimeout(drawRandom, 500);
}
canvas {
border: 1px solid black;
}
<canvas id="canvas" width ="400" height="400"></canvas>
All you need is ctx.setTransform and maybe ctx.transform if you are doing rigged animation stuff. I never use ctx.translate, ctx.scale, ctx.rotate because they are slow, and its hard to picture just where you are, oh and did I say they are SLOW!!!!
To reset the transform (remove scale, rotation and move back to 0,0) call ctx.resetTransform() or ctx.setTransform(1,0,0,1,0,0)
And some more regarding your approach to the code.
Granular coding
Looks like you want to draw a graph.
Manually drawing every tick, setting styles, and dozens of magic numbers and values is not going to make it much fun. Worse is that when it comes time to make changes it will take forever.
Don't repeat
You need to think like a lazy programmer. Create functions so you dont have to do the same thing over and over.
Define styles once and name them
For example setting the 2D context style is a pain. A drawing usually only has a few different styles, so create an object with named styles
const styles = {
textHang: {
textAlign : "center",
textBaseline : "top",
fillStyle: "blue",
font: "16px Arial",
},
};
And a function that will set a style
const setStyle = (style, c = ctx) => Object.assign(c, style);
Now you can set a style
const ctx = myCanvas.getContext("2d");
setStyle(styles, styles.textHang);
ctx.fillText("The text", 100, 100);
Basic 2D point helper
You are working in 2D and 2D uses a lot of points. You will be adding multiplying, copying... 2D points over and over and over.
Reduce the typing and cover the most basic 2D needs with only 7 functions
const P2 = (x = 0, y = 0) => ({x,y});
const P2Set = (p, pAs) => (p.x = pAs.x, p.y = pAs.y, p);
const P2Copy = p => P2(p.x, p.y);
const P2Mult = (p, val) => (p.x *= val, p.y *= val, p);
const P2Add = (p, pAdd) => (p.x += pAdd.x, p.y += pAdd.y, p);
const P2Sub = (p, pSub) => (p.x -= pSub.x, p.y -= pSub.y, p);
const P2Dist = (p1, p2) => ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5;
No line? 2D API
The 2D API is great, but lacking. To just draw a line is crazy long, foo bar....
ctx.linecap = 'round';
ctx.lineWidth = 2;
ctx.strokeStyle = '#FF9900';
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(410, 410);
ctx.stroke();
No way create functions, use named styles, don't enter coordinates use points.
Some common 2D tasks as functions
const clear = (c = ctx) => (setPos(), c.clearRect(0,0,c.canvas.width,c.canvas.height));
const line = (p1, p2, c = ctx) => (c.moveTo(p1.x, p1.y), c.lineTo(p2.x, p2.y))
const setPos = (p, c = ctx) => p ? c.setTransform(1, 0, 0, 1, p.x, p.y) : c.resetTransform();
const path = (p, path, c = ctx) => {
c.setTransform(1,0,0,1,p.x,p.y);
for(const seg of path) { // each segment
let first = true;
for(const p of seg) { // each point
first ? (c.moveTo(p.x,p.y), first = false):(c.lineTo(p.x, p.y));
}
}
}
Example
The following takes all the above and creates 2 Axis. It may seem like a lot extra, but as you add complexity to your drawing you quickly find you need less and less code.
/* Set up the context get common values eg W,H for width and height */
const W = canvas.width;
const H = canvas.height;
const ctx = canvas.getContext("2d");
// Helper functions will use a global ctx, or pass a 2d context as last argument
// P2 is a point. I use p to mean a point
const P2 = (x = 0, y = 0) => ({x,y});
const P2Set = (p, pAs) => (p.x = pAs.x, p.y = pAs.y, p);
const P2Copy = p => P2(p.x, p.y);
const P2Mult = (p, val) => (p.x *= val, p.y *= val, p);
const P2Add = (p, pAdd) => (p.x += pAdd.x, p.y += pAdd.y, p);
const P2Sub = (p, pSub) => (p.x -= pSub.x, p.y -= pSub.y, p);
const P2Dist = (p1, p2) => ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5;
const setStyle = (style, c = ctx) => Object.assign(c, style);
const clear = (c = ctx) => (setPos(0, c), c.clearRect(0,0,c.canvas.width,c.canvas.height));
const line = (p1, p2, c = ctx) => (c.moveTo(p1.x, p1.y), c.lineTo(p2.x, p2.y))
const setPos = (p, c = ctx) => p ? c.setTransform(1, 0, 0, 1, p.x, p.y) : c.resetTransform();
const path = (p, path, c = ctx) => {
setPos(p,c);
for(const seg of path) { // each segment
let first = true;
for(const p of seg) { // each point
first ? (c.moveTo(p.x,p.y), first = false):(c.lineTo(p.x, p.y));
}
}
}
const styles = { // define any of the 2D context properties you wish to set
textHang: {textAlign : "center", textBaseline : "top"},
textLeft: {textAlign : "left", textBaseline : "middle"},
markTextStyle: {fillStyle: "blue", font: "16px Arial"},
markStyle: {
strokeStyle: "black",
lineCap: "round",
lineWidth: 2,
},
};
const paths = { // Array of arrays of points. each sub array is a line segment
markLeft: [[P2(-2, 0), P2(5, 0)]],
markUp: [[P2(0, 2), P2(0, -5)]],
}
// Draw an axis from point to point, using mark to mark, lineStyle for the line
// marks is an array of names for each mark, markStyle is the style for the text marks
// markDist is the distance out (90 to the right) to put the text marks
function drawAxis(fromP, toP, mark, lineStyle, marks, markStyle, markDist) {
const norm = P2Mult(P2Sub(P2Copy(toP), fromP), 1 / P2Dist(fromP, toP));
const step = P2Mult(P2Sub(P2Copy(toP), fromP), 1 / (marks.length-1));
const pos = P2Copy(fromP);
setStyle(lineStyle);
ctx.beginPath();
setPos(); // without argument pos is 0,0
line(fromP, toP);
for(const m of marks) {
path(pos, mark);
P2Add(pos, step);
}
ctx.stroke();
P2Set(pos, fromP);
setStyle(markStyle);
for(const m of marks) {
setPos(pos);
ctx.fillText(m,-norm.y * markDist, norm.x * markDist)
P2Add(pos, step)
}
}
const insetW = W * 0.1;
const insetH = H * 0.1;
const axisText = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
clear();
drawAxis(
P2(insetW, H - insetH), P2(insetW, insetH), paths.markLeft,
styles.markStyle,
axisText,
{...styles.textLeft, ...styles.markTextStyle},
-18
);
drawAxis(
P2(insetW, H - insetH), P2(W - insetW, H - insetH), paths.markUp,
styles.markStyle,
axisText,
{...styles.textHang, ...styles.markTextStyle},
6
);
canvas {
border: 1px solid black;
}
<canvas id="canvas" width ="400" height="400"></canvas>

Sprite Animation clearRect alternative?

WHAT? I am attempting to use canvas and JavaScript to display an animation on top of a grid which also must be drawn using JavaScript. https://jsfiddle.net/cp1wqeeg/6/
PROBLEM! To remove the previous frames of the animation I have used clearRect(). This however breaks my grid which I do not want :(
JSFiddle: https://jsfiddle.net/cp1wqeeg/5
ctx.clearRect(50, 100, width, height);
QUESTION How can I remove the previous frames of my animation without breaking the grid behind my sprite?
The common action here is to clear all and redraw everything.
But it may become cumbersome if e.g in your case, your background doesn't change.
In this case, an simple solution, is to use offscreen canvases, that will act as layers.
First you draw you grid on this off-screen canvas in the init phase.
Then in your loop, you just draw your offscreen canvas on the main context, with the drawImage method.
var canvas = document.getElementById("myCanvas"),
ctx = canvas.getContext("2d"),
fov = 300,
viewDist = 5,
w = canvas.width / 2,
h = canvas.height / 2,
// here we create an offscreen canvas for the grid only
gridCtx = canvas.cloneNode().getContext('2d'),
angle = 0,
i, p1, p2,
grid = 5;
function initGrid(){
/// create vertical lines on the off-screen canvas
for(i = -grid; i <= grid; i++) {
p1 = rotateX(i, -grid);
p2 = rotateX(i, grid);
gridCtx.moveTo(p1[0], p1[1]);
gridCtx.lineTo(p2[0], p2[1]);
i++;
}
/// create horizontal lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(-grid, i);
p2 = rotateX(grid, i);
gridCtx.moveTo(p1[0], p1[1]);
gridCtx.lineTo(p2[0], p2[1]);
}
gridCtx.stroke();
}
function rotateX(x, y) {
var rd, ca, sa, ry, rz, f;
rd = angle * Math.PI / 180;
ca = Math.cos(rd);
sa = Math.sin(rd);
ry = y * ca;
rz = y * sa;
f = fov / (viewDist + rz);
x = x * f + w;
y = ry * f + h;
return [x, y];
}
initGrid();
var width = 200,
height = 200,
frames = 2,
currentFrame = 0,
imageSprite = new Image()
imageSprite.src = 'https://s27.postimg.org/eg1cjz6cz/sprite.png';
var drawSprite = function(){
ctx.clearRect(0,0,canvas.width, canvas.height);
ctx.drawImage(gridCtx.canvas, 0,0); // now draw our grid canvas
ctx.drawImage(imageSprite, 0, height * currentFrame, width, height, 50, 100, width, height);
if (currentFrame == frames) {
currentFrame = 0;
} else {
currentFrame++;
}
}
setInterval(drawSprite, 500);
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #c3c3c3;"></canvas>

Getting a shape to draw on the html5 canvas

I have a function that basically uses lines to draw a psuedo circle of a specific size and fills it in a colour.
However when I try to call my method, it does not draw and I found no errors on the debug console (I'm using chrome) and jsfiddle threw up no errors for me.
Here is the code:
function drawLineCircle (x, y, size, colour, scale, segments) {
context.save();
context.strokestyle = '#000000';
context.fillStyle = colour;
context.lineWidth = 3;
context.beginPath();
var anglePerSegment = Math.PI *2 / segments;
for (var i = 0; i <= segments; i = i + 1){
var angle = anglePerSegment * i;
var radius = size * scale;
var a = x + radius * cos (angle);
var b = y + radius * sin (angle);
if (i == 0)
context.moveTo (a, b);
else
context.lineTo (a, b);
}
context.stroke();
context.fill();
context.closePath();
context.restore();
}
Any help would be greatly appreciated!
There are a couple of problems with your code:
You are not defining context
cos and sin are properties of Math
Here is a corrected example: http://jsfiddle.net/REw7j/1/
function drawLineCircle (x, y, size, colour, scale, segments) {
var canvas = document.getElementById('example');
var context = canvas.getContext('2d');
context.save();
context.strokestyle = '#000000';
context.fillStyle = colour;
context.lineWidth = 3;
context.beginPath();
var anglePerSegment = Math.PI *2 / segments;
for (var i = 0; i <= segments; i = i + 1){
var angle = anglePerSegment * i;
var radius = size * scale;
var a = x + radius * Math.cos (angle);
var b = y + radius * Math.sin (angle);
if (i == 0)
context.moveTo (a, b);
else
context.lineTo (a, b);
}
context.stroke();
context.fill();
context.closePath();
context.restore();
}

Categories

Resources