I am trying to make a circle follow the mouse in HTML Canvas which I am using in a game. I am trying to make the circle move 5px per iteration, but it goes slower when traveling horizontal and faster when it goes vertical. Here's the math that I used:
x=distance between mouse and circle on the x-axis
y=distance between mouse and circle on the y-axis
z=shortest distance between mouse and circle
a=number of units circle should move along the x-axis
b=number of units circle should move along the y axis
x^2 + y^2=z^2
Want the total distance traveled every iteration to be five pixels
a^2 + b^2 = 25
b/a=y/x
b=ay/x
a=sqrt(25-ay/x^2)
a^2+ay/x-25=0
Use Quadratic formula to find both answers
a=(-y/x+-sqrt(y/x)^2+100)/2
I replicated the problem in the code below
$(function(){
let canvas = $("canvas")[0];
let ctx = canvas.getContext("2d");
//Gets position of mouse and stores the value in variables mouseX and mouseY
let mouseX = mouseY = 0;
$("canvas").mousemove(function(e){
mouseX = e.pageX;
mouseY = e.pageY;
}).trigger("mousemove");
let circleX = 0;
let circleY = 0;
function loop(t){
//Background
ctx.fillStyle="blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
let xFromMouse = mouseX-circleX;
let yFromMouse = mouseY-circleY;
let yxRatio = yFromMouse/xFromMouse;
let xyRatio = xFromMouse/yFromMouse;
let speed = 25;
let possibleXValues = [(-yxRatio+Math.sqrt(Math.pow(yxRatio,2)+(4*speed)))/2,(-yxRatio-Math.sqrt(Math.pow(yxRatio,2)+(4*speed)))/2];
//I use this code as a temporary fix to stop the circle from completely disappearing
if(xFromMouse === 0 || isNaN(yxRatio) || isNaN(possibleXValues[0]) || isNaN(possibleXValues[1])){
possibleXValues = [0,0];
yxRatio = 0;
}
//Uses b=ay/x to calculate for y values
let possibleYValues = [possibleXValues[0]*yxRatio,possibleXValues[1]*yxRatio];
if(xFromMouse >= 0){
circleX += possibleXValues[0];
circleY += possibleYValues[0];
} else {
circleX += possibleXValues[1];
circleY += possibleYValues[1];
}
ctx.beginPath();
ctx.arc(circleX, circleY, 25, 0, 2 * Math.PI,false);
ctx.fillStyle = "red";
ctx.lineWidth = 0;
ctx.fill();
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas width="450" height="250"></canvas>
I think you may be better using a cartesian to polar conversion. Here's an example from something I made previously. This will allow you to have a consistent step per iteration "speed".
//Canvas, context, mouse.
let c, a, m = { x:0, y:0};
//onload.
window.onload = function(){
let circle = {},
w, h,
speed = 5; //step speed = 5 "pixels" (this will be fractional in any one direction depending on direction of travel).
//setup
c = document.getElementById('canvas');
a = c.getContext('2d');
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
function move(){
//get distance and angle from mouse to circle.
let v1m = circle.x - m.x,
v2m = circle.y - m.y,
vDm = Math.sqrt(v1m*v1m + v2m*v2m),
vAm = Math.atan2(v2m, v1m);
//if distance is above some threshold, to stop jittering, move the circle by 'speed' towards mouse.
if(vDm > speed) {
circle.x -= Math.cos(vAm) * speed;
circle.y -= Math.sin(vAm) * speed;
}
}
function draw(){
//draw it all.
a.fillStyle = "blue";
a.fillRect(0,0,w,h);
a.fillStyle = "red";
a.beginPath();
a.arc(circle.x, circle.y, circle.r, Math.PI * 2, false);
a.closePath();
a.fill();
}
circle = {x:w/2, y:h/2, r:25};
function animate(){
requestAnimationFrame(animate);
move();
draw();
}
c.onmousemove = function(e){
m.x = e.pageX;
m.y = e.pageY;
};
animate();
}
<canvas id="canvas" width="450" height="250"></canvas>
Related
I'm building a basic game using plain javascript and I am trying to rotate my object to follow my mouse.
I've tried getting the client's mouse X and Y then subtracting the canvas width and height divided by two. Then taking those values and inputing it into Math.atan2(). However, I feel the issue may be in my transform and rotate. The code bellow is what I've tried.
WIDTH = c.height;
HEIGHT = c.width;
document.onmousemove = function(ve){
let cX = -c.width / 2;
let cY = -c.height / 2;
let x = ve.offsetX;
let y = ve.offsetY;
var rX = cX + x - 8;
var rY = cY + y - 8;
player.angle = Math.atan2(rX, rY) / Math.PI * 180;
}
function update(){
var now = Date.now();
dt = now - lastUpdate;
ctx.clearRect(0, 0, WIDTH, HEIGHT);
ctx.setTransform(1, 0, 0, 1, WIDTH / 2, HEIGHT / 2);
ctx.rotate(player.angle + 10);
drawCircle(player.x, player.y, 20, 0, 180, "red");
tx.setTransform(1, 0, 0, 1, 0, 0);
}
setInterval(update, dt/10000);
The player spins around my mouse in wide circles with no apparent pattern.
Here's a gif showing what's happening.
https://gyazo.com/006c99879ecf219791d059de14d98b74
In order to rotate the object to follow the mouse you need to get the angle between the previous position of the mouse and the actual position of the mouse and use this angle to rotate the object. Also the object is drawn with the tip in the origin of the canvas {x:0,y:0} so you'll need to translate the player to the position of the mouse.
I hope this is what you need.
const ctx = c.getContext("2d")
const HEIGHT = c.height = window.innerHeight;
const WIDTH = c.width = window.innerWidth;
let m = {x:0,y:0}
let prev = {x:0,y:0}
let angle = 0;
c.addEventListener("mousemove",(evt)=>{
ctx.clearRect(-WIDTH, -HEIGHT, 2*WIDTH, 2*HEIGHT);
// the previous position of the mouse
prev.x = m.x;
prev.y = m.y;
//the actual position of the mouse
m = oMousePos(c, evt);
// if the mpuse is moving get the angle between the previoue position and the actual position of the mouse
if(m.x != prev.x && m.y != prev.y){
angle = Math.atan2(m.y-prev.y, m.x-prev.x)
}
ctx.restore();
ctx.save();
ctx.translate(m.x, m.y);
ctx.rotate(angle);
drawPlayer();
})
function drawPlayer(){
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(-20,-5);
ctx.lineTo(-20,5);
ctx.lineTo(0,0);
ctx.closePath();
ctx.fill()
}
// 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)
}
}
<canvas id="c"></canvas>
As an observation: in your code you have Math.atan2(rX, rY) The first argument has to be y.
I am writing a simple JavaScript game where you are looking for a hidden image on a page. When you click on it the image appears. On every click a sound bite plays. It is essentially Marco-polo. As you get closer to the hidden object I want the volume of the sound bite to get louder. I have this working however with a linear relation between distance and volume it is quite hard to nail down exactly where the image is, so, I want to develop a relation where there is a really steep volume incline as you get really close. Something along the lines of y = x^5. Doesn't have to be x^5 but this is sort of what I have in mind.
Now, the image is placed on the page at page load randomly centred at the point (imgX, imgY). The page has dimensions (pageX, pageY) and I click on the screen at (clickX, clickY).
Now, my thinking is that there will always be a 'largest distance' LD on the page from the image coordinates (in theory this should be a corner on the screen). We can simply get the coordinates for the four corners and find the biggest distance, NBD.
The volume, going from 0 to 1 should have a function similar to
V = 1 - D
Where D is some relation I can't nail down right now.
To get a simple linear relation I am currently using
D = d / LD
Where
d = sqrt((imgX - clickX)^2 + (imgY - clickY)^2)
Edit
Just thought I would clarify my intention: 1-d/LD works however this causes a straight line increase in volume as you get closer. It's not intuitively clear but in practice as you get around 80%-100% volume it all sounds very much the same meaning that the area around the image seems to have the same volume to the human ear. I want a much more dramatic increase as you get really close. I.e., it should only get to above 80% volume when within 3-4% distance (if that makes sense)
Further to my earlier comment, here's a visualisation of what I think you need.
I've just realised that I didn't bother to re-calculate the distance from the most distant corner - I've simply used the distance from the centre of the square to the corners. This omission is the reason the red dot may be drawn to the left of the Y axis if the distance to the target exceeds the distance from the centre of the square to a corner.
Clicking on the 2nd canvas re-positions the hidden target. Moving the mouse will cause it's distance to this target to be computed. This value will then be divided by the above-mentioned max-distance-to-a-corner figure.
Finally, this value will be used as the X-coordinate of the attenuation function. The value [0..1] will be used to drive the resultant volume.
I've left a variable, steepnessFactor in the code for quick and easy modification of the attenuation curve. This value is simply the one to which the linear distance is raised to the power of.
function allByClass(clss,parent){return (parent==undefined?document:parent).getElementsByClassName(clss)}
function byId(id){return document.getElementById(id)}
window.addEventListener('load', onDocLoaded, false);
var steepnessFactor = 5; // inputs [0..1] will be raised to this power
var visSize = 128; // width/height of the 2 canvases
// click pos and corners of our window
var targetPoint;
var topLeft, topRight, botLeft, botRight;
// initialized to dist from center to (any) corner
var maxDist = (Math.sqrt(2) * visSize) / 2;
function onDocLoaded(evt)
{
targetPoint = new vec2_t(visSize/2,visSize/2);
topLeft = new vec2_t(0,0);
topRight = new vec2_t(visSize,0);
botLeft = new vec2_t(0,visSize);
botRight = new vec2_t(visSize,visSize);
var can1 = byId('graph');
var can2 = byId('map');
can1.width = visSize;
can1.height = visSize;
can2.width = visSize;
can2.height = visSize;
byId('map').addEventListener('click', onMapClicked, false);
byId('map').addEventListener('mousemove', onMapMouseMoved, false);
drawGraph();
drawMap(byId('map'));
}
function drawGraph()
{
var can = byId('graph');
var ctx = can.getContext('2d');
ctx.clearRect(0,0,can.width,can.height);
// draw the axis lines
ctx.strokeStyle = "#555555";
ctx.moveTo(0,can.height/2);
ctx.lineTo(can.width, can.height/2);
ctx.moveTo(can.width/2, 0);
ctx.lineTo(can.width/2, can.height);
ctx.stroke();
// draw the unit markers (spaced at 0.1 unit intervals)
var numDivisions = 20;
for (var x=0; x<can.width; x+= can.width/(numDivisions) )
{
ctx.moveTo(x, (can.height/2) - 4 );
ctx.lineTo(x, (can.height/2) + 4 );
}
for (var y=0; y<can.height; y+= can.height/(numDivisions) )
{
ctx.moveTo( (can.width/2)-4, y);
ctx.lineTo( (can.width/2)+4, y);
}
ctx.stroke();
var scaleX = 2 / can.width;
var scaleY = 2 / can.height;
ctx.beginPath();
ctx.moveTo(0,can.height);
for (var curX=0; curX<can.width; curX++)
{
var scaledX = -1;
scaledX += curX * scaleX;
var curY = Math.pow( scaledX, steepnessFactor); // steepness of curve
curY *= can.height/2;
curY = can.height/2 - curY;
ctx.lineTo(curX, curY);
}
ctx.strokeStyle = "#7e6cb5";
ctx.stroke();
}
function vec2_t(x,y)
{
this.x=x;
this.y=y;
this.equals = function(vec2){this.x = vec2.x; this.y = vec2.y;}
this.addVec = function(vec2){this.x += vec2.x; this.y += vec2.y;}
this.scalarMult = function(scalar){this.x *= scalar; this.y *= scalar;}
this.vecLen = function(){return Math.sqrt( this.x*this.x + this.y*this.y );}
this.normalize = function(){ let k = 1.0 / this.vecLen(); this.scalarMult(k); }
this.vecSub = function(vec2){this.x-=vec2.x;this.y-=vec2.y;}
this.toString = function(){return"<"+this.x+","+this.y+">"}
return this;
}
function onMapClicked(evt)
{
targetPoint.x = evt.offsetX;
targetPoint.y = evt.offsetY;
drawMap(this);
}
function drawMap(canvasElem)
{
var ctx = canvasElem.getContext('2d');
ctx.clearRect(0,0,canvasElem.width,canvasElem.height);
var radius = 5;
ctx.beginPath();
ctx.arc(targetPoint.x, targetPoint.y, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'green';
ctx.fill();
}
function onMapMouseMoved(evt)
{
var x = evt.offsetX, y = evt.offsetY;
var curPos = new vec2_t(x, y);
var curVec = new vec2_t();
curVec.equals( curPos );
curVec.vecSub( targetPoint );
var curDist = curVec.vecLen();
var linearDist = (1-(curDist/maxDist));
// console.log("CurDist / MaxDist = " + linearDist );
// console.log("CurValue = " + Math.pow(linearDist, 5) );
x = linearDist;
y = Math.pow(linearDist, steepnessFactor); // steepness of curve
setVolumeSVG(y * 100);
drawGraph();
var mapCan = byId('graph');
var ctx = mapCan.getContext('2d');
var scaleX = mapCan.width / 2;
var scaleY = -mapCan.height / 2;
var radius = 5;
ctx.beginPath();
ctx.arc( x*scaleX + mapCan.width/2,
y*scaleY + mapCan.height/2, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
ctx.beginPath();
}
function setVolumeSVG(percent)
{
var svg = byId('mSvg');
var barWidth = (percent/100) * svg.width.baseVal.value;
var barHeight = (percent/100) * svg.height.baseVal.value;
var msg = "0,"+svg.height.baseVal.value + " "
+ barWidth + "," + (svg.height.baseVal.value-barHeight) + " "
+ barWidth + "," + svg.height.baseVal.value;
allByClass('barSlider')[0].setAttribute('points', msg);
}
#graph{ border: solid 1px black; }
#map{ border: solid 1px red; }
<canvas width=256 height=256 id='graph'></canvas>
<canvas width=256 height=256 id='map'></canvas><br>
<svg id='mSvg' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 285 100" width=285 height=100>
<g>
<polygon class="barFrame" points="0,100 285,100 285,0"></polygon>
<polygon class='barSlider' points="0,100 143,100 143,50"></polygon>
</g>
<style>
.barFrame{ fill: #d1d3d4; }
.barSlider{ fill: #69bd45; }
</style>
</svg>
I'm attempting to draw the rotating line in this canvas animation with trailing opacity but it's not working. I've seen this effect with rectangles and arcs but never with a line, so I'm not sure what I need to add.
function radians(degrees) {
return degrees * (Math.PI / 180);
}
var timer = 0;
function sonar() {
var canvas = document.getElementById('sonar');
if (canvas) {
var ctx = canvas.getContext('2d');
var cx = innerWidth / 2,
cy = innerHeight / 2;
canvas.width = innerWidth;
canvas.height = innerHeight;
//ctx.clearRect(0, 0, innerWidth, innerHeight);
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.fillRect(0, 0, innerWidth, innerHeight);
var radii = [cy, cy - 30, innerHeight / 3.33, innerHeight / 6.67];
for (var a = 0; a < 4; a++) {
ctx.beginPath();
ctx.arc(cx, cy, radii[a], radians(0), radians(360), false);
ctx.strokeStyle = 'limegreen';
ctx.stroke();
ctx.closePath();
}
// draw grid lines
for (var i = 0; i < 12; i++) {
var x = cx + cy * Math.cos(radians(i * 30));
var y = cy + cy * Math.sin(radians(i * 30));
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.lineTo(x, y);
ctx.lineCap = 'round';
ctx.strokeStyle = 'rgba(50, 205, 50, 0.45)';
ctx.stroke();
ctx.closePath();
}
if (timer <= 360) {
timer++;
ctx.beginPath();
ctx.fillstyle = 'limegreen';
ctx.moveTo(cx, cy);
ctx.lineTo(cx + cy * Math.cos(radians(timer)), cy + cy * Math.sin(radians(timer)));
ctx.strokeStyle = 'limegreen';
ctx.stroke();
ctx.closePath();
} else {
timer = 0;
}
requestAnimationFrame(sonar);
}
}
sonar();
jsbin example
Here are two ways to do this: with a gradient and by adding translucent lines.
Sidenote, you should try and only redraw what you need to redraw. I separated the canvases and put one on top of the other so that we don't redraw the grid all the time.
function radians(degrees) {
return degrees * (Math.PI / 180);
}
var timer = 0;
function trail() {
var canvas = document.getElementById('trail');
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, innerWidth, innerHeight);
var cx = innerWidth / 2,
cy = innerHeight / 2;
canvas.width = innerWidth;
canvas.height = innerHeight;
if (timer <= 360) {
timer++;
ctx.beginPath();
ctx.fillstyle = 'limegreen';
ctx.moveTo(cx, cy);
ctx.arc(cx,cy,cy,radians(timer-30),radians(timer));
ctx.lineTo(cx + cy * Math.cos(radians(timer)), cy + cy * Math.sin(radians(timer)));
var gradient = ctx.createLinearGradient(
cx+cy*Math.cos(radians(timer)), cy+cy*Math.sin(radians(timer)),
cx+cy*0.9*Math.cos(radians(timer-30)), cy+cy*0.9*Math.sin(radians(timer-30)));
gradient.addColorStop(0,'limegreen');
gradient.addColorStop(1,'transparent');
ctx.strokeStyle='transparent';
ctx.fillStyle = gradient;
ctx.fill();
ctx.beginPath();
var fade = 10;
for(var i =0;i<fade;i++)
{
ctx.moveTo(cx, cy);
ctx.lineTo(cx+cy*Math.cos(radians(180+timer-i*1.3)),cy+cy*Math.sin(radians(180+timer-i*1.3)));
ctx.strokeStyle ="rgba(50,205,50,0.1)";
ctx.lineWidth=5;
ctx.closePath();
ctx.stroke();
}
} else {
timer = 0;
}
requestAnimationFrame(trail);
}
function sonar() {
var canvas = document.getElementById('sonar');
if (canvas) {
var ctx = canvas.getContext('2d');
var cx = innerWidth / 2,
cy = innerHeight / 2;
canvas.width = innerWidth;
canvas.height = innerHeight;
//ctx.clearRect(0, 0, innerWidth, innerHeight);
var radii = [cy, cy - 30, innerHeight / 3.33, innerHeight / 6.67];
for (var a = 0; a < 4; a++) {
ctx.beginPath();
ctx.arc(cx, cy, radii[a], radians(0), radians(360), false);
ctx.strokeStyle = 'limegreen';
ctx.stroke();
ctx.closePath();
}
// draw grid lines
for (var i = 0; i < 12; i++) {
var x = cx + cy * Math.cos(radians(i * 30));
var y = cy + cy * Math.sin(radians(i * 30));
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.lineTo(x, y);
ctx.lineCap = 'round';
ctx.strokeStyle = 'rgba(50, 205, 50, 0.45)';
ctx.stroke();
ctx.closePath();
}
}
}
sonar();
trail();
canvas{
position: absolute;
}
<canvas id=sonar></canvas>
<canvas id=trail></canvas>
The problem is that to get this effect, you need to draw a triangle with a gradient along an arc, and you can't do that in a canvas. Gradients must be linear or radial.
The other option is to have an inner loop run each time you want to draw the sweeper, and go backwards from your sweeper line, drawing with slightly less opacity each time. But lets say you want your sweep to cover 15 degrees--obviously, if you have a 100% opacity line at d and a 5% opacity line at d - 15, that doesn't do the trick. So start filling in more lines, and more lines...you will have to draw so many lines to make it seem filled your performance would probably suffer.
My suggestion--you shouldn't have to redraw that on every frame. I would just make a PNG that looks like you want it to, and then place it and just rotate it around the center on each frame. No need to redraw it all the time then. That will be much faster than drawing a bunch of lines.
Canvas stack trails.
Below is a quick demo of how to use a stack of canvases to create a trailing effect.
You have a normal on screen canvas (this FX will not effect it) and then a stack of canvases for the trail FX. Each frame you move to the next canvas in the stack, first slightly clearing it then drawing to it what you want to trail. Then you render that canvas and the one just above it to the canvas.
A point to keep in mind is that the trails can also have a hugh range of FX, like blurring (just render each frame stack on itself slightly offset each time you render to it), zoom in and out trails. Trails on top or trails under. You can change the trail distance and much more.
It is overkill but over kill is fun.
The slider above the demo controls the trail length. Also the code need babel because I dont have time to write it for ES5.
Top slider is trail amount.One under that is trail distance. Trail dist does not transition well. Sorry about that.
//==============================================================================
// helper function
function $(query,q1){
if(q1 !== undefined){
if(typeof query === "string"){
var e = document.createElement(query);
if(typeof q1 !== "string"){
for(var i in q1){
e[i] = q1[i];
}
}else{
e.id = q1;
}
return e;
}
return [...query.querySelectorAll(q1)];
}
return [...document.querySelectorAll(query)];
}
function $$(element,e1){
if(e1 !== undefined){
if(typeof element === "string"){
$(element)[0].appendChild(e1);
return e1;
}
element.appendChild(e1);
return e1;
}
document.body.appendChild(element);
return element;
}
function $E(element,types,listener){
if(typeof types === "string"){
types = types.split(",");
}
element = $(element)[0];
types.forEach(t=>{
element.addEventListener(t,listener)
});
return element;
}
function R(I){
if(I === undefined){
return Math.random();
}
return Math.floor(Math.random()*I);
}
//==============================================================================
//==============================================================================
// answer code
// canvas size
const size = 512;
const trailDist = 10; // There is this many canvases so be careful
var trailDistCurrent = 10; // distance between trails
var clearAll = false;
// create a range slider for trail fade
$$($("input",{type:"range",width : size, min:0, max:100, step:0.1, value:50, id:"trail-amount",title:"Trail amount"}));
$("#trail-amount")[0].style.width = size + "px";
$E("#trail-amount","change,mousemove",function(e){fadeAmount = Math.pow(this.value / 100,2);});
// create a range slider trail distance
$$($("input",{type:"range",width : size, min:2, max:trailDist , step:1, value:trailDist , id:"trail-dist",title:"Trail seperation"}));
$("#trail-dist")[0].style.width = size + "px";
$E("#trail-dist","change,mousemove", function(e){
if(this.value !== trailDistCurrent){
trailDistCurrent= this.value;
clearAll = true;
}
});
$$($("br","")) // put canvas under the slider
// Main canvas
var canvas;
$$(canvas = $("canvas",{width:size,height:size})); // Not jquery. Just creates a canvas
// and adds canvas to the document
var ctx = canvas.getContext("2d");
// Trailing canvas
var trailCanvases=[];
var i =0; // create trail canvas
while(i++ < trailDist){trailCanvases.push($("canvas",{width:size,height:size}));}
var ctxT = trailCanvases.map(c=>c.getContext("2d")); // get context
var topCanvas = 0;
var fadeAmount = 0.5;
// Draw a shape
function drawShape(ctx,shape){
ctx.lineWidth = shape.width;
ctx.lineJoin = "round";
ctx.strokeStyle = shape.color;
ctx.setTransform(shape.scale,0,0,shape.scale,shape.x,shape.y);
ctx.rotate(shape.rot);
ctx.beginPath();
var i = 0;
ctx.moveTo(shape.shape[i++],shape.shape[i++]);
while(i < shape.shape.length){
ctx.lineTo(shape.shape[i++],shape.shape[i++]);
}
ctx.stroke();
}
// Create some random shapes
var shapes = (function(){
function createRandomShape(){
var s = [];
var len = Math.floor(Math.random()*5 +4)*2;
while(len--){
s[s.length] = (R() + R()) * 20 * (R() < 0.5 ? -1 : 1);
}
return s;
}
var ss = [];
var i = 10;
while(i--){
ss[ss.length] = createRandomShape();
}
ss[ss.length] = [0,0,300,0]; // create single line
return ss;
})();
// Create some random poits to move the shapes
var points = (function(){
function point(){
return {
color : "hsl("+R(360)+",100%,50%)",
shape : shapes[R(shapes.length)],
width : R(4)+1,
x : R(size),
y : R(size),
scaleMax : R()*0.2 + 1,
scale : 1,
s : 0,
rot : R()*Math.PI * 2,
dr : R()*0.2 -0.1,
dx : R()*2 - 1,
dy : R()*2 - 1,
ds : R() *0.02 + 0.01,
}
}
var line = shapes.pop();
var ss = [];
var i = 5;
while(i--){
ss[ss.length] = point();
}
var s = ss.pop();
s.color = "#0F0";
s.x = s.y = size /2;
s.dx = s.dy = s.ds = 0;
s.scaleMax = 0.5;
s.dr = 0.02;
s.shape = line;
s.width = 6;
ss.push(s);
return ss;
})();
var frameCount = 0; // used to do increamental fades for long trails
function update(){
// to fix the trail distance problem when fade is low and distance high
if(clearAll){
ctxT.forEach(c=>{
c.setTransform(1,0,0,1,0,0);
c.clearRect(0,0,size,size);
});
clearAll = false;
}
frameCount += 1;
// get the next canvas that the shapes are drawn to.
topCanvas += 1;
topCanvas %= trailDistCurrent;
var ctxTop = ctxT[topCanvas];
// clear the main canvas
ctx.setTransform(1,0,0,1,0,0); // reset transforms
// Fade the trail canvas
ctxTop.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,size,size); // clear main canvas
// slowly blendout trailing layer
if(fadeAmount < 0.1){ // fading much less than this leaves perminant trails
// so at low levels just reduce how often the fade is done
if(((Math.floor(frameCount/trailDistCurrent)+topCanvas) % Math.ceil(1 / (fadeAmount * 10))) === 0 ){
ctxTop.globalAlpha = 0.1;
ctxTop.globalCompositeOperation = "destination-out";
ctxTop.fillRect(0,0,size,size);
}
}else{
ctxTop.globalAlpha = fadeAmount;
ctxTop.globalCompositeOperation = "destination-out";
ctxTop.fillRect(0,0,size,size);
}
ctxTop.globalCompositeOperation = "source-over";
ctxTop.globalAlpha = 1;
// draw shapes
for(var i = 0; i < points.length; i ++){
var p = points[i];
p.x += p.dx; // move the point
p.y += p.dy;
p.rot += p.dr;
p.s += p.ds;
p.dr += Math.sin(p.s) * 0.001;
p.scale = Math.sin(p.s) * p.scaleMax+1;
p.x = ((p.x % size) + size) % size;
p.y = ((p.y % size) + size) % size;
drawShape(ctxTop,p); // draw trailing layer (middle)
}
// draw the trail the most distance from the current position
ctx.drawImage(trailCanvases[(topCanvas + 1)%trailDistCurrent],0,0);
// do it all again.
requestAnimationFrame(update);
}
update();
I have two lines at a 90 degree angle and I want to make the angle get smaller and larger between 90 and 0 by rotating the vertical line. I'm trying to do this my changing the moveTo parameters I could increase the value but not decrease them. also could you help me make sure that the line that moves is the same length as the horizontal line during the animation. now it looks like it gets smaller then gets larger as it completes.
window.onload = function(){
var canvas =document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 50;
var x = 50;
var y= 50;
var forward = true;
(function animate(){
if(x <= 201 && y <= 201){
x++
y++
}
if(x > 195){
forward = false;
}
// console.log(x, y)
if(forward == false){
// alert("yo")
x = x - 1
y = y -1
}
console.log(x)
console.log(forward)
context.clearRect(0,0, canvas.width, canvas.height)
context.beginPath();
context.moveTo(x,y)
context.lineTo(50,200);
context.stroke();
context.closePath();
window.requestAnimationFrame(animate)
context.beginPath();
context.moveTo(50, 200);
context.lineTo(200, 200)
context.stroke();
context.closePath();
}())
}
<canvas id="canvas" width="400" height="400"></canvas>
EDIT:::
window.onload = function(){
var canvas =document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 50;
var x = 50;
var y= 50;
var dlt = 1
var forward = true;
var i = 0;
(function animate(){
if(x >= 50 && x < 200 ){
i++
x += dlt
y += dlt
console.log(i)
$(".display").html(i)
if(i >= 150){
X = 200;
Y = 200;
x -= dlt
y -= dlt
}
}
console.log("x", x)
console.log(forward)
context.clearRect(0,0, canvas.width, canvas.height)
context.beginPath();
context.moveTo(x,y)
context.lineTo(50,200);
context.stroke();
context.closePath();
window.requestAnimationFrame(animate)
context.beginPath();
context.moveTo(50, 200);
context.lineTo(200, 200)
context.stroke();
context.closePath();
}())
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="400" height="400"></canvas>
You can use transformations instead of trigonometry to draw your vertically collapsing line:
Here is annotated code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var nextTime=0;
var delay=100;
var angle=-Math.PI/2;
var cx=150;
var cy=150;
var radius=50;
requestAnimationFrame(animate);
function animate(time){
// wait until the desired time elapses
if(time<nextTime){requestAnimationFrame(animate); return;}
nextTime+=delay;
// draw the horizontal line
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
ctx.moveTo(cx,cy);
ctx.lineTo(cx+radius,cy);
ctx.stroke();
// use transformations to draw the vertical line
// at the desired angle
ctx.translate(cx,cy);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(radius,0);
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0);
// if the vertical line isn't horizontal,
// request another animation frame
if(angle<0){ requestAnimationFrame(animate); }
// adjust the angle
angle+=Math.PI/90;
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>
If you want to use Trig then you can calculate the line endpoint like this:
var lineX1 = lineX0 + radius*Math.cos(angle);
var lineY1 = lineY1 + radius*Math.sin(angle);
[Addition: add trigonometry to questioner's code]
Here is your code refactored to use trigonometry to reposition the vertical line.
var canvas =document.getElementById("canvas");
var context = canvas.getContext("2d");
var length = 50;
var x = 50;
var y= 50;
var dlt = 1
var forward = true;
var i = 0;
var angle=-Math.PI/2;
var direction=1;
(function animate(){
/*
if(x >= 50 && x < 200 ){
i++
x += dlt
y += dlt
console.log(i)
$(".display").html(i)
if(i >= 150){
X = 200;
Y = 200;
x -= dlt
y -= dlt
}
}
*/
var moveX=50+(200-50)*Math.cos(angle);
var moveY=200+(200-50)*Math.sin(angle);
// change the angle
angle+=(Math.PI/120*direction);
// if the angle is beyond vertical or horizontal then
// swing it the other way
if(angle<-Math.PI/2 || angle>0){ direction*=-1;}
context.clearRect(0,0, canvas.width, canvas.height)
context.beginPath();
context.moveTo(moveX,moveY)
context.lineTo(50,200);
context.stroke();
// context.closePath();
context.beginPath();
context.moveTo(50, 200);
context.lineTo(200, 200)
context.stroke();
// context.closePath();
window.requestAnimationFrame(animate)
}())
<canvas id="canvas" width=300 height=300></canvas>
Sin and Cos
All graphics programmers should know these two trigonometric functions thoroughly.
MarkE has given a good answer but I will point out the simple trig method that you can use to find a point that is a desired distance and angle.
Lets say the line you want has a length,
var length = 100;
its starts at,
var posX = 200;
var posY = 200;
the unknown end location will be,
var endX;
var endY;
and the angle you want is
var angle = 0; // in radians
Radians V Degrees
In javascript all Math functions that require angles use angle as radians.
The above angle is in radians, with angle = 0 pointing from left to right across the screen, angle = Math.PI/2 (90deg) from top down the screen, angle = Math.PI (180deg) from right to left across the screen and angle = Math.PI * (3/2) (270deg) from bottom to top up the screen.
If you like to work in degrees then you can convert to radians by multiplying degrees by Math.PI/180.
Function to convert degrees to radians
function degree2Radians(deg){
return deg * (Math.PI/180);
}
Back to getting the line at an angle. We will use the trig functions Math.cos and Math.sin see Wiki trigonometric functions for details.
So the calculation in steps
var x,y; // temp working variables
x = Math.cos(angle); // get the amount of x in the line for angle
y = Math.sin(angle); // get the amount of y in the line for angle
// cos and sin will return values -1 to 1 inclusive.
// now scale the values to the length of the line
x *= length;
y *= length;
// now you have the offset from the start of the line
// to get the end point add the start
endX = x + posX;
endY = y + posY;
Now you can draw the line at the angle you wanted.
ctx.beginPath();
ctx.moveTo(posX,posY);
ctx.lineTo(endX,endY);
ctx.stroke();
Of course that was a long way. It all can be done in two steps.
endX = Math.cos(angle) * length + posX;
endY = Math.sin(angle) * length + posY;
or if you like a function that works in degrees
// add to the current path a line from
// startX,startY of
// length pixels long
// at the angle angleDeg in degrees. Negative anticlockwise positive clockwise
function lineAtAngle(ctx, startX, startY, angleDeg, length){
var angle = angleDeg * (Math.PI / 180);
ctx.moveTo(startX, startY);
ctx.lineTo(
Math.cos(angle) * length + startX,
Math.sin(angle) * length + startY
)
}
And to use it
ctx.beginPath(); // begin the line
lineAtAngle(ctx,200,200,-45,100); // draw a line at -45deg
// (from bottom left to top right)
// 100 pixels long.
ctx.stroke(); // draw the line
Thats how to use sin and cos to draw a line at an angle and length.
I wrote a JavaScript that allows a user to draw with their mouse on an HTML5 canvas (similar to MS Paint).
Right now, I have 2 problems:
The drawing feature only works if the HTML5 canvas element is positioned at the top left corner (0, 0) of the web page, otherwise it doesn't work at all OR the drawing is off center.
I'm unable to erase the drawing. When I erase the drawing it erases BUT as soon as I start drawing again, it comes back.
My code is below:
HTML Canvas
<canvas id="can1" width="500" height="500"></canvas>1
JavaScript for Canvas Drawing
// Variables
var x1;
var y1;
var isPressed = false;
var myCanvas;
var myContext;
function startCanvas() {
// Canvas stuff
myCanvas = document.getElementById("can1");
myContext = myCanvas.getContext("2d");
// Specify a black background, and white lines that are 3 pixels thick.
myContext.fillStyle = '#fff';
myContext.strokeStyle = '#fff';
myContext.fillRect(0, 0, 500, 500);
myContext.lineWidth = 3;
myContext.fill();
}
function functionMouseDown(e) {
// Get coordinates
x1 = e.clientX
y1 = e.clientY;
isPressed = true;
}
function functionMouseMove(e) {
// If mouse is down and moved start drawing line
if (isPressed == true) {
drawLine(e);
}
}
function functionMouseUp() {
// Stop drawing line
isPressed = false;
//myContext.closePath();
//myContext.stroke();
}
function drawLine(e) {
// Draw line
var x = e.clientX;
var y = e.clientY;
myContext.strokeStyle = '#cc0000';
myContext.lineWidth = 1;
myContext.moveTo(x1, y1);
myContext.lineTo(x, y);
myContext.stroke();
// Set start coordinates to current coordinates
x1 = x;
y1 = y;
}
JavaScript that I use to erase canvas:
myContext.clearRect(0, 0, 500, 500);
I use the following function to accomplish this
function relMouseCoords(event){/*needs fixing for general case*/
var totalOffsetX = 0
var totalOffsetY = 0
var canvasX = 0
var canvasY = 0
var currentElement = this
do{
totalOffsetX += currentElement.offsetLeft
totalOffsetY += currentElement.offsetTop
}
while(currentElement = currentElement.offsetParent)
canvasX = event.pageX - totalOffsetX
canvasY = event.pageY - totalOffsetY
return {x:canvasX, y:canvasY}
}
HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
then
var cord = e.target.relMouseCoords(e);
x1 = cord.x;
y1 = cord.y;
...
var cord = e.target.relMouseCoords(e);
var x = cord.x;
var y =cord.y;
http://jsfiddle.net/mowglisanu/u3rvT/1/
The simplest solution is to set the off set of the canvas using myCanvas.offsetLeft and myCanvas.offsetTop.