I can't figure out how to give all the different circles a random color. Right now my code gives all the circles the same color but it switches when you refresh.
I want it to give all the circles different colors. I have tried two ways but they don't seem to work.
Here is my code
var can = document.getElementById('canvas');
can.width = window.innerWidth;
can.height = window.innerHeight;
var ctx = can.getContext('2d');
function Circle(centerX, centerY, speedX, speedX, radius) {
this.centerX = centerX;
this.centerY = centerY;
this.speedX = speedX;
this.speedY = speedY;
this.radius = radius;
this.draw = function () {
ctx.beginPath();
ctx.arc(this.centerX, this.centerY, this.radius, 0, Math.PI * 2);
ctx.fill();
}
this.update = function () {
this.ctx.fillStyle =
this.centerX += this.speedX;
if (this.centerX + this.radius > innerWidth || this.centerX - this.radius < 0) {
this.speedX = -this.speedX;
}
this.centerY += this.speedY;
if (this.centerY + this.radius > innerHeight || this.centerY - this.radius < 0) {
this.speedY = -this.speedY;
}
this.draw();
}
}
var circleArray = [];
var circleAmount = 45;
for (var i = 0; i < circleAmount; i++) {
var centerX = Math.random() * window.innerWidth;
var centerY = Math.random() * window.innerWidth;
var radius = (Math.random() * 24) + 3;
ctx.fillStyle = 'rgba(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + ".5" + ')';
var speedY = (Math.random() - 0.5) * 3;
var speedX = (Math.random() - 0.5) * 6;
circleArray.push(new Circle(centerX, centerY, speedX, speedY, radius));
}
function draw() {
requestAnimationFrame(draw);
ctx.clearRect(0, 0, innerWidth, innerHeight);
for(var i = 0; i < circleArray.length; i++){
circleArray[i].update();
}
}
draw();
Pass the color to the Circle constructor function, and set the fillColor in the update function:
var can = document.getElementById('canvas');
can.width = window.innerWidth;
can.height = window.innerHeight;
var ctx = can.getContext('2d');
function Circle(centerX, centerY, speedX, speedX, radius, color) {
this.centerX = centerX;
this.centerY = centerY;
this.speedX = speedX;
this.speedY = speedY;
this.radius = radius;
this.color = color;
this.draw = function() {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.arc(this.centerX, this.centerY, this.radius, 0, Math.PI * 2);
ctx.fill();
}
this.update = function() {
this.centerX += this.speedX;
if (this.centerX + this.radius > innerWidth || this.centerX - this.radius < 0) {
this.speedX = -this.speedX;
}
this.centerY += this.speedY;
if (this.centerY + this.radius > innerHeight || this.centerY - this.radius < 0) {
this.speedY = -this.speedY;
}
this.draw();
}
}
var circleArray = [];
var circleAmount = 45;
for (var i = 0; i < circleAmount; i++) {
var centerX = Math.random() * window.innerWidth;
var centerY = Math.random() * window.innerWidth;
var radius = (Math.random() * 24) + 3;
var color = 'rgba(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + ".5" + ')';
var speedY = (Math.random() - 0.5) * 3;
var speedX = (Math.random() - 0.5) * 6;
circleArray.push(new Circle(centerX, centerY, speedX, speedY, radius, color));
}
function draw() {
requestAnimationFrame(draw);
ctx.clearRect(0, 0, innerWidth, innerHeight);
for (var i = 0; i < circleArray.length; i++) {
circleArray[i].update();
}
}
draw();
<canvas id="canvas"></canvas>
i have made a an animations in canvas html but now i want to make the origin from where the balls originate move around the canvas according to my mouse position. i want to add mouse event function but i can't seem to get the logic straightand add added to the code , any help would be appreciated !
function runParticles () {
var canvas = document.createElement("canvas");
c = canvas.getContext("2d");
var particles = {};
var particleIndex = 0;
var particleNum = 15;
// set canvas size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// add canvas to body
document.body.appendChild(canvas);
// style the canvas
c.fillStyle = "black";
c.fillRect(0, 0, canvas.width, canvas.height);
function Particle() {
this.x = canvas.width / 2;
this.y = canvas.height / 2;
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
this.gravity = 0.3;
particleIndex++;
particles[particleIndex] = this;
this.id = particleIndex;
this.life = 0;
this.maxLife = Math.random() * 30 + 60;
this.color = "hsla(" + parseInt(Math.random() * 360, 10) + ",90%,60%,0.5";
}
Particle.prototype.draw = function() {
this.x += this.vx;
this.y += this.vy;
if (Math.random() < 0.1) {
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
}
this.life++;
if (this.life >= this.maxLife) {
delete particles[this.id];
}
c.fillStyle = this.color;
//c.fillRect(this.x, this.y, 5, 10);
c.beginPath();
c.arc(this.x, this.y, 2.5, degToRad(0), degToRad(360));
c.fill();
};
setInterval(function() {
//normal setting before drawing over canvas w/ black background
c.globalCompositeOperation = "source-over";
c.fillStyle = "rgba(0,0,0,0.5)";
c.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < particleNum; i++) {
new Particle();
}
// c.globalCompositeOperation = "darken";
for (var i in particles) {
particles[i].draw();
}
}, 30);
}
function degToRad(deg) {
var radians = (deg * Math.PI / 180) - Math.PI / 2;
return radians;
}
<!DOCTYPE html5>
<html>
<head>
<title>disturbed</title>
<script src="toto.js" type="text/javascript"></script>
<script>
window.onload = () => runParticles();
</script>
</head>
<body>
</body>
</html>
I've added a function to detect the mouse position:
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
I've declared a variable m (the mouse).
var m = {x:canvas.width/2,y:canvas.height/2};
and I've changed the origin of the particles from this.x = canvas.width / 2;
this.y = canvas.height / 2;to this.x = m.x; this.y = m.y;
This is the position when the mouse do not move over the canvas
I've added an event "mousemove". When the mouse move over the canvas it's position change.
canvas.addEventListener("mousemove", (evt)=>{
m = oMousePos(canvas, evt);
})
I've also added an event "mouseleave". When the mouse leaves the canvas, the mouse goes back in the center.
canvas.addEventListener("mouseleave", (evt)=>{
m = {x:canvas.width/2,y:canvas.height/2};
})
Also I've changed the setInterval for requestAnimationFrame ( much more efficient ). The code inside is your code.
function runParticles () {
var canvas = document.createElement("canvas");
c = canvas.getContext("2d");
var particles = {};
var particleIndex = 0;
var particleNum = 8;
// set canvas size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var m = {x:canvas.width/2,y:canvas.height/2};///////
// add canvas to body
document.body.appendChild(canvas);
// style the canvas
c.fillStyle = "black";
c.fillRect(0, 0, canvas.width, canvas.height);
function Particle() {
this.x = m.x;//////////
this.y = m.y;//////////
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
this.gravity = 0.3;
particleIndex++;
particles[particleIndex] = this;
this.id = particleIndex;
this.life = 0;
this.maxLife = Math.random() * 30 + 60;
this.color = "hsla(" + parseInt(Math.random() * 360, 10) + ",90%,60%,0.5";
}
Particle.prototype.draw = function() {
this.x += this.vx;
this.y += this.vy;
if (Math.random() < 0.1) {
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
}
this.life++;
if (this.life >= this.maxLife) {
delete particles[this.id];
}
c.fillStyle = this.color;
//c.fillRect(this.x, this.y, 5, 10);
c.beginPath();
c.arc(this.x, this.y, 2.5, degToRad(0), degToRad(360));
c.fill();
};
function Draw() {
window.requestAnimationFrame(Draw);
c.globalCompositeOperation = "source-over";
c.fillStyle = "rgba(0,0,0,0.5)";
c.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < particleNum; i++) {
new Particle();
}
// c.globalCompositeOperation = "darken";
for (var i in particles) {
particles[i].draw();
}
}
Draw();
///////////////////
canvas.addEventListener("mousemove", (evt)=>{
m = oMousePos(canvas, evt);
})
canvas.addEventListener("mouseleave", (evt)=>{
m = {x:canvas.width/2,y:canvas.height/2};
})
///////////////////
}
function degToRad(deg) {
var radians = (deg * Math.PI / 180) - Math.PI / 2;
return radians;
}
runParticles();
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
body,canvas{margin:0;padding:0;}
I hope this helps.
I would like to create a spark lines with the various lengths and animate them by moving them from center to end of the canvas and the process has to loop on a canvas. To achieve this i started with a particle system.
Please check the below code and here is the codepen link https://codepen.io/yesvin/pen/XPKNYW
window.onload = function() {
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
w = canvas.width = window.innerWidth,
h = canvas.height = window.innerHeight,
centerX = w / 2,
centerY = h / 2,
speed = 0,
time = 0,
numObjects = 5,
x, y, vx, vy, life, maxLife;
var lines = {},
lineIndex = 0;
function Line() {
this.x = centerX;
this.y = centerY;
this.vx = Math.random() * 16 - 8;
this.vy = Math.random() * 16 - 8;
this.life = 0;
this.maxLife = Math.random() * 10 + 20;
lineIndex++;
lines[lineIndex] = this;
this.id = lineIndex;
}
Line.prototype.draw = function() {
this.x += this.vx;
this.y += this.vy;
this.life++;
if (this.life >= this.maxLife) {
delete lines[this.id];
}
context.fillStyle = "#000";
context.fillRect(this.x, this.y, 3, 3)
}
setInterval(function() {
context.fillStyle = 'rgba(255,255,255,.05)';
context.fillRect(0, 0, w, h);
new Line();
for (var i in lines) {
lines[i].draw();
}
}, 30)
};
body {
overflow: hidden;
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<canvas id="canvas"></canvas>
So, How do we create this same effect using the lineTo(), and moveTo() methods? I tried with the following code (which is commented in a codepen)
context.beginPath();
context.moveTo(centerX, centerY);
context.lineTo(this.x * Math.random() * w, this.y * Math.random() * h);
context.lineWidth = 1;
context.stroke();
context.strokeStyle = "#000";
Sample GIF
Thanks in advance.
Here is one approach...
With lines you will get a more continuous effect:
The change I'm making to your code is to keep track of the start and end points.
window.onload = function() {
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
w = canvas.width = window.innerWidth,
h = canvas.height = window.innerHeight,
centerX = w / 2,
centerY = h / 2;
var lines = {},
lineIndex = 0;
function Line() {
this.start = { x: centerX, y: centerY };
this.end = { x: centerX, y: centerY };
this.vx = Math.random() * 16 - 8;
this.vy = Math.random() * 16 - 8;
this.life = 0;
this.maxLife = Math.random() * 10 + 20;
lineIndex++;
lines[lineIndex] = this;
this.id = lineIndex;
}
Line.prototype.draw = function() {
this.end.x += this.vx;
this.end.y += this.vy;
this.life++;
if (this.life >= this.maxLife) {
delete lines[this.id];
}
//context.fillStyle = "#000";
//context.fillRect(this.x, this.y, 1, 1)
context.beginPath();
context.moveTo(this.start.x, this.start.y);
context.lineTo(this.end.x, this.end.y);
context.lineWidth = 1;
context.stroke();
context.strokeStyle = "#000";
}
setInterval(function() {
context.fillStyle = 'rgba(255,255,255,.05)';
context.fillRect(0, 0, w, h);
new Line();
for (var i in lines) {
lines[i].draw();
}
}, 30)
};
body {
overflow: hidden;
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<canvas id="canvas"></canvas>
I'm populating the canvas with arcs at random position but now I want them to move to the center of the canvas, but they just jump to the center of the canvas and not slowly moving to the center.
The road so far
<canvas id="myCanvas"></canvas>
<script>
var myCanvas = document.getElementById("myCanvas");
var c = myCanvas.getContext("2d");
myCanvas.style.backgroundColor = "#000";
myCanvas.width = 600;
myCanvas.height = 600;
var myArr = [];
var firstCircle;
function Circledraw(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.draw = function() {
c.beginPath();
c.arc(this.x, this.y, this.r, 0, Math.PI * 2);
c.fillStyle = "#fff";
c.fill();
}
this.update = function() {
this.x = myCanvas.clientWidth/2;
this.y = myCanvas.clientHeight/2;
}
}
for(var i =0; i < 10; i++){
var x = Math.random() * myCanvas.clientWidth;
var y = Math.random() * myCanvas.clientHeight;
var r = 20;
firstCircle = new Circledraw(x, y, 20);
firstCircle.draw();
myArr.push(firstCircle);
}
setInterval(circleFall, 1000);
function circleFall() {
c.clearRect(0,0, myCanvas.clientWidth, myCanvas.clientHeight);
for(var z =0; z < myArr.length; z++){
myArr[z].update();
firstCircle.draw();
}
}
</script>
How do i fix this??
EDIT:
<script>
var canvas = document.getElementById("myCanvas"); var c = canvas.getContext("2d");
canvas.width = 600;
canvas.height = 600;
canvas.style.backgroundColor = "#e74c3c";
function Vectors(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
var centerX = canvas.width/2;
var centerY = canvas.height/2;
var diffX = centerX - this.x;
var diffY = centerY - this.y;
var angle = Math.atan(diffY, diffX);
var speed = 1;
var vector = {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed
}
this.draw = function() {
c.beginPath();
c.arc(this.x, this.y, this.r, 0, Math.PI * 2);
c.fillStyle = '#fff';
c.fill();
}
this.update = function() {
this.x += vector.x;
this.y += vector.y;
}
}
var newCircle = new Vectors(90, 100, 10);
function animate() {
window.requestAnimationFrame(animate);
c.clearRect(0, 0, canvas.width, canvas.height);
newCircle.update();
newCircle.draw();
}
animate();
The first arc is placed with co-ordinates rather than random position but it's not moving towards the center.
The myCanvas.clientWidth/2 and myCanvas.clientHeight/2 will always return the same result, in this case the center point of the canvas.
A better approach is to use a vector based on the angle between the original point and center - something like:
var diffX = myCanvas.width / 2 - this.x,
diffY = myCanvas.height / 2 - this.y,
angle = Math.atan2(diffY, diffX),
speed = 1;
var vector = {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed
};
Then in the update method add the vector to the position:
this.update = function() {
// todo add some checks here to see if it's close enough to center
this.x += vector.x;
this.y += vector.y;
}
Combine this with requestAnimationFrame() instead of using setInterval() will make the animation silk smooth as well.
var myCanvas = document.getElementById("myCanvas");
var c = myCanvas.getContext("2d");
myCanvas.style.backgroundColor = "#000";
myCanvas.width = 600;
myCanvas.height = 600;
var myArr = [];
var firstCircle;
function Circledraw(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
var cx = myCanvas.width/2,
cy = myCanvas.height/2,
diffX = cx - this.x,
diffY = cy - this.y,
angle = Math.atan2(diffY, diffX),
speed = 1;
var tolerance = 2;
var vector = {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed
};
this.update = function() {
if (!(this.x > cx - tolerance && this.x < cx + tolerance &&
this.y > cy - tolerance && this.y < cy + tolerance)) {
this.x += vector.x;
this.y += vector.y;
}
else { /* this can be used to detect finished action */ }
}
this.draw = function() {
c.beginPath();
c.arc(this.x, this.y, this.r, 0, Math.PI * 2);
c.fillStyle = "#fff";
c.fill();
}
}
for(var i =0; i < 10; i++){
var x = Math.random() * myCanvas.width;
var y = Math.random() * myCanvas.height;
var r = 20;
firstCircle = new Circledraw(x, y, 20);
firstCircle.draw();
myArr.push(firstCircle);
}
(function circleFall() {
c.clearRect(0,0, myCanvas.clientWidth, myCanvas.clientHeight);
for(var z =0; z < myArr.length; z++){
myArr[z].update();
myArr[z].draw(); // make sure to draw th current circle
}
requestAnimationFrame(circleFall);
})();
<canvas id="myCanvas"></canvas>
A different approach is to use linear interpolation which allows the dots to finish in center at the same time:
var myCanvas = document.getElementById("myCanvas");
var c = myCanvas.getContext("2d");
myCanvas.style.backgroundColor = "#000";
myCanvas.width = 600;
myCanvas.height = 600;
var myArr = [];
var firstCircle;
function Circledraw(x, y, r, step) {
var startX, startY;
var cx = myCanvas.width / 2;
var cy = myCanvas.height / 2;
this.x = startX = x;
this.y = startY = y;
this.r = r;
this.t = 0;
this.step = step; // since we work with normalized values, this tend to be small
function lerp(v1, v2, t) { // linear interpolation
return v1 + (v2 - v1) * t; // t = [0.0, 1.0] 0 = v1, 1 = v2
}
this.update = function() {
if (this.t <= 1) {
this.x = lerp(startX, cx, this.t); // set abs. position based on t
this.y = lerp(startY, cy, this.t);
this.t += this.step; // increase step for t
}
else {
/* this can be used to detect finished action, for example resetting pos */
this.x = startX = Math.random() * myCanvas.width;
this.y = startY = Math.random() * myCanvas.height;
this.t = 0;
}
}
this.draw = function() {
c.beginPath();
c.arc(this.x, this.y, this.r, 0, Math.PI * 2);
c.fillStyle = "#fff";
c.fill();
}
}
for(var i =0; i < 10; i++){
var x = Math.random() * myCanvas.width;
var y = Math.random() * myCanvas.height;
var r = 20;
firstCircle = new Circledraw(x, y, 20, Math.random() * 0.04 + 0.005);
firstCircle.draw();
myArr.push(firstCircle);
}
(function circleFall() {
c.clearRect(0,0, myCanvas.clientWidth, myCanvas.clientHeight);
for(var z =0; z < myArr.length; z++){
myArr[z].update();
myArr[z].draw(); // make sure to draw th current circle
}
requestAnimationFrame(circleFall);
})();
<canvas id="myCanvas"></canvas>
I would like to draw a spring in a HTML5 canvas, and show if that spring is at its rest length or not.
My spring is attached to a rectangular shape to some X-Y coordinates and defined as follows:
function Spring(restLenght, width, numRounds){
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = 0;
this.restLenght = restLenght;
this.width = width;
this.numRounds = numRounds;
this.color = "green";
this.lineWidth = 6;
}
The parameters are explained in the picture below:
When the spring is at its rest length, the lines shall be parallel to each other, otherwise this means the spring is stretched or compressed. Then it will be immediately clear what state the spring is.
I'm stuck now with the bezierCurveTo() Method:
Here is my Fiddle: https://jsfiddle.net/df3mm8kz/1/
var cv = document.getElementById('cv'),
ctx = cv.getContext('2d'),
mouse = capture(cv),
box = new Box(120, 80, 0, 16),
spring = new Spring(160, 20, 2, 0.03, 0.9),
vx = 0,
vy = 0;
function Spring(restLenght, width, numRounds, k, f){
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = 0;
this.restLenght = restLenght;
this.width = width;
this.numRounds = numRounds;
this.k = k;
this.f = f;
this.color = "green";
this.lineWidth = 6;
}
Spring.prototype.draw = function(ctx) {
var sPX, sPY, cP1X, cP1Y, cP2X, cP2Y, ePX, ePY;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.color;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.moveTo(this.x1, this.y1);
// length of one spring's round
var l = this.restLenght/(this.numRounds + 2);
// Initial segment, from spring anchor point to the first round
sPX = this.x1+l; sPY = this.y2;
ctx.lineTo(sPX, sPY);
// half width of spring's rounds
var hw = 0.5*this.width;
// half length of one spring's round
var hl = 0.5*l;
for(var i=0, n=this.numRounds; i<n; i++) {
cP1X = sPX + hl*i; cP1Y = sPY + hw;
cP2X = sPX + l*i; cp2Y = sPY + hw;
ePX = sPX + l*i; ePY = sPY;
ctx.bezierCurveTo(cP1X,cP1Y,cP2X,cp2Y,ePX,ePY);
cP1X = sPX + hl*i; cP1Y = sPY - hw;
cP2X = sPX + l*i; cp2Y = sPY - hw;
ePX = sPX + l*i; ePY = sPY;
ctx.bezierCurveTo(cP1X,cP1Y,cP2X,cp2Y,ePX,ePY);
}
// Final segment, from last springs round to the center of mass
ctx.lineTo(this.x2, this.y2);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
};
function Box(w, h, mx, my) {
this.x = 0;
this.y = 0;
this.w = w;
this.h = h;
this.mx = mx;
this.my = my;
this.vx = 0;
this.vy = 0;
this.rotation = 0;
this.color = "red";
this.lineWidth = 1;
}
Box.prototype.draw = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = "black";
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.rect(-0.5*this.w, -0.5*this.h, this.w, this.h);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "yellow";
ctx.fillStyle = "yellow";
ctx.arc(this.mx, 0.5*this.h-this.my, 6, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.closePath();
ctx.fill();
ctx.restore();
};
window.requestAnimFrame = (
function(callback) {
return window.setTimeout(callback, 1000/30);
});
(function drawFrame() {
window.requestAnimFrame(drawFrame, cv);
ctx.clearRect(0, 0, cv.width, cv.height);
var dx = box.x - mouse.x,
dy = box.y - mouse.y,
angle = Math.atan2(dy, dx),
boxAngle = angle + 0.5*Math.PI,
targetX = mouse.x + Math.cos(angle) * spring.restLenght,
targetY = mouse.y + Math.sin(angle) * spring.restLenght;
vx += (targetX - box.x) * spring.k;
vy += (targetY - box.y) * spring.k;
vx *= spring.f;
vy *= spring.f;
box.rotation = boxAngle;
box.x += vx;
box.y += vy;
box.draw(ctx);
spring.x1 = mouse.x;
spring.y1 = mouse.y;
spring.x2 = box.x;
spring.y2 = box.y;
spring.draw(ctx);
}());
function capture(element) {
var mouse = {
x: 0,
y: 0,
event: null
},
body_scrollLeft = document.body.scrollLeft,
element_scrollLeft = document.documentElement.scrollLeft,
body_scrollTop = document.body.scrollTop,
element_scrollTop = document.documentElement.scrollTop,
offsetLeft = element.offsetLeft,
offsetTop = element.offsetTop;
element.addEventListener('mousemove', function(event) {
var x, y;
if (event.pageX || event.pageY) {
x = event.pageX;
y = event.pageY;
} else {
x = event.clientX + body_scrollLeft + element_scrollLeft;
y = event.clientY + body_scrollTop + element_scrollTop;
}
x -= offsetLeft;
y -= offsetTop;
mouse.x = x;
mouse.y = y;
mouse.event = event;
}, false);
return mouse;
}
<canvas id="cv" width="600" height="400"></canvas>
Drawing a spring
Rather than use bezier curves which do not actually fit the curve of a spring (but close) I just use a simple path and use trig functions to draw each winding. the function has a start x1,y1 and end x2, y2, windings (should be an integer), width of spring, the offset (bits at ends), Dark colour, and light colour, and the stroke width (width of the wire).
The demo draws an extra highlight to give the spring a little more depth. It can easily be removed.
The code came from this answer that has a simpler version of the same function
function drawSpring(x1, y1, x2, y2, windings, width, offset, col1, col2, lineWidth){
var x = x2 - x1;
var y = y2 - y1;
var dist = Math.sqrt(x * x + y * y);
var nx = x / dist;
var ny = y / dist;
ctx.strokeStyle = col1
ctx.lineWidth = lineWidth;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(x1,y1);
x1 += nx * offset;
y1 += ny * offset;
x2 -= nx * offset;
y2 -= ny * offset;
var x = x2 - x1;
var y = y2 - y1;
var step = 1 / (windings);
for(var i = 0; i <= 1-step; i += step){ // for each winding
for(var j = 0; j < 1; j += 0.05){
var xx = x1 + x * (i + j * step);
var yy = y1 + y * (i + j * step);
xx -= Math.sin(j * Math.PI * 2) * ny * width;
yy += Math.sin(j * Math.PI * 2) * nx * width;
ctx.lineTo(xx,yy);
}
}
ctx.lineTo(x2, y2);
ctx.lineTo(x2 + nx * offset, y2 + ny * offset)
ctx.stroke();
ctx.strokeStyle = col2
ctx.lineWidth = lineWidth - 4;
var step = 1 / (windings);
ctx.beginPath();
ctx.moveTo(x1 - nx * offset, y1 - ny * offset);
ctx.lineTo(x1, y1);
ctx.moveTo(x2, y2);
ctx.lineTo(x2 + nx * offset, y2 + ny * offset)
for(var i = 0; i <= 1-step; i += step){ // for each winding
for(var j = 0.25; j <= 0.76; j += 0.05){
var xx = x1 + x * (i + j * step);
var yy = y1 + y * (i + j * step);
xx -= Math.sin(j * Math.PI * 2) * ny * width;
yy += Math.sin(j * Math.PI * 2) * nx * width;
if(j === 0.25){
ctx.moveTo(xx,yy);
}else{
ctx.lineTo(xx,yy);
}
}
}
ctx.stroke();
}
function display() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
ctx.lineWidth = 8;
drawSpring(canvas.width / 2,10, mouse.x,mouse.y,8,100,40,"green","#0C0",15);
}
// Boiler plate code from here down and not part of the answer
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true;
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas, resizeCanvas, setGlobals, resizeCount = 0;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if(firstRun){
onResize();
firstRun = false;
}else{
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
crashRecover : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") {
m.buttonRaw |= m.bm[e.which - 1];
} else if (t === "mouseup") {
m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") {
m.buttonRaw = 0;
m.over = false;
} else if (t === "mouseover") {
m.over = true;
} else if (t === "mousewheel") {
m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") {
m.w = -e.detail;
}
if (m.callbacks) {
m.callbacks.forEach(c => c(e));
}
if ((m.buttonRaw & 2) && m.crashRecover !== null) {
if (typeof m.crashRecover === "function") {
setTimeout(m.crashRecover, 0);
}
}
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === undefined) {
m.callbacks = [callback];
} else {
m.callbacks.push(callback);
}
}
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
return mouse;
})();
// Clean up. Used where the IDE is on the same page.
var done = function () {
window.removeEventListener("resize", resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = undefined;
}
function update(timer) { // Main update loop
if(ctx === undefined){
return;
}
globalTime = timer;
display(); // call demo code
if (!(mouse.buttonRaw & 2)) {
requestAnimationFrame(update);
} else {
done();
}
}
setTimeout(function(){
resizeCanvas();
mouse.start(canvas, true);
mouse.crashRecover = done;
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
/** SimpleFullCanvasMouse.js end **/
To make drawing easier, use .translate() and .rotate() to move into an aligned coordinate system.
ctx.translate(this.x1, this.y1);
ctx.rotate(Math.atan2(this.y2 - this.y1, this.x2 - this.x1));
You can then draw the spring along the local x-axis, and it will appear in the correct place and rotation.
Your spacing of the segments were wrong. hl*i is half the distance from the spring's starting point, not the segment's starting point.
var cv = document.getElementById('cv'),
ctx = cv.getContext('2d'),
mouse = capture(cv),
box = new Box(120, 80, 0, 16),
spring = new Spring(160, 50, 2, 0.03, 0.9),
vx = 0,
vy = 0;
function Spring(restLenght, width, numRounds, k, f) {
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = 0;
this.restLenght = restLenght;
this.width = width;
this.numRounds = numRounds;
this.k = k;
this.f = f;
this.color = "green";
this.lineWidth = 6;
}
Spring.prototype.draw = function(ctx) {
var sPX, sPY, cP1X, cP1Y, cP2X, cP2Y, ePX, ePY;
ctx.save();
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.color;
ctx.fillStyle = this.color;
var vx = this.x2 - this.x1;
var vy = this.y2 - this.y1;
var vm = Math.sqrt(vx * vx + vy * vy);
ctx.translate(this.x1, this.y1);
ctx.rotate(Math.atan2(vy, vx));
ctx.beginPath();
ctx.moveTo(0, 0);
// length of one spring's round
var l = vm / (this.numRounds + 2);
// Initial segment, from spring anchor point to the first round
sPX = l;
sPY = 0;
ctx.lineTo(sPX, sPY);
// half width of spring's rounds
var hw = 0.5 * this.width;
for (var i = 0, n = this.numRounds; i < n; i++) {
cP1X = sPX + l * (i + 0.0);
cP1Y = sPY + hw;
cP2X = sPX + l * (i + 0.5);
cp2Y = sPY + hw;
ePX = sPX + l * (i + 0.5);
ePY = sPY;
ctx.bezierCurveTo(cP1X, cP1Y, cP2X, cp2Y, ePX, ePY);
cP1X = sPX + l * (i + 0.5);
cP1Y = sPY - hw;
cP2X = sPX + l * (i + 1.0);
cp2Y = sPY - hw;
ePX = sPX + l * (i + 1.0);
ePY = sPY;
ctx.bezierCurveTo(cP1X, cP1Y, cP2X, cp2Y, ePX, ePY);
}
// Final segment, from last springs round to the center of mass
ctx.lineTo(vm, 0);
//ctx.closePath();
//ctx.fill();
ctx.stroke();
ctx.restore();
};
function Box(w, h, mx, my) {
this.x = 0;
this.y = 0;
this.w = w;
this.h = h;
this.mx = mx;
this.my = my;
this.vx = 0;
this.vy = 0;
this.rotation = 0;
this.color = "red";
this.lineWidth = 1;
}
Box.prototype.draw = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = "black";
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.rect(-0.5 * this.w, -0.5 * this.h, this.w, this.h);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "yellow";
ctx.fillStyle = "yellow";
ctx.arc(this.mx, 0.5 * this.h - this.my, 6, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.closePath();
ctx.fill();
ctx.restore();
};
window.requestAnimFrame = (
function(callback) {
return window.setTimeout(callback, 1000 / 30);
});
(function drawFrame() {
window.requestAnimFrame(drawFrame, cv);
ctx.clearRect(0, 0, cv.width, cv.height);
var dx = box.x - mouse.x,
dy = box.y - mouse.y,
angle = Math.atan2(dy, dx),
boxAngle = angle + 0.5 * Math.PI,
targetX = mouse.x + Math.cos(angle) * spring.restLenght,
targetY = mouse.y + Math.sin(angle) * spring.restLenght;
vx += (targetX - box.x) * spring.k;
vy += (targetY - box.y) * spring.k;
vx *= spring.f;
vy *= spring.f;
box.rotation = boxAngle;
box.x += vx;
box.y += vy;
box.draw(ctx);
spring.x1 = mouse.x;
spring.y1 = mouse.y;
spring.x2 = box.x;
spring.y2 = box.y;
spring.draw(ctx);
}());
function capture(element) {
var mouse = {
x: 0,
y: 0,
event: null
},
body_scrollLeft = document.body.scrollLeft,
element_scrollLeft = document.documentElement.scrollLeft,
body_scrollTop = document.body.scrollTop,
element_scrollTop = document.documentElement.scrollTop,
offsetLeft = element.offsetLeft,
offsetTop = element.offsetTop;
element.addEventListener('mousemove', function(event) {
var x, y;
if (event.pageX || event.pageY) {
x = event.pageX;
y = event.pageY;
} else {
x = event.clientX + body_scrollLeft + element_scrollLeft;
y = event.clientY + body_scrollTop + element_scrollTop;
}
x -= offsetLeft;
y -= offsetTop;
mouse.x = x;
mouse.y = y;
mouse.event = event;
}, false);
return mouse;
}
<canvas id="cv" width="600" height="400"></canvas>