Related
I am trying to move a triangle in the direction which the triangle is rotated by. When I press the key to move it, it doesn't move, but when I rotate it, its center of rotation shifts because of the key I pressed to move it previously.
I tried checking the formulas to determine the direction to move the triangle, but those seemed correct, and the translation point to rotate it is not moving based on those formulas.
Expected results: On click of the up arrow key, triangle moves in rotation angle direction.
Actual results: On click of up arrow key, triangle doesn't move in the direction, but if I click up arrow key, then left or right arrow key to rotate, triangle rotates away from center of rotation.
Here's my code:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let ship_width = 20;
let ship_height = 20;
let angle = 0;
let ship_velocity_change = {
x: 0,
y: 0
}
//initial center coordinates of triangle
let ship_center = {
x: 450,
y: 300
};
let ship_points = [
//coordinates for vertices of triangle
{
x: ship_center.x - ship_width / 2,
y: ship_center.y +
ship_height / 2
},
{
x: ship_center.x + ship_width / 2,
y: ship_center.y +
ship_height / 2
},
{
x: ship_center.x,
y: ship_center.y - ship_height / 2
}
];
function drawRect(x, y, width, height, color) {
ctx.rect(x, y, width, height);
ctx.fillStyle = color;
ctx.fill();
}
//vertices for triangle as parameters
function drawTriangle(bottom_left, bottom_right, top, color) {
ctx.beginPath();
ctx.moveTo(top.x, top.y);
ctx.lineTo(bottom_left.x, bottom_left.y);
ctx.lineTo(bottom_right.x, bottom_right.y);
ctx.lineTo(top.x, top.y);
ctx.strokeStyle = color;
ctx.stroke();
}
//rotate triangle by an angle in degrees
function rotate(angle) {
ctx.translate(ship_center.x, ship_center.y);
ctx.rotate(Math.PI / 180 * angle);
ctx.translate(-ship_center.x, -ship_center.y);
drawTriangle(ship_points[2], ship_points[1], ship_points[0],
"white");
}
document.addEventListener('keydown', function(event) {
//rate of degree change per 10 milliseconds
if (event.keyCode === 37) {
angle = -2;
} else if (event.keyCode === 38) {
//move triangle by direction of angle
ship_center.x += Math.cos(Math.PI / 180 * angle) * 5;
ship_center.y += Math.sin(Math.PI / 180 * angle) * 5;
} else if (event.keyCode === 39) {
angle = 2;
}
});
document.addEventListener('keyup', function(event) {
if (event.keyCode === 37 || event.keyCode === 39) {
angle = 0;
}
});
function game() {
drawRect(0, 0, 900, 600, "black");
rotate(angle);
}
let gameLoop = setInterval(game, 10);
<canvas id="canvas" width="900" height="600"></canvas>
Nice attempt! This is one of those things that takes some experimentation to get right.
A few suggestions:
Encapsulate all relevant data and functions for the ship in one object. This keeps things organized and easy to understand and will simplify your logic and reduce bugs significantly. This makes entity state and rendering functions much easier to manage.
Avoid calling ship movement functions directly from the key event callback. Doing so can result in jerky, inconsistent behavior when the keyboard re-triggers. This callback should simply flip key flags, then let the event loop take care of calling the relevant functions based on those flags.
Use requestAnimationFrame instead of setInterval for most animations.
Something like angle = 2; is too rigid (this may have been temporary). We need to use += here and introduce a velocity variable (and, ideally, acceleration too) for realistic rotation.
Math.PI / 180 * angle for Math functions isn't strictly necessary. We can use angle directly in radians. If your game depends on setting angles with degrees, then you can add a conversion function.
Use variables for acceleration and velocity if you want the movement to feel realistic. In the sketch below, I've added a few of these, but it's not a definitive approach and is intended to be tweaked to taste.
Here's a simple example of all of this in action. There is much room for improvement, but it should be a decent starter.
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.width = 400;
canvas.height = 200;
const kbd = {
ArrowLeft: false,
ArrowUp: false,
ArrowRight: false
};
const ship = {
angle: 0,
color: "white",
x: canvas.width / 2,
y: canvas.height / 2,
width: 10,
height: 15,
drag: 0.9,
accSpeed: 0.04,
rotSpeed: 0.012,
rotv: 0,
ax: 0,
ay: 0,
vx: 0,
vy: 0,
rotateLeft() {
this.rotv -= this.rotSpeed;
},
rotateRight() {
this.rotv += this.rotSpeed;
},
accelerate() {
this.ax += this.accSpeed;
this.ay += this.accSpeed;
},
move() {
this.angle += this.rotv;
this.rotv *= this.drag;
this.vx += this.ax;
this.vy += this.ay;
this.ax *= this.drag;
this.ay *= this.drag;
this.vx *= this.drag;
this.vy *= this.drag;
this.x += Math.cos(this.angle) * this.vx;
this.y += Math.sin(this.angle) * this.vy;
},
draw(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.beginPath();
ctx.moveTo(this.height, 0);
ctx.lineTo(-this.height, this.width);
ctx.lineTo(-this.height, -this.width);
ctx.closePath();
ctx.strokeStyle = this.color;
ctx.stroke();
ctx.restore();
}
};
document.addEventListener("keydown", event => {
if (event.code in kbd) {
event.preventDefault();
kbd[event.code] = true;
}
});
document.addEventListener("keyup", event => {
if (event.code in kbd) {
event.preventDefault();
kbd[event.code] = false;
}
});
(function update() {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
const shipActions = {
ArrowLeft: "rotateLeft",
ArrowUp: "accelerate",
ArrowRight: "rotateRight",
};
for (const key in shipActions) {
if (kbd[key]) {
ship[shipActions[key]]();
}
}
ship.move();
ship.draw(ctx);
requestAnimationFrame(update);
})();
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
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>
I would like to be able to simulate the movement of the body on a "carousel" with respect to physics. (centripetal, centrifugal force, angular speed). Below is some sample code.
<!DOCTYPE html>
<html>
<head>
<script>
var rotate = Math.PI / 180;
var ballRotation = 1;
function drawthis() {
var friction = 0.5;
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, cvs.width, cvs.height);
context.translate(350, 350);
context.rotate(rotate);
context.beginPath();
context.arc(1, 1, 12, 0, 2 * Math.PI, false);
context.fill();
context.beginPath();
context.arc(0, 0, 150, 0, Math.PI * 2, false);
context.lineWidth = 6;
context.stroke();
motion = ballRotation - friction;
rotate += motion;
requestAnimationFrame(drawthis);
}
function init() {
cvs = document.getElementById("canvas");
context = cvs.getContext("2d");
context.clearRect(0, 0, context.width, context.height);
context.fillStyle = "#ff0000";
requestAnimationFrame(drawthis);
}
</script>
</head>
<body onload="init()">
<canvas id="canvas" width="800" height="800"></canvas>
</body>
</html>
I mean something like this
Ball on a turn table
Below you will find a simple simulation of a point sliding on a turning wheel. The point represents the contact point of a ball.
The simulation ignores the fact that the ball can roll, or has mass.
The ball slides via a simple friction model, where friction is a scalar value applied to the difference between the balls speed vector, and the speed of the wheel at the point under the ball.
There is only 1 force involved. It is the force tangential to the vector from the ball to the wheel center, subtracted by the ball movement vector and then multiplied by the friction coefficient.
For details on how this is calculated see comments in the function ball.update()
Notes
That if the ball starts at the dead center of the wheel nothing will happen.
I could not workout if it was the path of the ball you wanted or just the simulation of the ball, so I added both.
The ball resets after it leaves the wheel.
The wheel is marked with text and center cross so its rotation can be seen.
const ROTATE = Math.PI / 50;
const WHEEL_SIZE = 0.6;
Math.rand = (min, max) => Math.random() * (max - min) + min;
Math.randPow = (min, max, p) => Math.random() ** p * (max - min) + min;
var friction = 0.35;
const ctx = canvas.getContext("2d");
requestAnimationFrame(mainLoop);
ctx.font = "30px arial";
ctx.textAlign = "center";
scrollBy(0, canvas.height / 2 - canvas.height / 2 * WHEEL_SIZE);
function mainLoop() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
wheel.update();
ball.update(wheel, arrow);
wheel.draw();
path.draw();
ball.draw();
arrow.draw(ball);
requestAnimationFrame(mainLoop);
}
const path = Object.assign([],{
draw() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.strokeStyle = "#F00";
ctx.lineWidth = 1;
ctx.beginPath();
for (const p of this) { ctx.lineTo(p.x, p.y) }
ctx.stroke();
},
reset() { this.length = 0 },
add(point) {
this.push({x: point.x, y: point.y});
if (this.length > 1000) { // prevent long lines from slowing render
this.shift()
}
}
});
const arrow = {
dx: 0,dy: 0,
draw(ball) {
if (this.dx || this.dy) {
const dir = Math.atan2(this.dy, this.dx);
// len is converted from frame 1/60th second to seconds
const len = Math.hypot(this.dy, this.dx) * 60;
const aXx = Math.cos(dir);
const aXy = Math.sin(dir);
ctx.setTransform(aXx, aXy, -aXy, aXx, ball.x, ball.y);
ctx.beginPath();
ctx.lineTo(0,0);
ctx.lineTo(len, 0);
ctx.moveTo(len - 4, -2);
ctx.lineTo(len, 0);
ctx.lineTo(len - 4, 2);
ctx.strokeStyle = "#FFF";
ctx.lineWidth = 2;
ctx.stroke();
}
}
};
const ball = {
x: canvas.width / 2 + 4,
y: canvas.height / 2,
dx: 0, // delta pos Movement vector
dy: 0,
update(wheel, arrow) {
// get distance from center
const dist = Math.hypot(wheel.x - this.x, wheel.y - this.y);
// zero force arrow
arrow.dx = 0;
arrow.dy = 0;
// check if on wheel
if (dist < wheel.radius) {
// get tangent vector direction
const tangent = Math.atan2(this.y - wheel.y, this.x - wheel.x) + Math.PI * 0.5 * Math.sign(wheel.dr);
// get tangent as vector
// which is distance times wheel rotation in radians.
const tx = Math.cos(tangent) * dist * wheel.dr;
const ty = Math.sin(tangent) * dist * wheel.dr;
// get difference between ball vector and tangent vector scaling by friction
const fx = arrow.dx = (tx - this.dx) * friction;
const fy = arrow.dy = (ty - this.dy) * friction;
// Add the force vector
this.dx += fx;
this.dy += fy;
} else if (dist > wheel.radius * 1.7) { // reset ball
// to ensure ball is off center use random polar coord
const dir = Math.rand(0, Math.PI * 2);
const dist = Math.randPow(1, 20, 2); // add bias to be close to center
this.x = canvas.width / 2 + Math.cos(dir) * dist;
this.y = canvas.height / 2 + Math.sin(dir) * dist;
this.dx = 0;
this.dy = 0;
path.reset();
}
// move the ball
this.x += this.dx;
this.y += this.dy;
path.add(ball);
},
draw() {
ctx.fillStyle = "#0004";
ctx.setTransform(1, 0, 0, 1, this.x + 5, this.y + 5);
ctx.beginPath();
ctx.arc(0, 0, 10, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = "#f00";
ctx.setTransform(1, 0, 0, 1, this.x, this.y);
ctx.beginPath();
ctx.arc(0, 0, 12, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = "#FFF8";
ctx.setTransform(1, 0, 0, 1, this.x - 5, this.y - 5);
ctx.beginPath();
ctx.ellipse(0, 0, 2, 3, -Math.PI * 0.75, 0, 2 * Math.PI);
ctx.fill();
},
}
const wheel = {
x: canvas.width / 2, y: canvas.height / 2, r: 0,
dr: ROTATE, // delta rotate
radius: Math.min(canvas.height, canvas.width) / 2 * WHEEL_SIZE,
text: "wheel",
update() { this.r += this.dr },
draw() {
const aXx = Math.cos(this.r);
const aXy = Math.sin(this.r);
ctx.setTransform(aXx, aXy, -aXy, aXx, this.x, this.y);
ctx.fillStyle = "#CCC";
ctx.strokeStyle = "#000";
ctx.lineWidth = 6;
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
ctx.strokeStyle = ctx.fillStyle = "#aaa";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.lineTo(-20,0);
ctx.lineTo(20,0);
ctx.moveTo(0,-20);
ctx.lineTo(0,20);
ctx.stroke();
ctx.fillText(this.text, 0, this.radius - 16);
},
}
<canvas id="canvas" width="300" height="300"></canvas>
Centripetal force
Centripetal is the force towards the center of the turning wheel. However because the ball is sliding the force calculated is not a centripetal force.
You can calculate the centripetal force by scaling the vector from the ball to the center by the dot product of the "vector to center" dot "force vector"
The force vector on the ball is shown as a white arrow. The arrows size is the force as acceleration in pixels per second.
The vector is towards the center but will never point directly at the center of the wheel.
Approximation
This simulation is an approximation. You will need an understanding of calculus and differential equations to get closer to reality.
Using a more complex simulation would only be noticeable if the friction was very close or at 1 and it is easier then to just fix the ball to the wheel, scaling the position from center by an inverse power of the friction coefficient.
I'm creating a color wheel (picker) and I want to know the fastest most efficient way to display the color wheel. I'm currently using JavaScript to generate it with a canvas. I think the other options are an actual image or data URI. If there is a faster way please let me know.
What's the fastest most efficient way to show the color picker?
Color Wheel using JavaScript / canvas
JSFiddle
var colorDisc = document.getElementById('surface'),
colorDiscRadius = colorDisc.offsetHeight / 2;
var drawDisk = function(ctx, coords, radius, steps, colorCallback) {
var x = coords[0] || coords, // coordinate on x-axis
y = coords[1] || coords, // coordinate on y-axis
a = radius[0] || radius, // radius on x-axis
b = radius[1] || radius, // radius on y-axis
angle = 360,
rotate = 0,
coef = Math.PI / 180;
ctx.save();
ctx.translate(x - a, y - b);
ctx.scale(a, b);
steps = (angle / steps) || 360;
for (; angle > 0; angle -= steps) {
ctx.beginPath();
if (steps !== 360) ctx.moveTo(1, 1); // stroke
ctx.arc(1, 1, 1, (angle - (steps / 2) - 1) * coef, (angle + (steps / 2) + 1) * coef);
if (colorCallback) {
colorCallback(ctx, angle);
} else {
ctx.fillStyle = 'black';
ctx.fill();
}
}
ctx.restore();
},
drawCircle = function(ctx, coords, radius, color, width) { // uses drawDisk
width = width || 1;
radius = [
(radius[0] || radius) - width / 2, (radius[1] || radius) - width / 2
];
drawDisk(ctx, coords, radius, 1, function(ctx, angle) {
ctx.restore();
ctx.lineWidth = width;
ctx.strokeStyle = color || '#000';
ctx.stroke();
});
};
if (colorDisc.getContext) {
drawDisk( // HSV color wheel with white center
colorDisc.getContext("2d"), [colorDisc.width / 2, colorDisc.height / 2], [colorDisc.width / 2 - 1, colorDisc.height / 2 - 1],
360,
function(ctx, angle) {
var gradient = ctx.createRadialGradient(1, 1, 1, 1, 1, 0);
gradient.addColorStop(0, 'hsl(' + (360 - angle + 0) + ', 100%, 50%)');
gradient.addColorStop(1, "#FFFFFF");
ctx.fillStyle = gradient;
ctx.fill();
}
);
drawCircle( // gray border
colorDisc.getContext("2d"), [colorDisc.width / 2, colorDisc.height / 2], [colorDisc.width / 2, colorDisc.height / 2],
'#555',
3
);
}
<canvas id="surface" width="500" height="500"></canvas>
I think an image would be faster, but it would be difficult to resize it without getting all kinds of scaling artifacts. So I would go with canvas.
However, there is a third option that I think is worth considering: angular gradient in CSS. Here is a way to do it with existing features: https://css-tricks.com/conical-gradients-css/