This is my first time using canvasses, and my first time using Javascript to make a full on game-type thing, so forgive the beginner confusion!
I am trying to make a sort of circular Flappy Bird type thing where you travel around a circle, are drawn into the circle in a sort of gravity like way, and clicking makes you rise up. Go too high and you die, go too low and you die. However, I am falling down at the first hurdles...
The two main problems I have right now, starting with the smallest, is with the .clearRect function. I currently have it vaguely clearing behind the ball as it moves, but this is a little imperfect and cuts into the middle circle if it gets too close. If I set the whole window to clear on each frame, everything flashes horribly; is there a better way of doing this?
The second problem is just making the ball go in a circle... I have tried a lot of solutions, and I know that this is very likely to be a huge failing of my maths skills, but I can't make it orbit the main circle at all. I can make it move, and I have made it bounce off of the walls so that I can at least see what it's doing, but it just isn't circling. I imagine I'll be asking about the other aspects of this game at some point in the future if I have the same difficulty, but, as I said, I'm trying to learn!
In the dx and dy variable, 'sun' represents the middle point of the circle that I am trying to get the planet to orbit around. The rest of the variable is based on one answer I found elsewhere but I haven't managed to get it to work either.
var canvas = document.createElement("canvas"),
context = canvas.getContext("2d"),
sun = 300,
sunRadius = sun / 2,
x = sun +110,
y = sun -110,
angle = 0,
distance = 10,
dx = sun + sunRadius * Math.cos(angle*Math.PI/180),
dy = sun + sunRadius * Math.sin(angle*Math.PI/180),
planetRadius = 10;
document.body.appendChild(canvas);
canvas.width = canvas.height = sun * 2;
function makeSun() {
context.beginPath();
context.strokeStyle = "yellow";
context.arc(sun, sun, 60, 0, 2 * Math.PI);
context.fillStyle = "yellow";
context.fill();
context.closePath();
}
function makePlanet() {
context.beginPath();
context.arc(x, y, planetRadius, 0, Math.PI * 2);
context.fillStyle = "green";
context.fill();
context.strokeStyle = "green";
context.stroke();
}
function draw() {
context.clearRect(x + -20, y - 15, 40, 40);
makePlanet();
if (x + dx > canvas.width - planetRadius || x + dx < planetRadius) {
dx = -dx;
}
if (y + dy > canvas.height - planetRadius || y + dy < planetRadius) {
dy = -dy;
}
x += dx;
y += dy;
}
setInterval(makeSun, 10);
setInterval(draw, 10);
Ok, so
first :
the screen is flashing because you are setting two different intervals for makesun and draw. javascript makes zero promises that it won't update the screen between the time it cals makesun and draw. the best solution I think is to effectively clear the whole rect then redraw your sun ( the graphics are simple enough so you won't have performance issues).
second :
to animate the ball, you want to get the current time and decide how fast (in rotations / sec) you want it to go. As others suggested, requestAnimationFrame is better than setInterval, as it passes you a timestamp.
example
I've adapted your code to make the planet spin. I also added movement to the sun so you see the planet is actually relative to it. Have a look and please ask if there's something you don't understand.
const canvas = document.createElement("canvas"),
context = canvas.getContext("2d"),
checkbox = document.getElementById("moveSunBx"),
sun = 300,
sunRadius = sun / 2,
controllables = {
movesun : false,
rotationspeed : 1/2 // half a turn every second (timestamps are in milliseconds)
},
distancePtoS = 110,//distance from planet to sun
planetRadius = 10;
var planetAngle = 0.0,// Starting angles
sunAngle = 0.0,
lastTimestamp;
var gui = new dat.GUI();
gui.add(controllables, 'movesun').name("move sun");
gui.add(controllables, 'rotationspeed', -6, 6).name("planet speed");// in turns per second
function makeSun(x, y) {
context.beginPath();
context.strokeStyle = "yellow";
context.arc(x, y, 60, 0, 2 * Math.PI);
context.fillStyle = "yellow";
context.fill();
context.closePath();
}
function makePlanet(x, y) {
context.beginPath();
context.arc(x, y, planetRadius, 0, Math.PI * 2);
context.fillStyle = "green";
context.fill();
context.strokeStyle = "green";
context.stroke();
}
function draw(timestamp) {
requestAnimationFrame(draw);//immediately ask for next frame
if(!lastTimestamp){
lastTimestamp = timestamp;
return;
}
var speed = Math.PI * 2.0 * controllables.rotationspeed / 1000, // convert speed from turns per second to radian per millisec
timestep = timestamp - lastTimestamp;
lastTimestamp = timestamp;//we save the stamp
planetAngle += timestep * speed;//we update the angle depending on the currentspeed and the timestep
//angle = angle % Math.PI / 2;// this is for better comprehension, Math.cos and sin do the clamping for us
debugger;
//let's make the sun move!!!
if(controllables.movesun){
sunAngle += timestep * speed;
}
var sunx = sunRadius + Math.cos(sunAngle /2) * distancePtoS;// <- sin of 2 angle gives a beautiful infinity
var suny = sunRadius + Math.sin(sunAngle) * distancePtoS / 2;
//x and y don't need to be kept, I made them local to draw()
var planetx = sunx + Math.cos(planetAngle) * distancePtoS;
var planety = suny + Math.sin(planetAngle) * distancePtoS;
context.clearRect(0, 0, canvas.width, canvas.height);
makeSun(sunx, suny);
makePlanet(planetx, planety);
}
document.body.appendChild(canvas);
canvas.width = canvas.height = sun * 2;
draw();//we start the cycle
<script src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
Use requestAnimationFrame() instead of the set intervals and you shouldn't have the flashing afterwards and the equation for a semicircle is y = ±√(r^2-x^2)
Use a variable to determine if it should be positive or negative and change the x variable of the orbiting object each draw to move it in a circle
Related
I have already looked for many questions like this and his answers on stackoverflow but it seems that I never complety have the exact same problem:
Player, created at x = canvas.width /2, y = canvas.height /2
The code that I use to generate the various sprites on the canvas is:
class Sprite {
constructor({
position,
imageSrc,
scale,
framesMax = 1,
offset = { x: 0, y: 0 },
}) {
this.position = position
this.width = 50
this.height = 150
this.image = new Image()
this.image.src = imageSrc
this.scale = scale
this.framesMax = framesMax
this.framesCurrent = 0
this.framesElapsed = 0
this.framesHold = 5
this.offset = offset
}
draw() {
c.drawImage(
this.image,
this.framesCurrent * (this.image.width / this.framesMax),
0,
this.image.width / this.framesMax,
this.image.height,
this.position.x - this.offset.x,
this.position.y - this.offset.y,
(this.image.width / this.framesMax) * this.scale,
this.image.height * this.scale
)
}
animateFrames() {
this.framesElapsed++
if (this.framesElapsed % this.framesHold === 0) {
if (this.framesCurrent < this.framesMax - 1) {
this.framesCurrent++
} else {
this.framesCurrent = 0
}
}
}
update() {
this.draw()
this.animateFrames()
}
}
Then what I want to do is to create the "Fishnet" that you see in the first picture, and position the image on a certain angle, but keep the starting point of the coordinates, another image is possible useful.
Fishnet:
I have tried many things, but the most common that I see everywhere, is to draw de image, save the canvas context, translate the canvas, rotate the canvas, and draw the image.
For reasons that I can't get in to, I never could rotate the image and maintain the starting position.
I wrote another sprite class specific for this rotation, and added the rotate method:
rotate(){
c.save();
c.translate(x,y)
c.rotate(this.angle)
c.drawImage(
this.image,
-(this.image.width),
-(this.image.height),
150,
50,
this.position.x - this.offset.x,
this.position.y - this.offset.y,
(this.image.width / this.framesMax) * this.scale,
this.image.height * this.scale
)
c.restore();
}
The x and y continue to be the (canvas.width / 2) and (canvas.height / 2) in witch I have my doubts of working...
The angle is calculate by the position of the yellow (projetile) and the center of the canvas:
angle = Math.atan2(projectile.position.y - canvas.height / 2, projectile.position.x - canvas.width / 2)
One of the few attempts this has """worked""", was by an example that I saw online, but I had to remove most of my parameters on the drawImage as I rotated it, like this:
draw() {
c.drawImage(
this.image,
this.framesCurrent * (this.image.width / this.framesMax),
0,
this.image.width / this.framesMax,
this.image.height,
this.position.x - this.offset.x,
this.position.y - this.offset.y,
(this.image.width / this.framesMax) * this.scale,
this.image.height * this.scale
)
}
Rotate:
rotate(){
c.save();
c.translate(x,y)
c.rotate(this.angle)
c.drawImage(
this.image,
-(this.image.width),
-(this.image.height)
)
c.restore();
Then I called the Draw method and after the Rotate method. It generated me 2 images. If I only call the rotate method it is obvious that I create only one image, but idk why, can't get the drawImage from rotate to work with the scale and position's x,y. Only works with those 3 paramaters
(this.image, -(this.image.width),-(this.image.height))
Here is the result of working only with the rotate method:
Rotation working with no scaling or proper angle (maybe translate is wrong?)
It can be a problem only on the angle, and I will try to figure it out (I do not think so anyway, because I have another solution launching a circle at that angle to check if its right, and it is.)
Still can't get it to work with scaling.. like I do in the draw() method above.
I know this will be very confusing but I have little knowledge of canvas in JavaScript (and overall..), feel free to comment on more information.
Thanks in advance.
Expanding on my comment, you can just draw the net yourself no need for an image, it does not look too complicated, an arc and a few lines should be a good starting point to test the concept
And the most complicated part I see you are already using:
c.translate(x,y)
c.rotate(angle)
Here is a quick example:
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var angle = 0
function drawNet(x, y, w, h, angle) {
ctx.beginPath();
ctx.translate(x,y)
ctx.rotate(angle)
ctx.moveTo(0, 0);
ctx.arc(0, h, w / 2, 0, Math.PI)
ctx.lineTo(0, 0);
ctx.stroke();
ctx.rotate(-angle)
ctx.translate(-x,-y)
}
function draw() {
ctx.clearRect(0,0, c.width, c.height)
drawNet(70, 50, 55, 50, angle)
drawNet(200, 80 + Math.sin(angle)*30, 45, 30, -angle*2)
angle += 0.1
}
setInterval(draw, 60)
<canvas id="myCanvas" style="border:1px solid;">
In my function drawNet I'm translating and rotating to the given parameters then rotating back and translating back to the original position, that allows all following drawing to be at the correct location
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
$(function() {
var centerX = 400,
centerY = 400,
radius = 300;
function make_base(ctx, angle) {
base_image = new Image();
base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
base_image.onload = function(){
ctx.save();
var x = centerX + Math.sin(angle) * radius;
var y = centerY + Math.cos(angle) * radius;
ctx.translate(x, y);
ctx.drawImage(base_image, 0, 0);
ctx.rotate(angle);
ctx.restore();
}
}
function draw(step) {
var ctx = document.getElementById("canvas").getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
var angle;
for (var i = 0; i < 180; i += 10) {
angle = step + (i * Math.PI / 90);
make_base(ctx, angle);
}
}
var step = 0;
draw(step);
setInterval(function() {
draw(step);
++step;
}, 20);
});
The issue is when i try to loop the image to create a ring shape and animate it by using setInterval, clearing and redrawing the images on the canvas results to flicker.
Is there a way to smoothen the orbit animation? or a better way to create an orbit animation?
Codepen
Canvas animations.
When animating on a browser always use requestAnimationFrame, it gives far better results than setInterval and setTimeout by only moving new pixel data to the display in sync with the refresh time.
Why the flicker
It's complicated...
The basic reason is that you were loading the image each time you wanted to use it. The onload will not fire until after your code has stopped running. When your code stops running the canvas content will be presented to the display and it will be empty because you just cleared it (no event will fire until you exit the draw function). Then the onload events will start to fire, each drawing the image on the canvas. When the event exits the browser thinks "ok you want to put that to the display!" and the whole canvas is again presented to the display. Because this happens much faster than the screen refresh rate you get you weird shearing, flickering, jumping effect.
Another reason is the use of setInterval The Answer from DominicValenciana still uses setInterval and if you look carefully you will see that it is not a smooth as using requestAnimationFrame. Good animation should be smooth as silk (well as close a possible)
Some tips below the demo.
This answer will also help explain image loading and drawing.
(function () {
const oneDeg = Math.PI / 180;
const D1 = Math.PI / 180; // again for lazy programmers
const centerX = 400;
const centerY = 400;
const radius = 300;
// create and load image
const base_image = new Image();
base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
// create size and add to DOM a canvas
const c = document.createElement("canvas");
c.width = 800;
c.height = 800;
document.body.appendChild(c);
const ctx = c.getContext("2d"); // get context
var step = 0; // the animation step
function draw() { // the main draw function
var x,y,i,angle;
if(base_image.complete){ // has the image loaded
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.clearRect(0, 0, c.width, c.height);
for (i = 0; i < Math.PI * 2; i += D1 * 10) { // 360 in steps of 10
angle = step + i; // angle in radians
x = centerX + Math.sin(angle) * radius;
y = centerY + Math.cos(angle) * radius;
ctx.setTransform(1, 0, 0, 1, x, y); // set translate and scale no need for save restore
ctx.rotate(angle); // set angle
ctx.drawImage(base_image, 0, 0); //draw image
}
step += 1* D1; // step 1 deg per frame
}
requestAnimationFrame(draw);
}
draw();
})();
Angles, PI, and Trig
Your calculation for the position of the sprite
x = Math.sin(angle);
y = Math.cos(angle);
has zero (angle = 0) at the top of the display (the 12 oclock position). To match the same angle as used by the canvas rotate function use
x = Math.cos(angle);
y = Math.sin(angle);
this has zero at the right of the screen (the 3oclock position)
If you want to keep creating animations and computer graphics. Forget about 360 deg. Circles are 2PI around not 360, PI is half a turn 180, PI/2 is a quarter turn 90, PI/30 is a minute hands movement per minute, the distance around a circle is 2*PI*radius, around half a circle PI * radius. Every trig function in JS uses radians (not to be annoying, or obtuse, but because it is far simpler to use), You will always need to use PI at some stage if you work in deg, so just drop the unnecessary overhead.
Constants and variables
Only initialize variables once if you never intend to change it. Better yet if a variable reference never needs to be changed make it a constant const myVar = 10; if you try and change it in any way it will throw an error myVar = 10; ERROR!!!!!
Animation where performance is everything
When animating it is important to keep performance at maximum so you can invest more into the quality of the animation. When setting the transform it is far easier to use ctx.setTransform(scale,0,0,scale,x,y); ctx.rotate(angle); as you do not need to save and restore the full canvas state just for transforms. If you wish to get the default transform use ctx.setTransform(1,0,0,1,0,0);
Don't support the dead
No need for the moz,webkit prefixed requestAnimationFrame is everywhere. and as far as I know if a browser does not support requestAnimationFrame it does not support canvas, and we should not support as browser that does. Also requestAnimationFrame is not just for canvas, it should be used for any DOM javascript based animations you create.
The problem is that a new instance of the image is being reloaded each time they are rendered in the circle. The network delay causes this flashing that you see. By loading the image first and then reusing the data I have removed the flashing.
Here is a working version:
http://codepen.io/anon/pen/JRVJRJ
I have moved the loading of base_image out side of the make_base function and made it a global variable for reuse in the make_base function. The causes the image to be loaded once and reapplied multiple times.
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
$(function() {
var centerX = 400,
centerY = 400,
radius = 300;
function make_base(ctx, angle) {
ctx.save();
var x = centerX + Math.sin(angle) * radius;
var y = centerY + Math.cos(angle) * radius;
ctx.translate(x, y);
ctx.drawImage(base_image, 0, 0);
ctx.rotate(angle);
ctx.restore();
}
function draw(step) {
var ctx = document.getElementById("canvas").getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
var angle;
for (var i = 0; i < 180; i += 10) {
angle = step + (i * Math.PI / 90);
make_base(ctx, angle);
}
}
base_image = new Image();
base_image.src = 'https://gxzzxb.files.wordpress.com/2014/12/16mario.png';
base_image.onload = function(){
var step = 0;
draw(step);
setInterval(function() {
draw(step);
++step;
}, 20);
}
});
Problem: Im drawing a spaceship on the canvas. Upon hovering over it's x/y, im drawing an arc on the canvas, indicating the starships weapons angle and range (considering the starships current Baring/facing). Currently the determined angle is being drawn in green and extends as far as the weapons range value allows.
However, i would like to use a gradiant to fill the determined arc to indicate a drop-off in accuracy (i.e. gradiant begins at green, moves to orange, turns red the further away from the starships Position the angle is).
However, i dont know how i could replace my stock ctx.fill() on the drawn arc with a gradiant.
var ship {
loc: {x, y}, // lets say 100, 100
facing: facing // lets say facing 0, i.e. straight right
weapons: objects (range, startArc, endArc) // lets say 50, 300, 60 -> 120 degree angle, so -60 and +60 from facing (0/360)
}
for (var i = 0; i < weapon.arc.length; i++){
var p1 = getPointInDirection(weapon.range, weapon.arc[i][0] + angle, pos.x, pos.y);
var p2 = getPointInDirection(weapon.range, weapon.arc[i][1] + angle, pos.x, pos.y)
var dist = getDistance( {x: pos.x, y: pos.y}, p1);
var rad1 = degreeToRadian(weapon.arc[i][0] + angle);
var rad2 = degreeToRadian(weapon.arc[i][1] + angle);
fxCtx.beginPath();
fxCtx.moveTo(pos.x, pos.y);
fxCtx.lineTo(p1.x, p1.y);
fxCtx.arc(pos.x, pos.y, dist, rad1, rad2, false);
fxCtx.closePath();
fxCtx.globalAlpha = 0.3;
fxCtx.fillStyle = "green";
fxCtx.fill();
fxCtx.globalAlpha = 1;
}
is it possible to replace the arc/globalalpha/fill so use a gradiant flow instead of it being colored fixed and if so, how ?
thanks
To fill an arc with a gradient, animated just for the fun.
Uses a radial gradient and set colour stops as a fraction of distance.
The function createRadialGradient takes 6 numbers the position x,y and start radius and the position x,y and end radius of the gradient.
Colour stops are added via the gradient object addColorStop function that takes a value 0 inner to 1 outer part of the gradient and the colour as a CSS color string. "#F00" or "rgba(200,0,0,0.5)" or "RED"
Then just use the gradient as the fill style.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
function update(time) {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// position of zones in fractions
var posRed = 0.8 + Math.sin(time / 100) * 0.091;
var posOrange = 0.5 + Math.sin(time / 200) * 0.2;
var posGreen = 0.1 + Math.sin(time / 300) * 0.1;
var pos = {
x: canvas.width / 2,
y: canvas.height / 2
};
var dist = 100;
var ang1 = 2 + Math.sin(time / 1000) * 0.5;
var ang2 = 4 + Math.sin(time / 1300) * 0.5;
var grad = ctx.createRadialGradient(pos.x, pos.y, 0, pos.x, pos.y, dist);
grad.addColorStop(0, "#0A0");
grad.addColorStop(posGreen, "#0A0");
grad.addColorStop(posOrange, "#F80");
grad.addColorStop(posRed, "#F00");
grad.addColorStop(1, "#000");
ctx.fillStyle = grad;
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
ctx.arc(pos.x, pos.y, dist, ang1, ang2);
ctx.fill();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
JSFiddle Link: http://jsfiddle.net/lustre/970tf041/6/
I didn't create this code, but the blog which it was created from isn't working at the moment...
Basically, I'm looking for a way to include an image (http://i.imgur.com/7IBiiOW.png) behind the spinner text, which spins with the spinner when it spins. This is my first foray into canvas' so I'm a bit lost as to where it could be added.
The code below used to colour the background of each segment with a random colour, but now it just makes each segment transparent.
function genHex(){
colorCache.push('transparent');
return 'transparent';
}
Here's the original in case it's useful:
function genHex(){
var colors=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"], color = "", digit = [], i;
for (i=0;i<6;i++){
digit[i]=colors[Math.round(Math.random()*14)];
color = color+digit[i];
}
if($.inArray(color, colorCache) > -1){
genHex();
} else {
colorCache.push('#'+color);
return '#'+color;
}
}
I'm really at a loss as to where it could possibly go... The spinners speed is randomised every time it spins, and I'd like the image to match that spin (even if it has to be slowed down).
I assume the code would need to go into the drawWheel() function, but I have no idea how to include it.
function drawWheel() {
ctx.strokeStyle = params.wheelBorderColor;
ctx.lineWidth = params.wheelBorderWidth;
ctx.font = params.wheelTextFont;
ctx.clearRect(0,0,500,500);
var text = null, i = 0, totalJoiner = pplLength;
for(i = 0; i < totalJoiner; i++) {
text = pplArray[i];
var angle = startAngle + i * arc;
ctx.fillStyle = colorCache.length > totalJoiner ? colorCache[i] : genHex();
ctx.beginPath();
// ** arc(centerX, centerY, radius, startingAngle, endingAngle, antiClockwise);
ctx.arc(250, 250, params.outterRadius, angle, angle + arc, false);
ctx.arc(250, 250, params.innerRadius, angle + arc, angle, true);
ctx.stroke();
ctx.fill();
ctx.save();
ctx.shadowOffsetX = -1;
ctx.shadowOffsetY = -1;
ctx.shadowBlur = 1;
ctx.shadowColor = params.wheelTextShadowColor;
ctx.fillStyle = params.wheelTextColor;
ctx.translate(250 + Math.cos(angle + arc / 2) * params.textRadius, 250 + Math.sin(angle + arc / 2) * params.textRadius);
ctx.rotate(angle + arc / 2 + Math.PI / 2);
ctx.fillText(text, -ctx.measureText(text).width / 2, 0);
ctx.restore();
ctx.closePath();
}
drawArrow();
}
Thank you for your time :)
See for example HTML5 Canvas Rotate Image for explanation of image rotation on canvas
The rotated image can be displayed by the following code in the function drawWheel() after ctx.clearRect(0,0,500,500):
var img = document.getElementById("img1");
ctx.save();
ctx.translate(250, 250);
ctx.rotate(startAngle);
ctx.drawImage(img, -params.outterRadius, -params.outterRadius,
2 * params.outterRadius, 2 * params.outterRadius);
ctx.restore();
The image size is adjusted to 2 * params.outterRadius.
Working example: http://jsfiddle.net/0npc395n/1/
I want to draw some lines inside a circle on a canvas, in the following manner.
I have no idea how to draw the lines as showed below. But I have the basic knowledge to draw line and arcs on a canvas. How to proceed?
You can use a bezier curve as suggested in comments with control points, however, these can turn out to be hard to control (no pun) as they do not pass through the points you have defined and you always need to define two control points.
In order to achieve a line through points using the actual points you need to use cardinal splines.
There is no built-in support for these but a while back I made an implementation of this for JavaScript and canvas (code can be downloaded from here, MIT license).
With this you can simply define three points as a minimum (to get a simple curve) and the function will take care of drawing a smooth curve between the points with a set tension value.
If you for example defined the following three points:
var pts = [10,100, 200,50, 390,100];
You would obviously just get a simple poly-line like this if we wanted to illustrate the points (for comparison):
Using a cardinal spline with the same three points would give you this:
The following code generates the above curve (without the red dots showing the point coordinates):
ctx.beginPath();
ctx.curve(pts);
ctx.stroke();
Now it is simply a matter of moving the points around (in particular the center point) and the curve will adopt. Adding a tension slider for the user can be an advantage:
Increasing the tension to for example 0.8 give you this result:
ctx.curve(pts, 0.8);
and lowering it to for example 0.3 will reduce the smoothness to this:
ctx.curve(pts, 0.3);
There are also other parameters (see the link at top for documentation) and you can have "unlimited" number of points in the point array in case you want to add super-fine control.
The implementation extends the canvas context but you can extract the method and use it separately if you are faint at heart. :-)
Implementing this for a circle
I hope I am interpreting your drawing correctly here... to use the above for a circle you would simply need to do the following:
Define the range, the side of the circle you want the lines to be drawn into.
Define the steps, ie. the space between each line.
Lets say you wanted to draw lines between -70° and 70° and maximum 5 lines you could do something like this:
var ctx = canvas.getContext('2d'),
cx = canvas.width * 0.5,
cy = canvas.height * 0.5,
pts,
startAngle = -70,
endAngle = 70,
lines = 5,
angle,
range,
steps,
radius = 90,
delta = 15,
x, y,
i;
ctx.lineWidth = 3;
ctx.strokeStyle = '#059';
/// draw arc
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
ctx.stroke();
/// calculate angle range normalized to 0 degrees
startAngle = startAngle * Math.PI / 180;
endAngle = endAngle * Math.PI / 180;
range = endAngle - startAngle;
steps = range / (lines + 1);
/// calculate point at circle (vertical only)
for(i = 1; i <= lines; i++) {
pts = [];
/// right side
x = cx + radius * Math.cos(startAngle + steps * i);
y = cy + radius * Math.sin(startAngle + steps * i);
pts.push(x, y);
/// center
pts.push(cx, y + delta * ((y - cy)/ cy));
/// flip for left side
x = cx - (x - cx);
pts.push(x, y);
/// draw curve
ctx.beginPath();
ctx.curve(pts, 0.8);
ctx.stroke();
}
Which would result in this:
Fiddle here
It's now just a matter of playing around with the values (delta for example) and to calculate the horizontal row - I'll leave that as an exercise for the OP:
Using ellipse side to draw the lines
That being said - if you intended the globe to be more um, circular :-S, you could also have used a function to calculate part of an ellipse and draw that as lines instead. If would be about the same implementation as above here but with a sub function to calculate the ellipse between left and right side using the difference between the line and middle point as radius.
For example:
/// calculate point at circle (vertical only)
for(i = 1; i <= lines; i++) {
pts = [];
/// right side
x = cx + radius * Math.cos(startAngle + steps * i);
y = cy + radius * Math.sin(startAngle + steps * i);
pts.push(cx - radius, cy);
pts.push(cx, y);
pts.push(cx + radius, cy);
/// draw ellipse side
ctx.beginPath();
drawEllipseSide(pts, true);
ctx.stroke();
}
Then in the method (only vertical shown):
function drawEllipseSide(pts, horizontal) {
var radiusX,
radiusY,
cx, cy,
x, y,
startAngle,
endAngle,
steps = Math.PI * 0.01,
i = 0;
if (horizontal) {
radiusX = Math.abs(pts[4] - pts[0]) * 0.5;
radiusY = pts[3] - pts[1];
cx = pts[2];
cy = pts[1];
startAngle = 0;
endAngle = Math.PI;
x = cx + radiusX * Math.cos(startAngle);
y = cy + radiusY * Math.sin(startAngle);
ctx.moveTo(x, y);
for(i = startAngle + steps; i < endAngle; i += steps) {
x = cx + radiusX * Math.cos(i);
y = cy + radiusY * Math.sin(i);
ctx.lineTo(x, y);
}
}
}
Resulting in this (I cheat a bit in the final drawing to give a more clear picture (no pun) of what the end result will be if you continue down these lines (no pun either, I am pun-cursed) given here):
Fiddle here
My code-OCD kicked in :-P but you should at least have a few options here to go with. Study the code to see how the vertical lines are calculated and adopt that for horizontal ones.
Hope this helps!