app.js
var svg = document.querySelector( 'svg' );
var checkbox = document.querySelector( 'input' );
var xSpeed = 0.5;
svg.addEventListener( 'click', event => {
const target = {
node: document.createElementNS( 'http://www.w3.org/2000/svg', 'circle' ),
x: event.clientX,
y: event.clientY
};
target.node.setAttribute( 'cx', target.x );
target.node.setAttribute( 'cy', target.y );
target.node.setAttribute( 'class', 'target' );
target.node.setAttribute( 'r', 10 );
svg.appendChild( target.node );
const start = {
x: Math.random() < 0.5 ? -50 : window.innerWidth + 50,
y: Math.random() * window.innerHeight / 2
};
const dx = target.x - start.x;
const dy = target.y - start.y;
const distance = Math.sqrt( dx * dx + dy * dy );
const duration = distance / xSpeed;
const peakOffset = -( Math.abs( dx ) / 4 );
const initialVelocity = 4 * peakOffset;
const gravity = -initialVelocity;
const attackStart = Date.now();
const bounceStart = attackStart + duration;
const circle = document.createElementNS( 'http://www.w3.org/2000/svg', 'circle' );
circle.setAttribute( 'r', '5' );
circle.setAttribute( 'fill', '#b51800' );
svg.appendChild( circle );
function attackPositionAt ( t ) {
const yOffset = ( initialVelocity * t ) + ( gravity * t * t );
const x = start.x + ( t * dx );
const y = start.y + ( t * dy ) + yOffset;
return { x, y };
}
function bouncePositionAt ( t ) {
return {
x: target.x + ( t * vx ),
y: target.y + ( t * vy ) + ( gravity * t * t ),
opacity: Math.pow( 1 - t, 2 )
};
}
function attack () {
const timeNow = Date.now();
const elapsed = timeNow - attackStart;
const t = elapsed / duration;
if ( t >= 1 ) {
vx = 0.7 * -dx;
vy = 0.7 * ( -4 * peakOffset );
target.node.style.fill = '#b51800';
bounce();
} else {
requestAnimationFrame( attack );
const pos = attackPositionAt( t );
circle.setAttribute( 'cx', pos.x );
circle.setAttribute( 'cy', pos.y );
}
}
function bounce () {
const timeNow = Date.now();
const elapsed = timeNow - bounceStart;
const pos = bouncePositionAt( elapsed / duration );
circle.setAttribute( 'cx', pos.x );
circle.setAttribute( 'cy', pos.y );
circle.style.opacity = pos.opacity;
if ( pos.y > window.innerHeight + 50 ) {
cleanup();
return;
}
requestAnimationFrame( bounce );
}
function cleanup () {
target.node.style.opacity = 0;
setTimeout( () => svg.removeChild( target.node ), 1000 );
if ( projection ) {
projection.style.opacity = 0;
setTimeout( () => svg.removeChild( projection ), 1000 );
}
}
if ( checkbox.checked ) {
var projection = document.createElementNS( 'http://www.w3.org/2000/svg', 'g' );
for ( let t = 0; t < 1; t += 0.01 ) {
const dot = document.createElementNS( 'http://www.w3.org/2000/svg', 'circle' );
const pos = attackPositionAt( t );
dot.setAttribute( 'cx', pos.x );
dot.setAttribute( 'cy', pos.y );
dot.setAttribute( 'r', 2 );
projection.appendChild( dot );
}
svg.appendChild( projection );
}
attack();
});