Related
I'm trying to create the Fibonacci spiral with the canvas element in HTML5.
This is my drawing code, this function gets called for every number in the Fibonacci sequence. Side is the square's side length, or the current Fibonacci number. And start is the bottom right corner of each square.
function drawSpiral(side, start) {
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.lineTo(start.x, start.y - side);
ctx.lineTo(start.x - side, start.y - side);
ctx.lineTo(start.x - side, start.y);
ctx.lineTo(start.x, start.y);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
switch (direction) {
case 0:
ctx.arc(start.x, start.y - side, side, Math.PI, 1.5 * Math.PI, true);
break;
case 1:
ctx.arc(
start.x - side,
start.y - side,
side,
1.5 * Math.PI,
2 * Math.PI,
true
);
break;
case 2:
ctx.arc(start.x - side, start.y, side, 2 * Math.PI, Math.PI / 2, true);
break;
case 3:
ctx.arc(start.x, start.y, side, Math.PI / 2, Math.PI, true);
break;
}
ctx.stroke();
ctx.closePath();
}
Direction is a number, 0 to 4, that represents the direction of the next square, counterclockwise - 0 = right, 1 = up, ...
It draws the squares and arcs just fine, but it's also drawing half circles everywhere, and I can't figure out why.
Screenshot of the issue
The last param is "counterclockwise",
ctx.arc(x, y, radius, startAngle, endAngle, true);
will do the same as
ctx.arc(x, y, rad, endAngle, startAngle);
// note how start and end have been swapped
that is to trace an arc from endAngle to startAngle, so in your case, it will draw the 3/4 of a circle:
const ctx = document.querySelector("canvas").getContext("2d");
const startAngle = Math.PI; // 9 o'clock
const endAngle = Math.PI * 1.5; // 12 o'clock
ctx.arc(50, 50, 50, startAngle, endAngle, true);
ctx.strokeStyle = "green";
ctx.stroke();
ctx.beginPath();
ctx.arc(160, 50, 50, endAngle, startAngle);
ctx.strokeStyle = "blue";
ctx.stroke();
<canvas></canvas>
If you want to trace only the 1/4 of the circle, then don't set this param.
Am I meant to draw the eyes in an alternative way for it to follow the cursor? Please help :) I am completely lost from here and have tried online solutions but they all require css in which my code doesn't. I want to run all of this purely from javascript, any tips?
function drawEyes() {
const c = document.getElementById("canvasEyes")
const ctx = c.getContext('2d');
//left eye
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2, false);
ctx.stroke();
//iris
ctx.beginPath();
ctx.arc(75, 75, 30, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillStyle = "black";
ctx.fill();
//pupil
ctx.beginPath();
ctx.arc(75, 75, 15, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
//right eye
ctx.beginPath();
ctx.arc(225, 75, 50, 0, Math.PI * 2, false);
ctx.stroke();
//iris
ctx.beginPath();
ctx.arc(225, 75, 30, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillStyle = "black";
ctx.fill();
//pupil
ctx.beginPath();
ctx.arc(225, 75, 15, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
}
Basic 2D eyes that follow mouse
The eyes follow mouse by scaling the mouse coordinates to the range of motion that the iris & pupil have within the radius of the eye.
The lookat position is relative to the top left of the canvas and assumes that the eyes are at the center of the canvas.
The scaled lookat position is then set relative to the canvas center (center of both eyes)
To prevent the iris & pupil from being drawn outside the eye use the canvas clip function to clip the iris & pupil if outside the circles of the eye.
More details
It is possible to add more details
Consider adding shading, highlights, eyelids, blink, etc.. to give the animation more depth and life, for instance...
Spheres
Eyes are spheres, you can use ellipses to draw the iris & pupil, Flattening the ellipses of the iris & pupil as they get near the edge, also rotate the ellipse in the direction of the mouse. This will make the eyes look rounder in the 3rd dimention.
Example
Basic 2D eyes. See comments for details
const ctx = canvas.getContext("2d");
// Object to hold mouse coords
const lookat = {x: 150, y: 75};
// details need to make eye look at mouse coords
const eye = {
radius: 50,
iris: 30,
// limits of movement
limMin: -0.1,
limMax: 1.1,
};
// add mouse move listener to whole page
addEventListener("mousemove",e => {
// make mouse coords relative to the canvas ignoring scroll in this case
const bounds = canvas.getBoundingClientRect();
lookat.x = e.pageX - bounds.left;// - scrollX;
lookat.y = e.pageY - bounds.top;// - scrollY;
ctx.clearRect(0, 0, 300, 150);
drawEyes(lookat);
});
drawEyes(lookat);
function drawEyes(lookat) {
var {x,y} = lookat;
// normalise lookat range from 0 to 1 across and down canvas
x /= canvas.width;
y /= canvas.height;
// limit eye movement to -0.1 to 1.1 or what ever you prefer
x = x < eye.limMin ? eye.limMin : x > eye.limMax ? eye.limMax : x;
y = y < eye.limMin ? eye.limMin : y > eye.limMax ? eye.limMax : y;
// move lookat so that 0.5 is center
x -= 0.5;
y -= 0.5;
// get range of movement of iris
const range = (eye.radius - eye.iris) * 2;
// scale the lookats to the range of movement
x *= range;
y *= range;
// draw outer eyes left, right
ctx.beginPath();
ctx.arc(75, 75, eye.radius, 0, Math.PI * 2, false);
ctx.moveTo(225 + eye.radius, 75);
ctx.arc(225, 75, eye.radius, 0, Math.PI * 2, false);
ctx.stroke();
// use eyes to create a clip so iris does not draw outside the eye.
// first save canvas state so clip can be turned off at end
ctx.save();
// turn on clip which will use the two circles currently the active path
ctx.clip();
// draw iris & pupil are offset by x,y within the clip
//iris left, right
ctx.fillStyle = "blue";
ctx.beginPath();
ctx.arc(75 + x, 75 + y, eye.iris, 0, Math.PI * 2, false);
ctx.moveTo(225 + x + eye.iris, 75 + y);
ctx.arc(225 + x, 75 + y, eye.iris, 0, Math.PI * 2, false);
ctx.fill();
//pupil left, right
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(75 + x, 75 + y, 15, 0, Math.PI * 2, false);
ctx.moveTo(225 + x + 15, 75 + y);
ctx.arc(225 + x, 75 + y, 15, 0, Math.PI * 2, false);
ctx.fill();
// turn the clip off by restoring canvas state
ctx.restore();
}
<canvas id="canvas" width="300" height="150"></canvas>
This question already has an answer here:
All html canvas shapes are given the color of the last object added
(1 answer)
Closed 2 years ago.
I am drawing a filled ellipse (using arc()) on the javascript canvas:
ctx.arc(centerX, centerY, dotDiameter / 2, 0, 2 * Math.PI, false);
ctx.fillStyle = color1;
ctx.fill();
I then want to draw a line, that is clipped to this ellipse, in a different color.
ctx.clip(); // Clip to the ellipse
ctx.strokeStyle = color2;
ctx.moveTo(centerX - dotRadius, centerY);
ctx.lineTo(centerX + dotRadius, centerY);
ctx.stroke();
However, the ellipse is also stroked with color2.
How can I clip lines to my ellipse, but not have the ellipse stroked? Is there a way to remove the ellipse (aka arc) from the canvas after I call clip()?
Thanks!
(FYI, this is an oversimplification of my code. The lines that stroked are more complicated than a single horizontal line and the lines do need to be clipped.)
We can calculate a point on the perimeter of an ellipsis with some basic trigonometry...
ctx.moveTo(x, y);
x += radiusX * Math.cos(lineangle)
y += radiusY * Math.sin(lineangle)
ctx.lineTo(x, y);
See code snippet below, I'm drawing an ellipse and a line from center to edge.
ctx = document.getElementById('c').getContext('2d');
ctx.lineWidth = 2;
function draw(x, y, radiusX, radiusY, lineangle, color1, color2) {
ctx.beginPath()
ctx.ellipse(x, y, radiusX, radiusY, 0, 0, 2 * Math.PI, false);
ctx.fillStyle = color1;
ctx.fill();
ctx.beginPath()
ctx.strokeStyle = color2;
ctx.moveTo(x, y);
x += radiusX * Math.cos(lineangle)
y += radiusY * Math.sin(lineangle)
ctx.lineTo(x, y);
ctx.stroke();
}
angle = 0
function loop() {
ctx.clearRect(0,0, 500, 500);
draw(80, 80, 70, 50, angle, "red", "lime")
draw(240, 60, 80, 30, angle*0.7, "black", "cyan")
draw(360, 80, 30, 70, angle*2, "white", "black")
angle += 0.05
}
setInterval(loop, 100)
<canvas height="160" width="500" id="c">
I try to figure why angle > 2 PI does not give the same result when drawing an arc clockwise and counterclockwise.
Look at this code snippet, on the first line I draw "clockwise" 3 red arcs with a start angle of 0 and an end angle of PI, 2*PI and 3*PI.
Then I draw "counterclockwise" 3 blue arcs with same parameters.
The 3rd result bewilders me... Can anyone explain this to me ?
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
// CLOCKWISE, angle = PI
ctx.beginPath();
ctx.arc(50, 50, 40, 0, Math.PI, false);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
// CLOCKWISE, angle = 2 PI
ctx.beginPath();
ctx.arc(150, 50, 40, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
// CLOCKWISE, angle = 3 PI
ctx.beginPath();
ctx.arc(250, 50, 40, 0, 3 * Math.PI, false);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
// COUNTERCLOCKWISE, angle = PI
ctx.beginPath();
ctx.arc(50, 150, 40, 0, Math.PI, true);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
// COUNTERCLOCKWISE, angle = 2 PI
ctx.beginPath();
ctx.arc(150, 150, 40, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
// COUNTERCLOCKWISE, angle = 3 PI
ctx.beginPath();
ctx.arc(250, 150, 40, 0, 3 * Math.PI, true);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
<canvas id="myCanvas" width="350" height="250" style="border:1px solid #d3d3d3;"/>
According to specs:
If anticlockwise is false and endAngle-startAngle is equal to or greater than 2π, or, if anticlockwise is true and startAngle-endAngle is equal to or greater than 2π, then the arc is the whole circumference of this ellipse, and the point at startAngle along this circle's circumference, measured in radians clockwise from the ellipse's semi-major axis, acts as both the start point and the end point.
Otherwise, the points at startAngle and endAngle along this circle's circumference, measured in radians clockwise from the ellipse's semi-major axis, are the start and end points respectively, and the arc is the path along the circumference of this ellipse from the start point to the end point, going anti-clockwise if anticlockwise is true, and clockwise otherwise. Since the points are on the ellipse, as opposed to being simply angles from zero, the arc can never cover an angle greater than 2π radians.
Put in maybe clearer pseudo-code:
if(
(anticlockwise === false && (endAngle - startAngle) >= 2π) ||
(anticlockwise === true && (startAngle - endAngle) >= 2π)
) {
arc_circumference = 2π;
}
else {
startAngle = startAngle % 2π;
endAngle = endAngle % 2π;
}
In your case, startAngle = 0, endAngle = 3π, anticlowkwise = true, if we run the above algorithm, we end up in the else case (0 - 3π < 2π) and endAngle is now (3π % 2π = 1π).
We could achieve the same output without the anticlockwise flag by swapping startAngle and endAngle:
var ctx = canvas.getContext("2d");
// COUNTERCLOCKWISE, angle = -3 PI (from OP)
ctx.beginPath();
ctx.arc(50, 150, 40, 0, 3 * Math.PI, true);
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
// CLOCKWISE, angle = -3 PI
ctx.beginPath();
ctx.arc(50, 50, 40, 3 * Math.PI, 0);
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
<canvas id="canvas"></canvas>
When you go COUNTERCLOCKWISE, after 0 comes 2PI. You should try this instead:
// COUNTERCLOCKWISE, angle = 3 PI
ctx.beginPath();
ctx.arc(250, 150, 40, 2 * Math.PI, 0, true);
ctx.closePath();
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
UPDATE:
After OP's comment I've added an animated demo:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
let delta = 0;
function Draw(){
requestAnimationFrame(Draw)
delta+= .01;
ctx.clearRect(0,0,c.width,c.height)
// CLOCKWISE: animating the end point
ctx.beginPath();
ctx.arc(50, 50, 40, 0, delta, false);
ctx.closePath();
ctx.stroke();
// CONTERCLOCKWISE, animating the start point
ctx.beginPath();
ctx.arc(150, 50, 40, 0,-delta, true);
ctx.closePath();
ctx.stroke();
}
Draw()
<canvas id="myCanvas" width="350" height="250" style="border:1px solid #d3d3d3;"/>
This is my jsfiddle:
http://jsfiddle.net/c4upM/103/
I have the gray arc and the blue/green arc.
I am trying to make them more Elliptic, like:
and I didn't succeed.
I read this one: http://jsbin.com/ovuret/2/edit and tried to make it on my jsfiddle but I didn't succeed because the arc-s are painted in the function of: DrawCircle and DrawEllipseForProjection.
There are two functions: blueArc and grayarc. by the radian angle, there is a calculation in function circleInArc that places the small lightblue circle and the small gray circle (when you mouse over the canvas) in the arc-s.
I want these functions to work after the change but I didn't succeed.
these are the blue and gray arc-s:
function grayArc(strokeColor, cx, cy, radius, ctx, linewidth) {
ctx.beginPath();
ctx.arc(cx, cy, radius, Math.PI, Math.PI * 2);
ctx.lineWidth = linewidth;
ctx.strokeStyle = strokeColor;
ctx.stroke();
}
function blueArc(strokeColor, radianStart, radianEnd, cx, cy, radius, ctx, linewidth) {
ctx.beginPath();
ctx.arc(cx, cy, radius, radianStart, radianEnd);
ctx.lineWidth = linewidth;
ctx.strokeStyle = strokeColor;
ctx.stroke();
}
and this is the circleInArc function:
function circleInArc(fillColor, radianAngle, cx, cy, radius, ctx, linewidth) {
var x = cx + radius * Math.cos(radianAngle);
var y = cy + radius * Math.sin(radianAngle);
ctx.beginPath();
ctx.arc(x, y, linewidth / 2, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = fillColor;
ctx.fill();
return ({
x: x,
y: y,
radius: linewidth / 2
});
}
Any help appreciated!
You can try draw arc with bigger radius and not from Math.PI to 2*Math.PI but this one
ctx.arc(cx, cy, radius, Math.PI*5/4, Math.PI * 7/4);
and don't forget move the arc down for radius size
grayArc("rgb(177,177,177)", cx, cy+radius, radius*2, ctx, linewidth);
hope it will help
I've fiddled around a bit, and you'll propably have to adjust some of the values, but here's an example: Fiddle (Only the gray arc was adjusted)
The basic strategy is to save the state of the context, then scale it to your liking. If you only scale one axis.. voilá, ellipsis.
Also, I translated the origin of the canvas to the center of the arc before scaling, because they get scewed with the scaling. The center of the arc is then 0, 0.
After that the context can be restored like nothing ever happened, an you can stroke your transformed arc.
ctx.save();
ctx.translate(cx,cy);
ctx.scale(1.3, 1);
ctx.arc(0, 0, radius, 1.2*Math.PI, 1.8*Math.PI);
ctx.restore();
You'll propably want to play around with the radius, the ratio of scaling and maybe the range in which the arc gets drawn to fit your needs.
Edit: I cut down on the range of the arc which gets drawn, to prevent the stubby ends of the ellipsis showing. From 1.2*PI to 1.8*PI instead of 1*PI to 2*PI seems to look alright in the current case. :)
For future questions, please try to extract the a more basic example of your problem, to make it easier for those who want to help out. :)