setTimeout am I doing it wrong? for fireworks - javascript
So I am trying to get the clearInterval to work. This is from: https://codepen.io/whqet/pen/Auzch (I modified it a bit to make it more towards what I needed. Essentially, I want the animation to cease after 10000 ms. Excuse the messy coding, I threw in a timer at the bottom so I could see whether or not it would work.. Any assistance would be appreciated. Thanks!
var canvas = document.getElementById( 'canvas' ),
ctx = canvas.getContext( '2d' ),
// full screen dimensions
cw = window.innerWidth,
ch = window.innerHeight,
// firework collection
fireworks = [],
// particle collection
particles = [],
// starting hue
hue = 120,
// when launching fireworks with a click, too many get launched at once without a limiter, one launch per 5 loop ticks
limiterTotal = 5,
limiterTick = 0,
// this will time the auto launches of fireworks, one launch per 60 loop ticks
timerTotal = 60,
timerTick = 0,
mousedown = false,
// mouse x coordinate,
mx,
// mouse y coordinate
my;
// set canvas dimensions
canvas.width = cw;
canvas.height = ch;
// now we are going to setup our function placeholders for the entire demo
// get a random number within a range
function random( min, max ) {
return Math.random() * ( max - min ) + min;
}
// calculate the distance between two points
function calculateDistance( p1x, p1y, p2x, p2y ) {
var xDistance = p1x - p2x,
yDistance = p1y - p2y;
return Math.sqrt( Math.pow( xDistance, 2 ) + Math.pow( yDistance, 2 ) );
}
// create firework
function Firework( sx, sy, tx, ty ) {
// actual coordinates
this.x = sx;
this.y = sy;
// starting coordinates
this.sx = sx;
this.sy = sy;
// target coordinates
this.tx = tx;
this.ty = ty;
// distance from starting point to target
this.distanceToTarget = calculateDistance( sx, sy, tx, ty );
this.distanceTraveled = 0;
// track the past coordinates of each firework to create a trail effect, increase the coordinate count to create more prominent trails
this.coordinates = [];
this.coordinateCount = 3;
// populate initial coordinate collection with the current coordinates
while( this.coordinateCount-- ) {
this.coordinates.push( [ this.x, this.y ] );
}
this.angle = Math.atan2( ty - sy, tx - sx );
this.speed = 2;
this.acceleration = 1.05;
this.brightness = random( 50, 70 );
// circle target indicator radius
this.targetRadius = 1;
}
// update firework
Firework.prototype.update = function( index ) {
// remove last item in coordinates array
this.coordinates.pop();
// add current coordinates to the start of the array
this.coordinates.unshift( [ this.x, this.y ] );
// cycle the circle target indicator radius
if( this.targetRadius < 8 ) {
this.targetRadius += 0.3;
} else {
this.targetRadius = 1;
}
// speed up the firework
this.speed *= this.acceleration;
// get the current velocities based on angle and speed
var vx = Math.cos( this.angle ) * this.speed,
vy = Math.sin( this.angle ) * this.speed;
// how far will the firework have traveled with velocities applied?
this.distanceTraveled = calculateDistance( this.sx, this.sy, this.x + vx, this.y + vy );
// if the distance traveled, including velocities, is greater than the initial distance to the target, then the target has been reached
if( this.distanceTraveled >= this.distanceToTarget ) {
createParticles( this.tx, this.ty );
// remove the firework, use the index passed into the update function to determine which to remove
fireworks.splice( index, 1 );
} else {
// target not reached, keep traveling
this.x += vx;
this.y += vy;
}
}
// draw firework
Firework.prototype.draw = function() {
ctx.beginPath();
// move to the last tracked coordinate in the set, then draw a line to the current x and y
ctx.moveTo( this.coordinates[ this.coordinates.length - 1][ 0 ], this.coordinates[ this.coordinates.length - 1][ 1 ] );
ctx.lineTo( this.x, this.y );
ctx.strokeStyle = 'hsl(' + hue + ', 100%, ' + this.brightness + '%)';
ctx.stroke();
ctx.beginPath();
// draw the target for this firework with a pulsing circle
ctx.arc( this.tx, this.ty, this.targetRadius, 0, Math.PI * 2 );
ctx.stroke();
}
// create particle
function Particle( x, y ) {
this.x = x;
this.y = y;
// track the past coordinates of each particle to create a trail effect, increase the coordinate count to create more prominent trails
this.coordinates = [];
this.coordinateCount = 5;
while( this.coordinateCount-- ) {
this.coordinates.push( [ this.x, this.y ] );
}
// set a random angle in all possible directions, in radians
this.angle = random( 0, Math.PI * 2 );
this.speed = random( 1, 10 );
// friction will slow the particle down
this.friction = 0.95;
// gravity will be applied and pull the particle down
this.gravity = 1;
// set the hue to a random number +-50 of the overall hue variable
this.hue = random( hue - 50, hue + 50 );
this.brightness = random( 50, 80 );
this.alpha = 1;
// set how fast the particle fades out
this.decay = random( 0.015, 0.03 );
}
// update particle
Particle.prototype.update = function( index ) {
// remove last item in coordinates array
this.coordinates.pop();
// add current coordinates to the start of the array
this.coordinates.unshift( [ this.x, this.y ] );
// slow down the particle
this.speed *= this.friction;
// apply velocity
this.x += Math.cos( this.angle ) * this.speed;
this.y += Math.sin( this.angle ) * this.speed + this.gravity;
// fade out the particle
this.alpha -= this.decay;
// remove the particle once the alpha is low enough, based on the passed in index
if( this.alpha <= this.decay ) {
particles.splice( index, 1 );
}
}
// draw particle
Particle.prototype.draw = function() {
ctx. beginPath();
// move to the last tracked coordinates in the set, then draw a line to the current x and y
ctx.moveTo( this.coordinates[ this.coordinates.length - 1 ][ 0 ], this.coordinates[ this.coordinates.length - 1 ][ 1 ] );
ctx.lineTo( this.x, this.y );
ctx.strokeStyle = 'hsla(' + this.hue + ', 100%, ' + this.brightness + '%, ' + this.alpha + ')';
ctx.stroke();
}
// create particle group/explosion
function createParticles( x, y ) {
// increase the particle count for a bigger explosion, beware of the canvas performance hit with the increased particles though
var particleCount = 300;
while( particleCount-- ) {
particles.push( new Particle( x, y ) );
}
}
// main demo loop
function loop() {
// this function will run endlessly with requestAnimationFrame
requestAnimFrame( loop );
// increase the hue to get different colored fireworks over time
//hue += 0.5;
// create random color
hue= random(0, 360 );
// normally, clearRect() would be used to clear the canvas
// we want to create a trailing effect though
// setting the composite operation to destination-out will allow us to clear the canvas at a specific opacity, rather than wiping it entirely
ctx.globalCompositeOperation = 'destination-out';
// decrease the alpha property to create more prominent trails
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect( 0, 0, cw, ch );
// change the composite operation back to our main mode
// lighter creates bright highlight points as the fireworks and particles overlap each other
ctx.globalCompositeOperation = 'lighter';
// loop over each firework, draw it, update it
var i = fireworks.length;
while( i-- ) {
fireworks[ i ].draw();
fireworks[ i ].update( i );
}
// loop over each particle, draw it, update it
var i = particles.length;
while( i-- ) {
particles[ i ].draw();
particles[ i ].update( i );
}
// launch fireworks automatically to random coordinates, when the mouse isn't down
if( timerTick >= timerTotal ) {
if( !mousedown ) {
// start the firework at the bottom middle of the screen, then set the random target coordinates, the random y coordinates will be set within the range of the top half of the screen
fireworks.push( new Firework( cw / 2, ch, random( 0, cw ), random( 0, ch / 2 ) ) );
timerTick = 0;
}
} else {
timerTick++;
}
// limit the rate at which fireworks get launched when mouse is down
if( limiterTick >= limiterTotal ) {
if( mousedown ) {
// start the firework at the bottom middle of the screen, then set the current mouse coordinates as the target
fireworks.push( new Firework( cw / 2, ch, mx, my ) );
limiterTick = 0;
}
} else {
limiterTick++;
}
}
// mouse event bindings
// update the mouse coordinates on mousemove
canvas.addEventListener( 'mousemove', function( e ) {
mx = e.pageX - canvas.offsetLeft;
my = e.pageY - canvas.offsetTop;
});
// toggle mousedown state and prevent canvas from being selected
canvas.addEventListener( 'mousedown', function( e ) {
e.preventDefault();
mousedown = true;
});
canvas.addEventListener( 'mouseup', function( e ) {
e.preventDefault();
mousedown = false;
});
// once the window loads, we are ready for some fireworks!
window.onload = loop;
// when animating on canvas, it is best to use requestAnimationFrame instead of setTimeout or setInterval
window.requestAnimFrame = ( function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( ) {
window.setTimeout( timePeriodms );
};
function stopFireworks () {
var timePeriodms = 10000;
window.clearTimeout(timePeriodms);
};
})();
// now we will setup our basic variables for the demo
var minute = 0;
var sec = 00;
var zeroPholder = 0;
var counterIdv2 = setInterval(function(){
countUp2();
}, 1000);
function countUp2 () {
sec++;
if(sec == 60){
sec = 00;
minute = minute + 1;
}
if (minute == 0 && sec == 1) {
document.getElementById('count-up-2').style.color = 'red';
}
if (minute == 0 && sec == 59) {
document.getElementById('count-up-2').style.color = 'blue';
}
if (minute == 10 && sec == 00) {
document.getElementById('count-up-2').style.color = 'red';
}
if(sec == 10){
zeroPholder = '';
}else
if(sec == 00){
zeroPholder = 0;
}
document.getElementById("count-up-2").innerText = minute+':'+zeroPholder+sec;
}
body {
background: #000;
margin: 0;
}
canvas {
display: block;
}
<canvas id="canvas">Canvas is not supported in your browser.</canvas>
<table border="1" style="border-color:black;">
<tbody>
<tr>
<td style="background-color: #fff; padding: 5px;"><span>Time spent on page: </span><span id="count-up-2">0:00</span>
</td>
</tr>
</tbody>
</table>
requestAnimationFrame callback's argument
When requestAnimationFrame calls the callback function it supplies a high precision time in ms. You can use this time to control the timing of your animations.
For example the following animation loop will stop 10 seconds after the first frame is called.
requestAnimationFrame(mainLoop);
var startTime;
const endTime = 10000; // in ms
function mainLoop(time) {
if (startTime === undefined) { startTime = time }
if (time - startTime > endTime) {
console.log("Animation end.");
return;
}
// draw animated content
requestAnimationFrame(mainLoop);
}
This gives you better timing control in animations than using setTimeout especially if you have more than one thing to control the timing of (see demo)
Demo
The demo uses the same method to count down the time and control the fireworks. A few seconds before the end the fireworks are held back and then a second before a group are fired to explode in time with zero (Well there about!!!)
// Code based loosely on OPs code in so far as it animates fireworks.
// There is no control of fireworks via mouse.
// Uses reusable buffers to avoid GC overhead in low end devices.
const ctx = canvas.getContext( '2d' );
const GRAVITY = 0.2; // per frame squared
var cw = canvas.width = innerWidth - 40;
var ch = canvas.height = 500;
const iH = innerHeight;
var startTime;
const DISPLAY_TIME = 10000; // in ms. Time to countdown
const SHELL_X = cw / 2; // Location that shells are fired from in px
const SHELL_Y = ch;
const SHELL_TIME = 100; // in frames
const MAX_SHELLS = 8;
const MAX_PARTICLES = 1000; // Approx max particles.
const SHELL_RANDOM_RATE = 0.01; // cof of shell random fire control
const SHELL_FIRE_CURVE = 3; // Highest power of fire control exponents
var randomFire = 0; // holds the odds of a random shell being fired
Math.TAU = Math.PI * 2;
Math.PI90 = Math.PI / 2;
Math.PI270 = Math.PI + Math.PI90;
Math.rand = (m, M) => Math.random() * (M - m) + m;
Math.distance = (x1, y1, x2, y2) => ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5;
requestAnimationFrame(mainLoop);
function mainLoop(time) {
if (startTime === undefined) { startTime = time }
const timer = DISPLAY_TIME - (time - startTime);
const displayTime = timer / 1000 | 0;
if (timer <= 0) {
countdown.textContent = "HAPPY NEW YEAR 2020";
if(particles.size === 0 && shells.size === 0) {
ctx.clearRect(0,0,cw,ch);
shells.clear();
startTime = undefined;
countdown.textContent = "Click to restart";
canvas.addEventListener("click",() => requestAnimationFrame(mainLoop), {once: true});
return; // ends update
} else {
randomFire = 0; // pervent shells firing after zero time
}
} else {
countdown.textContent !== displayTime && (countdown.textContent = displayTime);
}
ctx.lineCap = "round";
ctx.globalCompositeOperation = 'destination-out';
ctx.globalAlpha = 0.2;
ctx.fillStyle = "#000"
ctx.fillRect( 0, 0, cw, ch );
ctx.globalCompositeOperation = 'lighter';
shells.update();
particles.update();
ctx.lineWidth = 2;
shells.draw();
ctx.lineWidth = 3;
particles.draw();
if (timer < 2500 && timer > 1000) { randomFire = 0 }
else if(timer <= 1000 && timer > 0) { randomFire = 1 }
if(shells.size < MAX_SHELLS && particles.size < MAX_PARTICLES) {
if(Math.random() < randomFire ** SHELL_FIRE_CURVE) {
randomFire = 0;
shells.fire(
Math.rand(cw * (1/3), cw *(2/3)),
Math.rand(iH * (3/4), iH *(4/4)),
SHELL_TIME
);
}
randomFire += SHELL_RANDOM_RATE;
}
requestAnimationFrame(mainLoop);
}
function Trail() {}
function Particle() { }
function Shell( sx, sy, tx, ty ) {
this.trail = new Trail();
this.init(sx, sy,tx,sy);
}
Trail.prototype = {
init(x, y) {
this.x1 = this.x2 = this.x3 = x;
this.y1 = this.y2 = this.y3 = y;
},
update(x, y) {
this.x3 = this.x2
this.y3 = this.y2
this.x2 = this.x1
this.y2 = this.y1
this.x1 = x;
this.y1 = y;
},
draw() {
ctx.moveTo(this.x1, this.y1);
ctx.lineTo(this.x2, this.y2);
ctx.lineTo(this.x3, this.y3);
}
};
Shell.prototype = {
init(x, y, time) {
this.x = SHELL_X;
this.y = SHELL_Y;
this.sx = (x - this.x) / (time / 2);
this.sy = ((y - this.y) * (GRAVITY / ((time) ** 0.5)))* 2;
this.power = (-this.sy * 10) | 0;
this.hue = Math.rand(360, 720) % 360 | 0;
this.active = true;
this.trail.init(this.x, this.y);
this.time = time / 2;
this.life = time / 2;
},
explode() {
this.active = false;
particles.explode(this, this.power);
},
update() {
this.time -= 1;
if (this.time <= 0) { this.explode() }
this.sy += GRAVITY;
this.x += this.sx;
this.y += this.sy;
this.trail.update(this.x, this.y);
return this.active;
},
draw() {
ctx.strokeStyle = `hsl(${this.hue},100%,${(this.time / this.life) * 100}%)`;
ctx.beginPath();
this.trail.draw();
ctx.stroke();
},
};
Particle.prototype = {
init(shell) {
this.x2 = this.x1 = this.x = shell.x;
this.y2 = this.y1 = this.y = shell.y;
this.dx = shell.sx;
this.dy = shell.sy;
this.angle = Math.rand(0, Math.TAU);
const zAng = Math.cos(Math.random() ** 2 * Math.PI)
this.speed = zAng * shell.power / 30;
this.friction = 0.95;
this.gravity = GRAVITY;
this.hue = (Math.rand(shell.hue - 5, shell.hue + 5) + 360) % 360;
this.brightness = Math.rand( 25, 50 );
this.alpha = shell.power / 10;
this.decay = Math.rand( 0.2, 0.5);
this.active = true;
},
update() {
const dx = Math.cos(this.angle);
const dy = Math.sin(this.angle);
this.x2 = this.x1;
this.y2 = this.y1;
this.x1 = this.x - dx;
this.y1 = this.y + dy;
this.speed *= this.friction;
this.x += (this.dx *= 0.9);
this.y += (this.dy *= 0.9);
this.dy += GRAVITY / 100;
this.x += dx * this.speed;
this.y += dy * this.speed;
this.alpha -= this.decay;
if( this.alpha <= 0 || this.x < 0 || this.y < 0 || this.x > cw) {
this.active = false;
}
return this.active;
},
draw() {
const alpha = this.alpha / 5 > 1 ? 1 : this.alpha / 5;
const lum = this.brightness + this.alpha
ctx.strokeStyle = `hsla(${this.hue},100%,${lum<100 ? lum : 100}%,${alpha})`;
ctx. beginPath();
ctx.moveTo( this.x2, this.y2);
ctx.lineTo( this.x, this.y );
ctx.stroke();
}
};
function BubbleArray(extension) {
return Object.assign([], {
size: 0,
update() {
var read = 0, write = 0;
while (read < this.size) {
const item = this[read];
if(read !== write) {
const temp = this[write]
this[write] = item;
this[read] = temp;
}
item.update() === true && (write ++);
read++;
}
this.size = write;
},
draw() {
var i = 0,len = this.size;
while(i < len) { this[i++].draw() }
},
add(item) {
this.size ++;
this.push(item);
},
clear() { this.length = this.size = 0 },
getInactive() { return this.size < this.length ? this[this.size++] : undefined },
},
extension,
);
}
const particles = BubbleArray({
explode(shell, count) {
var item;
while(count-- > 0) {
!(item = this.getInactive()) && this.add(item = new Particle());
item.init(shell);
}
},
});
const shells = BubbleArray({
fire(tx = mx, ty = my) {
var item;
!(item = this.getInactive()) && this.add(item = new Shell());
item.init(tx, ty, 100);
}
});
body {
padding: 0px;
}
canvas {
background: #000;
position: absolute;
top: 0px;
left: 0px;
}
#countdown {
position: absolute;
top: 20px;
left: 20px;
font-family: arial;
font-size: xx-large;
color: white;
}
<canvas id="canvas"></canvas>
<div id="countdown"></div>
Related
How to move object to target naturally and smoothly?
Can somebody fix it script to make it works properly? What I expects: Run script Click at the canvas to set target (circle) Object (triangle) starts to rotate and move towards to target (circle) Change target at any time How it works: Sometimes object rotates correctly, sometimes isn't Looks like one half sphere works well, another isn't Thanks! // prepare 2d context const c = window.document.body.appendChild(window.document.createElement('canvas')) .getContext('2d'); c.canvas.addEventListener('click', e => tgt = { x: e.offsetX, y: e.offsetY }); rate = 75 // updates delay w = c.canvas.width; h = c.canvas.height; pi2 = Math.PI * 2; // object that moves towards the target obj = { x: 20, y: 20, a: 0, // angle }; // target tgt = undefined; // main loop setInterval(() => { c.fillStyle = 'black'; c.fillRect(0, 0, w, h); // update object state if (tgt) { // draw target c.beginPath(); c.arc(tgt.x, tgt.y, 2, 0, pi2); c.closePath(); c.strokeStyle = 'red'; c.stroke(); // update object position // vector from obj to tgt dx = tgt.x - obj.x; dy = tgt.y - obj.y; // normalize l = Math.sqrt(dx*dx + dy*dy); dnx = (dx / l);// * 0.2; dny = (dy / l);// * 0.2; // update object position obj.x += dnx; obj.y += dny; // angle between +x and tgt a = Math.atan2(0 * dx - 1 * dy, 1 * dx + 0 * dy); // update object angle obj.a += -a * 0.04; } // draw object c.translate(obj.x, obj.y); c.rotate(obj.a); c.beginPath(); c.moveTo(5, 0); c.lineTo(-5, 4); c.lineTo(-5, -4); //c.lineTo(3, 0); c.closePath(); c.strokeStyle = 'red'; c.stroke(); c.rotate(-obj.a); c.translate(-obj.x, -obj.y); }, rate);
This turned out to be a bit more challenging than I first thought and I ended up just re-writing the code. The challenges: Ensure the ship only rotated to the exact point of target. This required me to compare the two angle from the ship current position to where we want it to go. Ensure the target did not rotate past the target and the ship did not translate past the target. This required some buffer space for each because when animating having this.x === this.x when an object is moving is very rare to happen so we need some room for the logic to work. Ensure the ship turned in the shortest direction to the target. I have added notes in the code to better explain. Hopefully you can implement this into yours or use it as is. Oh and you can change the movement speed and rotation speed as you see fit. let canvas = document.getElementById("canvas"); let ctx = canvas.getContext("2d"); canvas.width = 400; canvas.height = 400; let mouse = { x: 20, y: 20 }; let canvasBounds = canvas.getBoundingClientRect(); let target; canvas.addEventListener("mousedown", (e) => { mouse.x = e.x - canvasBounds.x; mouse.y = e.y - canvasBounds.y; target = new Target(); }); class Ship { constructor() { this.x = 20; this.y = 20; this.ptA = { x: 15, y: 0 }; this.ptB = { x: -15, y: 10 }; this.ptC = { x: -15, y: -10 }; this.color = "red"; this.angle1 = 0; this.angle2 = 0; this.dir = 1; } draw() { ctx.save(); //use translate to move the ship ctx.translate(this.x, this.y); //angle1 is the angle from the ship to the target point //angle2 is the ships current rotation angle. Once they equal each other then the rotation stops. When you click somewhere else they are no longer equal and the ship will rotate again. if (!this.direction(this.angle1, this.angle2)) { //see direction() method for more info on this if (this.dir == 1) { this.angle2 += 0.05; //change rotation speed here } else if (this.dir == 0) { this.angle2 -= 0.05; //change rotation speed here } } else { this.angle2 = this.angle1; } ctx.rotate(this.angle2); ctx.beginPath(); ctx.strokeStyle = this.color; ctx.moveTo(this.ptA.x, this.ptA.y); ctx.lineTo(this.ptB.x, this.ptB.y); ctx.lineTo(this.ptC.x, this.ptC.y); ctx.closePath(); ctx.stroke(); ctx.restore(); } driveToTarget() { //get angle to mouse click this.angle1 = Math.atan2(mouse.y - this.y, mouse.x - this.x); //normalize vector let vecX = mouse.x - this.x; let vecY = mouse.y - this.y; let dist = Math.hypot(vecX, vecY); vecX /= dist; vecY /= dist; //Prevent continuous x and y increment by checking if either vec == 0 if (vecX != 0 || vecY != 0) { //then also give the ship a little buffer incase it passes the given point it doesn't turn back around. This allows time for it to stop if you increase the speed. if ( this.x >= mouse.x + 3 || this.x <= mouse.x - 3 || this.y >= mouse.y + 3 || this.y <= mouse.y - 3 ) { this.x += vecX; //multiple VecX by n to increase speed (vecX*2) this.y += vecY; //multiple VecY by n to increase speed (vecY*2) } } } direction(ang1, ang2) { //converts rads to degrees and ensures we get numbers from 0-359 let a1 = ang1 * (180 / Math.PI); if (a1 < 0) { a1 += 360; } let a2 = ang2 * (180 / Math.PI); if (a2 < 0) { a2 += 360; } //checks whether the target is on the right or left side of the ship. //We use then to ensure it turns in the shortest direction if ((360 + a1 - a2) % 360 > 180) { this.dir = 0; } else { this.dir = 1; } //Because of animation timeframes there is a chance the ship could turn past the target if rotating too fast. This gives the ship a 1 degree buffer to either side of the target to determine if it is pointed in the right direction. //We then correct it to the exact degrees in the draw() method above once the if statment defaults to 'else' if ( Math.trunc(a2) <= Math.trunc(a1) + 1 && Math.trunc(a2) >= Math.trunc(a1) - 1 ) { return true; } return false; } } let ship = new Ship(); class Target { constructor() { this.x = mouse.x; this.y = mouse.y; this.r = 3; this.color = "red"; } draw() { ctx.strokeStyle = this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, false); ctx.stroke(); } } function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillRect(0, 0, canvas.width, canvas.height); ship.draw(); ship.driveToTarget(); if (target) { target.draw(); } requestAnimationFrame(animate); } animate(); <canvas id="canvas"></canvas>
Space-Ship movement - simulation
I have written some code to simulate a gravitation-free movement of a ship with a single thruster. Most of the time it works, and the ship reaches it's destination perfectly, but just sometimes it accelerates infinitively. But I can't figure out, why? seek(target) { var desired = p5.Vector.sub(target, this.position); // A vector pointing from the location to the target if (desired.mag()>0.1){ this.orientation = desired; if (this.velocity.heading() - desired.heading() > 0.01 && this.velocity.mag() >0.01) { this.orientation = this.velocity.copy().mult(-1); } if ((this.velocity.mag()*this.velocity.mag())/(2*(this.maxForce/this.mass)) > desired.mag()) { this.orientation.mult(-1); } this.applyForce(this.orientation.normalize().mult(this.maxForce/this.mass)); } else { this.velocity = createVector(0,0); } } You can test the result here: https://editor.p5js.org/Ahiru/sketches/r1rQ9-T5m
The issue of the ship object going past the target is caused by the magnitude delta being too small for the increment that the ship moves in. In order to get the spaceship object to land on the selected point you need to modify the seek method: seek(target) { var desired = p5.Vector.sub(target, this.position); // A vector pointing from the location to the target if (desired.mag()>.01){ The object is moving in increments that can cause desired.mag to go from a number that is greater than .01 as the object approaches to another magnitude that is larger than .01 as the object passes the target and moves away. modify if (desired.mag() > .01) to if (desired.mag() > 2.0) for example and the ship will be captured and land on the target and stay there until another target is selected. Here is a working example with the delta set to equal the diameter of the target so that the ship appears to land on the surface of the target. let v; var targetDiameter = 12; function setup() { pixelDensity(1); createCanvas(1000, 1000); v = new Vehicle(width / 2, height / 2); target = createVector(200, 200); } function draw() { background(51); // Draw an ellipse at the mouse position fill(127); stroke(200); strokeWeight(2); ellipse(target.x, target.y, targetDiameter, targetDiameter); // Call the appropriate steering behaviors for our agents v.seek(target); v.update(); v.display(); } function mouseClicked() { target = createVector(mouseX, mouseY); hex = find_HexCoordinates(100, target); console.log("click " + hex.x + " " + hex.y + " " + hex.z); } class ThrustList { constructor(thrust, time) { this.thrust = createVector(thrust); this.time = time; } set(thrust, time) { this.thrust.set(thrust); this.time = time; } } class ThrustParams { constructor (deltaPosition, deltaVelocity, maxForce, mass) { this.deltaPosition = createVector(deltaPosition); this.deltaVelocity = createVector(deltaVelocity); this.maxForce = maxForce; this.mass = mass; } } class hexmetrics { constructor (radius) { this.outerRadius = radius; this.innerRadius = this.outerradius * sqrt(3)/2; } } function find_HexCoordinates (radius, position) { this.innerRadius = radius; this.outerRadius = this.innerRadius * sqrt(3)/2; this.px = position.x - 1000/2; this.py = position.y - 1000/2; this.x = px / this.innerRadius * 2; this.y = -x; this.offset = py / this.outerRadius * 3; this.x -= offset; this.y -= offset; this.iX = Math.round(x); this.iY = Math.round(y); this.iZ = Math.round(-x -y); if (iX + iY + iZ != 0) { dX = Math.abs(x-iX); dY = Math.abs(y-iY); dZ = Math.abs(-x -y -iZ); if (dX > dY && dX > dZ) { iX = -iY -iZ; } else if (dZ > dY) { iZ = -iX -iY; } } return createVector(this.iX, this.iY, this.iZ); } class Vehicle { constructor(x, y){ this.mass = 1; this.orientation = createVector(0,1); this.turning_speed = 90; this.acceleration = createVector(0, 0); this.maxForce = .02; this.position = createVector(x, y); this.r = 3; this.velocity = createVector(0, 0); } // Method to update location update() { // Update velocity this.velocity.add(this.acceleration); // Limit speed this.position.add(this.velocity); // Reset accelerationelertion to 0 each cycle this.acceleration.mult(0); } applyForce(force) { this.acceleration.add(force); } // A method that calculates a steering force towards a target // STEER = DESIRED MINUS VELOCITY seek(target) { var desired = p5.Vector.sub(target, this.position); // A vector pointing from the location to the target if (desired.mag() > targetDiameter){ this.orientation = desired; if (Math.abs(this.velocity.heading() - desired.heading()) > 0.01 && this.velocity.mag() >0.01) { this.orientation = this.velocity.copy().mult(-1); } if ((this.velocity.mag()*this.velocity.mag())/(2*(this.maxForce/this.mass)) > desired.mag()) { this.orientation.mult(-1); } this.applyForce(this.orientation.normalize().mult(this.maxForce/this.mass)); } else { this.velocity = createVector(0,0); } } display() { // Draw a triangle rotated in the direction of velocity var theta = this.orientation.heading() + PI / 2; fill(127); stroke(200); strokeWeight(1); push(); translate(this.position.x, this.position.y); rotate(theta); beginShape(); vertex(0, -this.r * 2); vertex(-this.r, this.r * 2); vertex(this.r, this.r * 2); endShape(CLOSE); pop(); } } <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
I'm never using p5js before but I think it's because of the applyForce() , it's keep adding new force when user click. applyForce(force) { this.acceleration.add(force);}
Follow image to mouse with rotation on Javascript
How make Santa follow to mouse with rotation and own speed, not mouse speed? Where mouse now - it's destination, where go santa with own speed. Santa rotate with speed. How can I do this? Demo code game.js var canvas, ctx, player function init() { canvas = document.getElementById("canvas") ctx = canvas.getContext( "2d" ) resizeCanvas() player = new Player( ctx, canvas.width / 2, canvas.height / 2 + 100 ) window.onresize = resizeCanvas canvas.onmousemove = mousemove } function mousemove( e ) { player.x = e.clientX * devicePixelRatio player.y = e.clientY * devicePixelRatio } function render() { ctx.clearRect( 0, 0, canvas.width, canvas.height ) player.draw() } function step() { render() requestAnimationFrame( step ) } init() step()
It's not trivial task with rotation. Modified version with some magic: demo For distance used Pythagorean theorem. Player moving to forward (by current rotation). class Player { constructor(ctx, x, y) { this.x = x this.y = y this.dest = { x: 0, y: 0 } this.width = 200 this.height = 200 this.velocity = 12 this.angularVelocity = 7 this.rotation = 0 this.ctx = ctx this.image = new Image() this.image.src = "//habrastorage.org/files/447/9b4/6d3/4479b46d397e439a9613ce122a66a506.png" } draw() { this.ctx.translate(this.x, this.y) this.ctx.rotate(this.rotation + 4.7) this.ctx.drawImage( this.image, -this.width / 2, -this.height / 2, this.width, this.height ) this.ctx.rotate(-this.rotation - 4.7) this.ctx.translate(-this.x, -this.y) } distance(target) { let data = { x: target.x - this.x, y: target.y - this.y } data.len = Math.sqrt(data.x * data.x + data.y * data.y) return data } rotate(dt) { let path = this.distance(this.dest) let target = Math.atan2(path.y, path.x) let delta = this.rotation - target if (delta > 0.1 || delta < -0.1) { var _delta = delta if (_delta < 0) { _delta += Math.PI * 2 } if (delta < -Math.PI || (delta > 0 && delta < Math.PI)) { this.rotation -= _delta / this.angularVelocity } else { this.rotation -= (_delta - Math.PI * 2) / this.angularVelocity } // Reduce character rotation into the -PI thru PI range this.rotation = (this.rotation + 3 * Math.PI) % (2 * Math.PI) - Math.PI } } step() { let distance = this.distance(this.dest).len if (distance < this.width / 1.5) { this.draw() return } let vel = distance / 15 if (vel > this.velocity) { vel = this.velocity } this.rotate() this.x += vel * Math.cos(this.rotation) this.y += vel * Math.sin(this.rotation) this.draw() } }
HTML5 canvas draw a rectangle with gelatine effect
I'm creating a platformer game in javascript in which players are rectangles and they can jump on and off platforms. This is pretty straightforward but now I'm trying to add a 'gelatine effect' to the players so that when they land on a platform they move a bit(like a gelatine). I've been searching for quite some time now but I can't seem to find any good examples on how I can change the shape of a rectangle. All I've come up with is by using #keyframes in css and i've tried implementing it, which works if I use it on html elements. This is because with the DOM elements I can access the CSSStyleDeclaration with .style but if I create a new player object I can't(as seen in my code below). I'm not sure how I can convert the css #keyframes to javascript or if what i'm doing isn't possible.. or if there perhaps are other (better) ways to achieve my goal? var keystate = []; var players = []; var jelly = document.getElementById('jelly'); console.log(jelly.style); // shows the CSSStyleDeclarations document.body.addEventListener("keydown", function(e) { keystate[e.keyCode] = true; }); document.body.addEventListener("keyup", function(e) { keystate[e.keyCode] = false; }); function gelatine(e) { if (e.style.webkitAnimationName !== 'gelatine') { e.style.webkitAnimationName = 'gelatine'; e.style.webkitAnimationDuration = '0.5s'; e.style.display = 'inline-block'; setTimeout(function() { e.style.webkitAnimationName = ''; }, 1000); } } var canvas = document.getElementById("canvas"); var context = canvas.getContext('2d'); canvas.width = 500; canvas.height = 300; function Player(x, y, width, height) { this.x = x; this.y = y; this.width = width; this.height = height; this.jumping = false; this.velocityY = 0; this.gravity = 0.3; this.speed = 5; this.timer = 0; this.delay = 120; } Player.prototype.render = function render() { context.clearRect(0, 0, canvas.width, canvas.height); context.fillStyle = 'blue'; context.fillRect(this.x, this.y, this.width, this.height); context.fillStyle = 'black'; context.font = '20pt sans-serif'; context.fillText("I'm not jelly:(", this.x - 160, this.y + 30); }; Player.prototype.update = function update() { // arrow up key to jump with the player if (keystate[38]) { if (!this.jumping) { this.jumping = true; this.velocityY = -this.speed * 2; } } this.velocityY += this.gravity; this.y += this.velocityY; if (this.y >= canvas.height - this.height) { this.y = canvas.height - this.height; this.jumping = false; } if (this.timer === 0) { gelatine(jelly); this.timer = this.delay; } if (this.timer > 0 && this.timer <= this.delay) { this.timer--; } }; players.push(new Player((canvas.width / 2) - 25, (canvas.height / 2), 50, 50)); console.log(players[0].style); // no CSSStyleDeclarations :( function render() { for (var i = 0; i < players.length; i++) { players[i].render(); } } function update() { for (var i = 0; i < players.length; i++) { players[i].update(); } } function tick() { update(); render(); requestAnimationFrame(tick); } tick(); <html> <head> <style> #jelly { position: absolute; margin-left: auto; margin-right: auto; top: 80px; bottom: 0; left: 0; right: 0; display: inline-block; width: 100px; height: 100px; background: blue; } p { font-size: 20px; color: white; } canvas { border: 1px solid #000; position: absolute; margin: auto; top: 20px; bottom: 0; left: 0; right: 0; } #keyframes gelatine { 25% { -webkit-transform: scale(0.9, 1.1); transform: scale(0.9, 1.1); } 50% { -webkit-transform: scale(1.1, 0.9); transform: scale(1.1, 0.9); } 75% { -webkit-transform: scale(0.95, 1.05); transform: scale(0.95, 1.05); } } </style> <title>Jelly rectangle</title> </head> <body> <div id="jelly"> <p>   i'm jelly :)</p> </div> <canvas id='canvas'></canvas> </body> </html>
Jelly. To create a jelly effect for using in a game we can take advantage of a step based effect that is open ended (ie the effect length is dependent on the environment and has no fixed time) Jelly and most liquids have a property that they are incompressible. This means that no matter the force applied to them the volume does not change. For a 2D game this mean that for jelly the area does not change. This means we can squash the height and knowing the area calculate the width. Thus when a object drops and hits the ground we can apply a squashing force to the object in the height direction. Simulating a dampened spring we can produce a very realistic jelly effect. Defining the jelly I am being a little silly as it is the season so the variables wobbla and bouncy define the dampened spring with wobbla being the stiffness of the spring from 0 to 1, with 0.1 being very soft to 1 being very stiff. Bouncy being the damping of the spring from 0 to 1, with 0 having 100% damping and 1 having no damping. I have also added react which is a simple multiplier to the force applied to the spring. As this is a game there is a need to exaggerate effects. react multiplies the force applied to the spring. Values under 1 reduce the effect, values over 1 increase the effect. hr and hd (yes bad names) are the real height (displayed) and the height delta ( the change in height per frame). These two variable control the spring effect. There is a flag to indicate that the jelly is on the ground and the sticky flag if true keeps the jelly stuck to the ground. There are also three functions to control the jelly function createJellyBox(image,x, y, wobbla, bouncy, react){ return { img:image, x : x, // position y : y, dx : 0, // movement deltas dy : 0, w : image.width, // width h : image.height, // height area : image.width * image.height, // area hr : image.height, // squashed height chaser hd : 0, // squashed height delta wobbla : wobbla, // higher values make it wobble more bouncy : bouncy, // higher values make it react to change in speed react : react, // additional reaction multiplier. < 1 reduces reaction > 1 increases reaction onGround : false, // true if on the ground sticky : true, // true to stick to the ground. false to let it bounce squashed : 1, // the amount of squashing or stretching along the height force : 0, // the force applied to the jelly when it hits the ground draw : drawJelly, // draw function update : updateJelly, // update function reset : resetJelly, // reset function } } The functions There are three functions to control the jelly, reset, update and draw. Sorry the answer is over the 30000 character limit so find the functions in the demo below. Draw Draw simply draws the jelly. It uses the squashed value to calculate the height and from that calculates the width, then simply draws the image with the set width and height. The image is drawn at its center to keep calculations simpler. Reset Simply resets the jelly at a position defined by x and y you can modify it to remove any wobbles by setting jelly.hr = jelly.h and jelly.hd = 0 Update This is where the hard work is. It updates the object position by adding gravity to the delta Y. It checks if the jelly has hit the ground, if it has it applies the force to the jelly.hr spring by adding to the jelly.hd (height delta) the force equal to the speed of the inpact times jelly.react I found that the force applied should be over time so there is a little cludge (marked with comments) to apply a squashing force over time. That can be removed but it just reduces the smoothness of the jelly wobbly effect. Last thing is to recalculate the height and move the jelly so that it does not cross into the ground. Clear as mud cake I am sure. So feel free anyone to ask questions if there is a need to clarify. DEMO What would any good SO answer be without a demo. So here is a jelly simulation. Click on the canvas to drop the jelly. The three sliders on the top left control the values Wobbla, Bouncy, and React. Play witht the values to change the jelly effect. The check box turn on and off sticky, but you need a long screen to get the impact you need to bounce up. Remove the cludge code to see sticky real purpose. As I needed a UI there is a lot of extra code that does not apply to the answer. The Answer code is clearly marked so to be easy to find. The main loop is at the bottom. I have not yet tested it on FF but it should work. If not let me know and I will fix it. var STOP = false; // stops the app var jellyApp = function(){ /** Compiled by GROOVER Quick run 9:43pm DEC-22-2015 **/ /** fullScreenCanvas.js begin **/ var canvas = (function(){ var canvas = document.getElementById("canv"); if(canvas !== null){ document.body.removeChild(canvas); } // creates a blank image with 2d context canvas = document.createElement("canvas"); canvas.id = "canv"; canvas.width = window.innerWidth; canvas.height = window.innerHeight; canvas.style.position = "absolute"; canvas.style.top = "0px"; canvas.style.left = "0px"; canvas.style.zIndex = 1000; canvas.ctx = canvas.getContext("2d"); document.body.appendChild(canvas); return canvas; })(); var ctx = canvas.ctx; /** fullScreenCanvas.js end **/ /** MouseFull.js begin **/ var canvasMouseCallBack = undefined; // if needed var mouse = (function(){ var mouse = { x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, interfaceId : 0, buttonLastRaw : 0, buttonRaw : 0, over : false, // mouse is over the element bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits; getInterfaceId : function () { return this.interfaceId++; }, // For UI functions startMouse:undefined, }; function mouseMove(e) { var t = e.type, m = mouse; m.x = e.offsetX; m.y = e.offsetY; if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; } 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 (canvasMouseCallBack) { canvasMouseCallBack(m.x, m.y); } e.preventDefault(); } function startMouse(element){ if(element === undefined){ element = document; } "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",").forEach( function(n){element.addEventListener(n, mouseMove);}); } mouse.mouseStart = startMouse; return mouse; })(); if(typeof canvas === "undefined"){ mouse.mouseStart(canvas); }else{ mouse.mouseStart(); } /** MouseFull.js end **/ // unit size for rendering scale var ep = canvas.height / 72; // font constants const FONT = "px Arial Black"; const FONT_SIZE = Math.ceil(3 * ep); const GRAVITY = ep * (1/5); const GROUND_AT = canvas.height - canvas.height * (1/20); // Answer code. //----------------------------------------------------------------------- // draw the jelly function drawJelly(ctx){ var w,h; w = this.w, h = this.h; h *= this.squashed; // the width is ajusted so that the area of the rectagle remains constant w = this.area / h; // to keep the area constant ctx.drawImage(this.img,this.x - w / 2, this.y - h / 2, w, h); } function resetJelly(x,y){ // reset the jelly position this.x = x; this.y = y; this.onGround = false; this.dx = 0; this.dy = 0; } // do the jelly math function updateJelly(){ var h; // temp height var hitG = false; // flag that the ground has just been hit h = this.h * this.squashed; // get the height from squashed if(!this.onGround){ // if not on the ground add grav this.dy += GRAVITY; }else{ // if on the ground move it so it touches correctly this.dy = 0; this.y = GROUND_AT - h / 2; } // update the position this.x += this.dx; this.y += this.dy; // check if it has hit the ground if(this.y + h / 2 >= GROUND_AT && this.dy >= 0){ this.hd += this.dy * this.react; // add the hit speed to the height delta // multiply with react to inhance or reduce effect this.force += this.dy * this.react; hitG = true; this.onGround = true; // flag that jelly is on the ground } if(this.force > 0){ this.hd += this.force; this.force *= this.wobbla; } this.hd += (this.h - this.hr) * this.wobbla; // add wobbla to delta height this.hd *= this.bouncy; // reduce bounce this.hr += this.hd; // set the real height this.squashed = this.h / this.hr; // calculate the new squashed amount h = this.h * this.squashed; // recalculate hieght to make sure // the jelly does not overlap the ground // do the finnal position ajustment to avoid overlapping the ground if(this.y + h / 2 >= GROUND_AT || hitG || (this.sticky && this.onGround)){ this.y = GROUND_AT - h / 2; } } // create a jelly box function createJellyBox(image,x, y, wobbla, bouncy, react){ return { img:image, x : x, // position y : y, dx : 0, // movement deltas dy : 0, w : image.width, // width h : image.height, // height area : image.width * image.height, // area hr : image.height, // squashed height chaser hd : 0, // squashed height delta wobbla : wobbla, // higher values make it wobble more bouncy : bouncy, // higher values make it react to change in speed react : react, // additional reaction multiplier. < 1 reduces reaction > 1 increases reaction onGround : false, // true if on the ground sticky : true, // true to stick to the groun. false to let it bounce squashed : 1, // the amount of squashing or streaching along the height force : 0, draw : drawJelly, // draw function update : updateJelly, // update function reset : resetJelly, // reset function } } // -------------------------------------------------------------------------------------------- // END OF ANSWER CODE. // FIND the usage at the bottom inside the main loop function. // -------------------------------------------------------------------------------------------- // The following code is just helpers and UI stuff and are not related to the answer. var createImage = function (w, h ){ var image = document.createElement("canvas"); image.width = w; image.height = h; image.ctx = image.getContext("2d"); return image; } var drawSky = function (img, col1, col2){ var c, w, h; c = img.ctx; w = img.width; h = img.height; var g = c.createRadialGradient(w * (1 / 2), h * (3 / 2), h * (1 / 2) +w * (1 / 4), w * (1 / 2), h * (3 / 2), h * (3 / 2) + w * ( 1 / 4)); g.addColorStop(0,col1); g.addColorStop(1,col2); c.fillStyle = g; c.fillRect(0, 0, w, h); return img; } var drawGround = function (img, col1, col2,lineColour, lineWidth) { var c, w, h, lw; c = img.ctx; w = img.width; h = img.height; lw = lineWidth; var g = c.createLinearGradient(0, 0, 0, h + lineWidth); g.addColorStop(0, col1); g.addColorStop(1, col2); c.fillStyle = g; c.lineWidth = lw; c.strokeStyle = lineColour; c.strokeRect(-lw * 2, lw / 2, w + lw * 4, h + lw * 4); c.fillRect(-lw * 2, lw / 2, w + lw * 4, h + lw * 4); return img; } /** CanvasUI.js begin **/ var drawRoundedBox = function (img, colour, rounding, lineColour, lineWidth) { var c, x, y, w, h, r, p p = Math.PI/2; // 90 deg c = img.ctx; w = img.width; h = img.height; lw = lineWidth; r = rounding ; c.lineWidth = lineWidth; c.fillStyle = colour; c.strokeStyle = lineColour; c.beginPath(); c.arc(w - r - lw / 2, h - r - lw / 2, r, 0, p); c.lineTo(r + lw / 2, h - lw / 2); c.arc(r + lw / 2, h - r - lw / 2, r, p, p * 2); c.lineTo(lw / 2, h - r - lw / 2); c.arc(r + lw / 2, r + lw / 2, r, p * 2, p * 3); c.lineTo(w-r - lw / 2, lw / 2); c.arc(w - r - lw / 2, r + lw / 2, r, p * 3, p * 4); c.closePath(); c.stroke(); c.fill(); return img; } var drawTick = function (img , col, lineColour, lineWidth){ var c, w, h, lw, m, l; m = function (x, y) {c.moveTo(lw / 2 + w * x, lw / 2 + h * y);}; l = function (x, y) {c.lineTo(lw / 2 + w * x, lw / 2 + h * y);}; lw = lineWidth; c = img.ctx; w = img.width - lw; h = img.height - lw; c.fillStyle = col; c.strokeStyle = lineColour; c.lineWidth = lw; c.beginPath(); m(1, 0); l(5 / 8, 1); l(0, 3 / 4); l(1 / 4, 2 / 4); l(2 / 4, 3 / 4); l(1, 0); c.stroke(); c.fill(); return img; } var setFont = function(ctx,font,align){ ctx.font = font; ctx.textAlign = align; } var measureText = function(ctx,text){ return ctx.measureText(text).width; } var drawText = function(ctx,text,x,y,col,col1){ var of; of = Math.floor(FONT_SIZE/10); ctx.fillStyle = col1; ctx.fillText(text,x+of,y+of); ctx.fillStyle = col; ctx.fillText(text,x,y); } var drawSlider = function(ctx){ var x,y; x = this.owner.x; y = this.owner.y; ctx.drawImage(this.image, this.x + x, this.y + y); ctx.drawImage(this.nob, this.nx + x, this.ny + y); } var updateSlider = function(mouse){ var mx, my; mx = mouse.x - this.owner.x; my = mouse.y - this.owner.y; this.cursor = ""; if(this.owner.dragging === -1 || this.owner.dragging === this.id ){ if(mx >= this.x && mx < this.x + this.w && my >= this.y && my <= this.y + this.h){ this.mouseOver = true; this.cursor = "pointer" }else{ this.mouseOver = false; } if(mx >= this.nx && mx < this.nx + this.nw && my >= this.ny && my <= this.ny + this.nh){ this.mouseOverNob = true; this.cursor = "ew-resize" }else{ this.mouseOverNob = false; } if((mouse.buttonRaw&1) === 1 && (this.mouseOver||this.mouseOverNob) && !this.dragging){ this.owner.dragging = this.id; this.dragging = true; this.cursor = "ew-resize" }else if(this.dragging){ this.cursor = "ew-resize" if((mouse.buttonRaw & 1)=== 0){ this.dragging = false; this.owner.dragging = -1; this.cursor = "pointer"; } var p = mx- (this.x+this.nw/2); p /= (this.w-this.nw); p *= this.range; p += this.min; this.value = Math.min(this.max, Math.max(this.min, p)); } if(this.mouseOver || this.mouseOverNob || this.dragging){ this.owner.toolTip = this.toolTip.replace("##",this.value.toFixed(this.decimals)); } } this.nx = (this.value - this.min) / this.range * (this.w - this.nw) + this.x; } var createSlider = function(image,nobImage, x, y, value, min, max, toolTip) { var decimals = 0; if(toolTip.indexOf("#.") > -1){ if(toolTip.indexOf(".DDD") > -1){ decimals = 3; toolTip = toolTip.replace("#.DDD","##"); }else if(toolTip.indexOf(".DD") > -1){ decimals = 2; toolTip = toolTip.replace("#.DD","##"); }else if(toolTip.indexOf(".D") > -1){ decimals = 1; toolTip = toolTip.replace("#.D","##"); }else{ toolTip = toolTip.replace("#.","##"); } } return { id : undefined, image : image, nob : nobImage, min : min, max : max, x : x, y : y + (nobImage.height - image.height)/2, ny : y , nx : ((value - min) / (max - min)) * (image.width - nobImage.width) + x, range : max - min, w : image.width, h : image.height, nw : nobImage.width, nh : nobImage.height, value : value, maxH : Math.max( image.height, nobImage.height), mouseOver : false, mouseOverNob : false, toolTip:toolTip, decimals:decimals, dragging : false, update : updateSlider, draw : drawSlider, position : function (x, y){ this.x += x; this.y += y; this.nx += x; this.ny += y; }, } } var drawTickCont = function(ctx){ var x,y, ofx, ofy; x = this.owner.x; y = this.owner.y; ctx.drawImage(this.image, this.x + x, this.y + y); ofy = this.h / 2 - this.textImage.height / 2; ofx = this.w / 2; ctx.drawImage(this.textImage, this.x + x + this.w + ofx, this.y + y + ofy); if(this.value){ x -= this.tickImage.width * ( 1/ 4); y -= this.tickImage.height * ( 2/ 5); ctx.drawImage(this.tickImage, this.x + x, this.y + y); } } var updateTick = function(mouse){ var mx, my; mx = mouse.x - this.owner.x; my = mouse.y - this.owner.y; this.cursor = ""; if(this.owner.dragging === -1 || this.owner.dragging === this.id ){ if(mx >= this.x && mx < this.x + this.w && my >= this.y && my <= this.y + this.h){ this.mouseOver = true; this.cursor = "pointer" }else{ this.mouseOver = false; } if((mouse.buttonRaw&1) === 1 && this.mouseOver && !this.dragging){ this.owner.dragging = this.id; this.dragging = true; }else if(this.dragging){ if((mouse.buttonRaw & 1)=== 0){ if(this.mouseOver){ this.value = ! this.value; } this.dragging = false; this.owner.dragging = -1; this.cursor = "pointer"; } } if(this.mouseOver || this.dragging){ this.owner.toolTip = this.toolTip; } } } var createTick= function(image,tickImage,textImage, x, y, value, toolTip) { return { id : undefined, image : image, tickImage : tickImage, textImage : textImage, x : x, y : y, w : image.width, h : image.height, value : value, maxH : Math.max( image.height, tickImage.height), mouseOver : false, mouseOverNob : false, toolTip:toolTip, dragging : false, update : updateTick, draw : drawTickCont, position : function (x, y){ this.x += x; this.y += y; }, } } function UI(ctx, mouse, x, y){ this.dragging = -1; var ids = 0; var controls = []; var length = 0; this.x = x; this.y = y; var posX = 0; var posY = 0; this.addControl = function (control, name) { control.id = ids ++; control.owner = this; control.position(posX, posY); posY += control.maxH + ep; length = controls.push(control) this[name] = control; } this.update = function(){ var i, cursor, c; cursor = ""; this.toolTip = ""; for(i = 0; i < length; i ++){ c = controls[i]; c.update(mouse); if(c.cursor !== ""){ cursor = c.cursor; } c.draw(ctx); } if(cursor === ""){ ctx.canvas.style.cursor = "default"; }else{ ctx.canvas.style.cursor = cursor; } if(this.toolTip !== ""){ if(mouse.y - FONT_SIZE * (5 / 3) < 0){ drawText(ctx, this.toolTip, mouse.x, mouse.y + FONT_SIZE * (4 / 3), "#FD4", "#000"); }else{ drawText(ctx, this.toolTip, mouse.x, mouse.y - FONT_SIZE * (2 / 3), "#FD4", "#000"); } } } } /** CanvasUI.js end **/ //----------------------------- // create UI //----------------------------- setFont(ctx, FONT_SIZE + FONT, "left"); // images for UI var tickBox = drawRoundedBox(createImage(4 * ep, 4 * ep), "#666", (3 / 2) * ep, "#000", (1 / 2) * ep); var tickBoxTick = drawTick(createImage(6 * ep, 6 * ep), "#0D0", "#000", (1 / 2) * ep); var w = measureText(ctx, "Sticky"); var tickBoxText = createImage(w, FONT_SIZE); setFont(tickBoxText.ctx, FONT_SIZE + FONT, "left"); drawText(tickBoxText.ctx, "Sticky", 0, FONT_SIZE * (3 / 4), "white", "black"); var sliderBar = drawRoundedBox(createImage(20 * ep, 2 * ep), "#666", ep * 0.9, "#000", (1 / 2) * ep); var sliderNob = drawRoundedBox(createImage(3 * ep, 4 * ep), "#AAA", ep, "#000", (1 / 2) * ep); // UI control var controls = new UI(ctx, mouse, 10, 10); controls.addControl(createSlider(sliderBar, sliderNob, 0, 0, 0.3, 0, 1, "Wobbla #.DD"), "wobbla"); controls.addControl(createSlider(sliderBar, sliderNob, 0, 0, 0.8, 0, 1, "Bouncy #.DD"), "bouncy"); controls.addControl(createSlider(sliderBar, sliderNob, 0, 0, 1.5, 0, 2, "React #.DD"), "react"); controls.addControl(createTick(tickBox, tickBoxTick, tickBoxText, 0, 0, true, "Activate / Deactivate sticky option."), "sticky"); //----------------------------- // create playfield //----------------------------- var skyImage = drawSky(createImage(32, 32), "#9EF", "#48D"); var groundImage = drawGround(createImage(100, canvas.height - GROUND_AT), "#5F5", "#5A5", "#181", 4); //----------------------------- // create jelly //----------------------------- var jellyImage = drawRoundedBox(createImage(ep * 20, ep * 20), "#FA4", ep * 2, "black", ep * (3 / 5)); var jelly = createJellyBox( jellyImage, canvas.width / 2, 100, 0.1, 0.8, 1.2 ); // some more settings ctx.imageSmoothingEnabled = true; setFont(ctx, FONT_SIZE + FONT, "left"); //----------------------------- // Main animtion loop //----------------------------- function updateAnim(){ // draw the background and ground ctx.drawImage(skyImage, 0, 0, canvas.width, canvas.height) ctx.drawImage(groundImage, 0, GROUND_AT, canvas.width, canvas.height - GROUND_AT) // update and draw jelly jelly.update(); jelly.draw(ctx); // update and draw controls controls.update(); // update jelly setting from controls. jelly.wobbla = controls.wobbla.value; jelly.bouncy = controls.bouncy.value; jelly.react = controls.react.value; jelly.sticky = controls.sticky.value; // if the mouse is not busy then use left mouse click and drag to position jellu if(controls.dragging < 0 && mouse.buttonRaw === 1){ controls.dragging = -2; jelly.reset(mouse.x,mouse.y); }else if(controls.dragging === -2){ // release mouse controls.dragging = -1; } if(!STOP){ requestAnimationFrame(updateAnim); }else{ STOP = false; } } updateAnim(); }; function resizeEvent(){ var waitForStopped = function(){ if(!STOP){ // wait for stop to return to false jellyApp(); return; } setTimeout(waitForStopped,200); } STOP = true; setTimeout(waitForStopped,100); } window.addEventListener("resize",resizeEvent); jellyApp();
Getting mouse position within canvas
I am trying to modified this effect to work within my canvas. However, I can't seem to get the mouse position to work properly. The hover area isn't contained to my canvas. Here's a CSSdeck of how i tried to implement it: http://cssdeck.com/labs/ukktjtis Effect: function hoverText(){ window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })(); var canvas = document.getElementById("canvas"), ctx = canvas.getContext("2d"), keyword = "MacroPlay Games", imageData, density = 3, mouse = {}, hovered = false, colors = ["0,120,232", "8,200,255", "30,140,255"], minDist = 20, bounceFactor = 0.7; var W = window.innerWidth, H = window.innerHeight; canvas.width = W; canvas.height = H; document.addEventListener("mousemove", function(e) { mouse.x = e.pageX-50; mouse.y = e.pageY+200; }, false); // Particle Object var Particle = function() { this.w = Math.random() * 10.5; this.h = Math.random() * 10.5; this.x = -W; this.y = -H; this.free = false; this.vy = -5 + parseInt(Math.random() * 10) / 2; this.vx = -4 + parseInt(Math.random() * 8); // Color this.a = Math.random(); this.color = colors[parseInt(Math.random()*colors.length)]; this.setPosition = function(x, y) { this.x = x; this.y = y; }; this.draw = function() { ctx.fillStyle = "rgba("+this.color+","+this.a+")"; ctx.fillRect(this.x, this.y, this.w, this.h); } }; var particles = []; // Draw the text function drawText() { ctx.clearRect(0, 0, W, H); ctx.fillStyle = "#000000"; ctx.font = "100px 'Arial', sans-serif"; ctx.textAlign = "center"; ctx.fillText(keyword, W/2, H/2); } // Clear the canvas function clear() { ctx.clearRect(0, 0, W, H); } // Get pixel positions function positionParticles() { // Get the data imageData = ctx.getImageData(0, 0, W, W); data = imageData.data; // Iterate each row and column for (var i = 0; i < imageData.height; i += density) { for (var j = 0; j < imageData.width; j += density) { // Get the color of the pixel var color = data[((j * ( imageData.width * 4)) + (i * 4)) - 1]; // If the color is black, draw pixels if (color == 255) { particles.push(new Particle()); particles[particles.length - 1].setPosition(i, j); } } } } drawText(); positionParticles(); // Update function update() { clear(); for(i = 0; i < particles.length; i++) { var p = particles[i]; if(mouse.x > p.x && mouse.x < p.x + p.w && mouse.y > p.y && mouse.y < p.y + p.h) hovered = true; if(hovered == true) { var dist = Math.sqrt((p.x - mouse.x)*(p.x - mouse.x) + (p.y - mouse.y)*(p.y - mouse.y)); if(dist <= minDist) p.free = true; if(p.free == true) { p.y += p.vy; p.vy += 0.15; p.x += p.vx; // Collision Detection if(p.y + p.h > H) { p.y = H - p.h; p.vy *= -bounceFactor; // Friction applied when on the floor if(p.vx > 0) p.vx -= 0.1; else p.vx += 0.1; } if(p.x + p.w > W) { p.x = W - p.w; p.vx *= -bounceFactor; } if(p.x < 0) { p.x = 0; p.vx *= -0.5; } } } ctx.globalCompositeOperation = "lighter"; p.draw(); } } (function animloop(){ requestAnimFrame(animloop); update(); })(); }
It's highly advised you use jquery (or some js lib) to avoid cross-browser issues like getting the mouse position. You can easily get the mouse position in any browser using jquery like this: // get the position of the canvas relative to the web page var canvasOffset=$("#canvas").offset(); var offsetX=canvasOffset.left; var offsetY=canvasOffset.top; // then in the mouse handler, get the exact mouse position like this: function handleMouseDown(e){ mouseX=parseInt(e.clientX-offsetX); mouseY=parseInt(e.clientY-offsetY); // Put your mousedown stuff here } // tell the browser to send mousedown events to the handleMouseDown function $("#canvas").mousedown(function(e){handleMouseDown(e);});
I personally prefer a library like hammer.js. I've use it for all my projects - check it out, it's pretty good.