I am trying to make a sine wave on a canvas that would be filled with a certain color. I was successful with creating the wave animation. However, i can't seem to get the fill right. You can see the way my code works in the snippet below
const canvas = document.querySelector('canvas')
const gui = new dat.GUI()
const c = canvas.getContext('2d')
canvas.width = innerWidth
canvas.height = innerHeight
const wave = {
y: canvas.height / 2,
length: 0.01,
amplitude: 150,
frequency: 0.01
}
const strokeColor = {
h: 200,
s: 50,
l: 50
}
const backgroundColor = {
r: 255,
g: 50,
b: 255,
a: 1
}
const waveFolder = gui.addFolder('wave')
waveFolder.add(wave, 'y', 0, canvas.height)
waveFolder.add(wave, 'length', -0.01, 0.01)
waveFolder.add(wave, 'amplitude', -300, 300)
waveFolder.add(wave, 'frequency', -0.01, 1)
const strokeFolder = gui.addFolder('stroke')
strokeFolder.add(strokeColor, 'h', 0, 255)
strokeFolder.add(strokeColor, 's', 0, 100)
strokeFolder.add(strokeColor, 'l', 0, 100)
const backgroundFolder = gui.addFolder('background')
backgroundFolder.add(backgroundColor, 'r', 0, 255)
backgroundFolder.add(backgroundColor, 'g', 0, 255)
backgroundFolder.add(backgroundColor, 'b', 0, 255)
backgroundFolder.add(backgroundColor, 'a', 0, 1)
let increment = 0
function drawSineWave() {
c.fillStyle = `rgba(${backgroundColor.r},${backgroundColor.g},${backgroundColor.b},${backgroundColor.a})`
c.clearRect(0, 0, canvas.width, canvas.height)
increment++
c.beginPath()
c.moveTo(0, canvas.height / 2)
c.lineTo(0, canvas.height / 2)
c.lineTo(0, canvas.height)
c.lineTo(canvas.width, canvas.height)
c.lineTo(canvas.width, canvas.height / 2)
c.moveTo(0, canvas.height / 2)
for (let i = 0; i < canvas.width; i++) {
c.lineTo(i, wave.y + Math.sin(increment / 50) * wave.amplitude * Math.sin(i * wave.length + wave.frequency))
}
c.lineTo(canvas.width, canvas.height / 2)
c.strokeStyle = `hsl(${strokeColor.h},${strokeColor.s}%,${strokeColor.l}%)`
c.fill()
c.stroke()
}
function animate() {
requestAnimationFrame(animate)
drawSineWave()
}
animate()
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
canvas {
width: 100%;
height: 100%;
}
<head>
<title>Waves</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<canvas></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>
<script src="index.js"></script>
</body>
The way I want the wave to look is this:
Can someone please help me achieve the desired result? Thanks!
You just need to draw the bottom half in the correct order
The result is
function drawSineWave() {
c.fillStyle = `rgba(${backgroundColor.r},${backgroundColor.g},${backgroundColor.b},${backgroundColor.a})`
c.clearRect(0, 0, canvas.width, canvas.height)
increment++
c.beginPath()
for (let i = 0; i < canvas.width; i++) {
c.lineTo(i, wave.y + Math.sin(increment / 50) * wave.amplitude * Math.sin(i * wave.length + wave.frequency))
}
c.strokeStyle = `hsl(${strokeColor.h},${strokeColor.s}%,${strokeColor.l}%)`
c.stroke(); // draw stroke along wave top only
c.lineTo(canvas.width, canvas.height) // bottom right
c.lineTo(0, canvas.height) // bottom left
c.fill()
}
Example
I played about with it a bit.
const canvas = document.querySelector('canvas')
const gui = new dat.GUI()
const c = canvas.getContext('2d')
canvas.width = innerWidth
canvas.height = innerHeight
const wave = {
offset: canvas.height/2,
speed: 1,
amplitude:50,
frequency: 1,
}
var disFreq = wave.frequency, disFreqChase = 0;
var disAmp = wave.amplitude, disAmpChase = 0;
var disY = wave.offset, disYChase = 0;
var disSp = wave.speed, disSpChase = 0;
const strokeColor = {width: 2, h:200,s:50, l:50 }
const backgroundColor = { r:255, g:50, b:255, a:1 }
const waveFolder = gui.addFolder('wave')
waveFolder.add(wave,'speed',-1,1); // peeks per second
waveFolder.add(wave,'offset',0,canvas.height);
waveFolder.add(wave,'amplitude',-100,100)
waveFolder.add(wave,'frequency',0.5,10); // frequency also defines wave length
// Value is fraction of canvas width
const strokeFolder = gui.addFolder('stroke')
strokeFolder.add(strokeColor,'width',0,18)
strokeFolder.add(strokeColor,'h',0,255)
strokeFolder.add(strokeColor,'s',0,100)
strokeFolder.add(strokeColor,'l',0,100)
const backgroundFolder = gui.addFolder('background')
backgroundFolder.add(backgroundColor,'r',0,255)
backgroundFolder.add(backgroundColor,'g',0,255)
backgroundFolder.add(backgroundColor,'b',0,255)
backgroundFolder.add(backgroundColor,'a',0,1)
let increment = 0
function drawSineWave() {
// smooth out changes
disFreq += disFreqChase = (disFreqChase += (wave.frequency - disFreq)*0.2)* 0.2;
disAmp += disAmpChase = (disAmpChase += (wave.amplitude - disAmp) * 0.2) * 0.2;
disY += disYChase = (disYChase += (wave.offset - disY) * 0.2) * 0.2;
disSp += disSpChase = (disSpChase += (wave.speed - disSp) * 0.2) * 0.2;
c.lineWidth = strokeColor.width;
c.fillStyle = `rgba(${backgroundColor.r},${backgroundColor.g},${backgroundColor.b},${backgroundColor.a})`
c.clearRect(0,0,canvas.width,canvas.height)
//increment++
increment += (disSp / 60);
c.beginPath()
const h = canvas.height;
for (let i=0;i<canvas.width;i++){
const a = (disAmp / 200) * h;
const x = ((i / canvas.width) * disFreq) % 1 + increment;
const p = x * Math.PI * 2;
c.lineTo(i, disY + Math.sin(p)*a);
}
c.strokeStyle = `hsl(${strokeColor.h},${strokeColor.s}%,${strokeColor.l}%)`
c.stroke()
c.lineTo(canvas.width,canvas.height)
c.lineTo(0, canvas.height)
c.fill()
}
function animate(){
requestAnimationFrame(animate)
drawSineWave()
}
animate()
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
canvas{
width: 100%;
height: 100%;
}
<canvas></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>
<script src="index.js"></script>
</body>
Related
I am creating an animation using java script & canvas. I am using a fiddle as a reference, currently object are generating randomly & falling from top right corner to bottom left corner which is okay. but the issue is speed the objects are generating & falling in high speed. I want to make animation flow little slow & smooth.
I am new canvas programming, any help will be very useful.
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
var particleArr = [],
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
flakeCount = 700,
mouseX = -100,
mouseY = -100,
xMultiplier = 0.015
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function getRandomColor() {
// Random Color Generate
const colorArr = ["rgba(215,88,69, 1)", "rgba(117, 161, 199, 1)"]; // Blue & Orange Color
const randomColor = colorArr[Math.floor(Math.random() * colorArr.length)];
return randomColor;
}
function flow() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < flakeCount; i++) {
var flake = particleArr[i],
x = mouseX,
y = mouseY,
minDist = 150,
x2 = flake.x,
y2 = flake.y;
var dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)),
dx = x2 - x,
dy = y2 - y;
if (dist < minDist) {
var force = minDist / (dist * dist),
xcomp = (x - x2) / dist,
ycomp = (y - y2) / dist,
deltaV = force / 2;
flake.velX -= deltaV * xcomp;
flake.velY -= deltaV * ycomp;
} else {
flake.velX *= .98;
if (flake.velY <= flake.speed) {
flake.velY = flake.speed
}
flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
}
ctx.fillStyle = getRandomColor();
flake.y += flake.velY;
flake.x += flake.velX;
if (flake.y >= canvas.height || flake.y <= 0) {
reset(flake);
}
if (flake.x >= canvas.width || flake.x <= 0) {
reset(flake);
}
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
ctx.fill();
}
requestAnimationFrame(flow);
};
function reset(flake) {
let temp = (Math.random() * 1) + 0.5;
flake.x = canvas.width;
flake.y = 50;
flake.size = (Math.random() * 3) + 5;
flake.speed = (Math.random() * 7) + 0.5;
flake.velY = flake.speed;
flake.velX = -xMultiplier * canvas.width * temp;
// flake.opacity = (Math.random() * 0.5) + 0.3;
}
function init() {
for (var i = 0; i < flakeCount; i++) {
var x = canvas.width,
y = 50,
size = (Math.random() * 3) + 5,
// speed = (Math.random() * 1) + 0.5;
speed = 0;
// opacity = (Math.random() * 0.5) + 0.3;
particleArr.push({
speed: speed,
velY: speed,
velX: -xMultiplier * canvas.width * speed,
x: x,
y: y,
size: size,
stepSize: (Math.random()) / 30,
step: 0,
angle: 360
// opacity: opacity
});
}
flow();
};
canvas.addEventListener("mousemove", function(e) {
mouseX = e.clientX,
mouseY = e.clientY
});
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
init();
canvas {
background-color: #000000 !important;
}
body {
margin: 0;
overflow: hidden;
}
<canvas id="canvas"></canvas>
Could not work out what it was you wanted to slow down as there are several FX and interactions in the code.
I rewrote from the ground up as your code is a little old school.
Rather than play with the constants you had OI added the global variable rate (at the ver top of the source) is used to control the rate at which the animation plays, including the user interaction.
I have added two button to slow or speed up the animation.
Hope this helps :)
var rate = 1;
slower.addEventListener("click", () => rate *= 1 / 1.2);
faster.addEventListener("click", () => rate *= 1.2);
const flakes = [], flakeCount = 700, xMultiplier = 0.015;
const minDist = 150, minDistSqr = minDist * minDist;
const colors = ["#F99","#F83","#AF9","#ED9","#AC8","#FA9" ];
const ctx = canvas.getContext("2d");
const mouse = {x: -100, y: -100};
const randPick = (arr, len = arr.length) => arr[Math.random() * len | 0];
Math.rand = (min, range) => Math.random() * range + min;
function Flake() {
this.reset();
this.stepSize = Math.random() / 30;
this.step = 0;
}
Flake.prototype = {
reset() {
this.x = canvas.width;
this.y = 50;
this.size = Math.rand(5, 3);
this.speed = Math.rand(0.5, 7);
this.velY = this.speed;
this.velX = -xMultiplier * canvas.width * Math.rand(0.5, 1);
this.col = randPick(colors);
},
draw() {
ctx.fillStyle = this.col;
const s = this.size, sh = -s / 2;
ctx.fillRect(this.x + sh, this.y + sh, s, s);
},
update(w, h) {
const f = this;
const dx = f.x - mouse.x;
const dy = f.y - mouse.y;
const distSqr = dx * dx + dy * dy;
if (distSqr < minDistSqr) {
const deltaV = 2 * minDist * rate / distSqr ** 1.5;
f.velX -= deltaV * dx;
f.velY -= deltaV * dy;
} else {
f.velX -= 0.1 * rate * f.velX;
if (f.velY <= f.speed ) { f.velY = f.speed }
f.velX += Math.cos(f.step += 0.05 * rate) * f.stepSize * rate;
}
f.y += f.velY * rate;
f.x += f.velX * rate;
if (f.y >= h || f.y <= 0 || f.x >= w || f.x <= 0) { this.reset() }
else { this.draw() }
}
};
init();
mainLoop();
function mainLoop() {
if (innerWidth !== canvas.width || innerHeight !== canvas.height) { resize() }
else { ctx.clearRect(0, 0, canvas.width, canvas.height) }
for (const f of flakes) { f.update(canvas.width, canvas.height) }
requestAnimationFrame(mainLoop);
}
function init() {
var i = flakeCount;
while (i--) { flakes.push(new Flake()) }
}
canvas.addEventListener("mousemove", e => { mouse.x = e.clientX; mouse.y = e.clientY });
function resize() { canvas.width = innerWidth; canvas.height = innerHeight }
canvas {
background-color: #000;
}
body {
margin: 0;
}
.buttons {
position: absolute;
top: 12px;
left: 12px;
color: #000;
background-color: #AAA;
}
.buttons > div {
margin: 3px;
padding: 3px;
background-color: #EEE;
cursor: pointer;
}
.buttons > div:hover {
background-color: #DEF;
}
<canvas id="canvas"></canvas>
<div class = "buttons">
<div id="slower">Slower</div>
<div id="faster">Faster</div>
</div>
That requestAnimationFrame() function that calls flow() every frame is designed to run as fast as possible for whoever's computer it's running on. I wouldn't mess with your actual render loop.
Try messing with the flake.speed or the xMultiplier. Those are two of the main variables affecting the speed of your particles. You can see how each time through the flow() loop you're adjusting each particle's position based on their velocity properties and position. Then finally rendering the arc with ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
So any variable passed to ctx.arc() affects the particle's position. And many of those variables are recalculated each time through the loop.
I'm no expert here, but maybe try fiddling with your variables.
https://codepen.io/nitwit/pen/XWXJNaJ
https://jsfiddle.net/z6r8h5de/
if the issue is there are too many flakes on the screen, turn the count down from 700.
flakeCount = 100,
I've created a cursor trail over an image canvas where the image canvas is at the background and the cursor trail is on a second canvas on top of the image canvas. The issue I'm currently facing is that I am unable to create a trail that fades away gradually.
I've seen tips on how to make it fade with a plain background using fillStyle but I don't know how to make a fading cursor trail work with an image as the background.
<!DOCTYPE html>
<html>
<head>
<style>
.stack {
position: relative;
}
.stack canvas {
position: absolute;
left: 0;
top: 0;
}
.stack,
#main_canvas {
background-size: contain;
width: 100%;
margin: auto;
}
</style>
</head>
<body>
<div class="stack">
<canvas id="main_canvas"> main canvas</canvas>
</div>
<script>
var SCREEN_WIDTH = window.innerWidth;
var SCREEN_HEIGHT = window.innerHeight;
var RADIUS = 70;
var RADIUS_SCALE = 1;
var RADIUS_SCALE_MIN = 1;
var RADIUS_SCALE_MAX = 1.5;
var QUANTITY = 25;
var canvas;
var canvas_bg;
var context;
var context_bg;
var particles;
var slider_image;
var mouseX = SCREEN_WIDTH * 0.5;
var mouseY = SCREEN_HEIGHT * 0.5;
var mouseIsDown = false;
var ind = 0;
function init() {
canvas = document.getElementById("main_canvas");
canvas_bg = document.createElement("canvas"); //<canvas> predefined
canvas.setAttribute("alt", "countless stars");
if (canvas && canvas.getContext) {
windowResizeHandler();
//background canvas
create_sliders();
//main canvas for creating mouse trails
context = canvas.getContext("2d");
// Register event listeners
window.addEventListener("mousemove", documentMouseMoveHandler, false);
window.addEventListener("mousedown", documentMouseDownHandler, false);
window.addEventListener("mouseup", documentMouseUpHandler, false);
document.addEventListener(
"touchstart",
documentTouchStartHandler,
false
);
document.addEventListener(
"touchmove",
documentTouchMoveHandler,
false
);
window.addEventListener("resize", windowResizeHandler, false);
createParticles();
setInterval(loop, 1000 / 60);
}
}
function create_sliders() {
slider_image = new Image();
slider_image.src =
"https://images.pexels.com/photos/956999/milky-way-starry-sky-night-sky-star-956999.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940";
canvas_bg.width = canvas.width;
canvas_bg.height = canvas.height;
// insert into DOM on top:
canvas.parentNode.insertBefore(canvas_bg, canvas);
context_bg = canvas_bg.getContext("2d");
context_bg.drawImage(slider_image, 0, 0, canvas.width, canvas.height);
}
function createParticles() {
particles = [];
for (var i = 0; i < QUANTITY; i++) {
var particle = {
size: 1,
position: { x: mouseX, y: mouseY },
offset: { x: 0, y: 0 },
shift: { x: mouseX, y: mouseY },
speed: 0.01 + Math.random() * 0.04,
targetSize: 1,
fillColor:
"#" + ((Math.random() * 0x404040 + 0xaaaaaa) | 0).toString(16),
orbit: RADIUS * 0.5 + RADIUS * 0.5 * Math.random()
};
particles.push(particle);
}
}
function documentMouseMoveHandler(event) {
mouseX = event.clientX - (window.innerWidth - SCREEN_WIDTH) * 0.5;
mouseY = event.clientY - (window.innerHeight - SCREEN_HEIGHT) * 0.5;
}
function documentMouseDownHandler(event) {
mouseIsDown = true;
}
function documentMouseUpHandler(event) {
mouseIsDown = false;
}
function documentTouchStartHandler(event) {
if (event.touches.length == 1) {
event.preventDefault();
mouseX =
event.touches[0].pageX - (window.innerWidth - SCREEN_WIDTH) * 0.5;
mouseY =
event.touches[0].pageY - (window.innerHeight - SCREEN_HEIGHT) * 0.5;
}
}
function documentTouchMoveHandler(event) {
if (event.touches.length == 1) {
event.preventDefault();
mouseX =
event.touches[0].pageX - (window.innerWidth - SCREEN_WIDTH) * 0.5;
mouseY =
event.touches[0].pageY - (window.innerHeight - SCREEN_HEIGHT) * 0.5;
}
}
function windowResizeHandler() {
SCREEN_WIDTH = window.innerWidth;
SCREEN_HEIGHT = window.innerHeight;
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
}
function loop() {
if (mouseIsDown) {
RADIUS_SCALE += (RADIUS_SCALE_MAX - RADIUS_SCALE) * 0.02;
} else {
RADIUS_SCALE -= (RADIUS_SCALE - RADIUS_SCALE_MIN) * 0.02;
}
RADIUS_SCALE = Math.min(RADIUS_SCALE, RADIUS_SCALE_MAX);
// context.fillStyle = 'rgba(0,0,0,0.05)';
context.fillStyle = "rgba(0, 0, 0, 0)";
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
for (i = 0, len = particles.length; i < len; i++) {
var particle = particles[i];
var lp = { x: particle.position.x, y: particle.position.y };
// Rotation
particle.offset.x += particle.speed;
particle.offset.y += particle.speed;
// Follow mouse with some lag
particle.shift.x += (mouseX - particle.shift.x) * particle.speed;
particle.shift.y += (mouseY - particle.shift.y) * particle.speed;
// Apply position
particle.position.x =
particle.shift.x +
Math.cos(i + particle.offset.x) * (particle.orbit * RADIUS_SCALE);
particle.position.y =
particle.shift.y +
Math.sin(i + particle.offset.y) * (particle.orbit * RADIUS_SCALE);
// Limit to screen bounds
particle.position.x = Math.max(
Math.min(particle.position.x, SCREEN_WIDTH),
0
);
particle.position.y = Math.max(
Math.min(particle.position.y, SCREEN_HEIGHT),
0
);
particle.size += (particle.targetSize - particle.size) * 0.05;
if (Math.round(particle.size) == Math.round(particle.targetSize)) {
particle.targetSize = 1 + Math.random() * 7;
}
if (particle.position) context.beginPath();
context.fillStyle = particle.fillColor;
context.strokeStyle = particle.fillColor;
context.lineWidth = particle.size;
context.moveTo(lp.x, lp.y);
context.lineTo(particle.position.x, particle.position.y);
context.stroke();
context.arc(
particle.position.x,
particle.position.y,
particle.size / 2,
0,
Math.PI * 2,
true
);
context.fill();
}
}
window.onload = init;
</script>
</body>
</html>
Currently, the older trails will not fade away and it is creating something more of like a paint effect which I don't want.
One way to fade out your particles is to replace your (currently transparent) drawRect call with a drawImage that draws semi-transparent copy of your background image over each frame before adding particles to the current frame:
In your loop() function:
// Instead of this:
// context.fillStyle = "rgba(0, 0, 0, 0)";
// context.fillRect(0, 0, context.canvas.width, context.canvas.height);
// Do this:
context.save();
context.globalAlpha = 0.1;
context.drawImage(slider_image, 0, 0, canvas.width, canvas.height);
context.restore();
for (i = 0, len = particles.length; i < len; i++) {
...
I have nice Beautiful Stars Canvas Background script, and i wonder- how do i Display a DIV ontop of that Canvas script.
I try to put the DIV in or under the canvas tags (you can see the results on the link below) but it display nothing, or bring it at the bottom of the page in a way that you have no way to see it .
Here is my code:
HTML:
<canvas></canvas>
<div style="background-color:red">this is a test</div>
CSS:
body{
margin: 0;
overflow: hidden;
background: black
}
JS:
var n_stars = 150
var colors = [ '#176ab6', '#fb9b39']
for ( let i = 0; i < 98; i++) {
colors.push( '#fff')
}
var canvas = document.querySelector('canvas')
canvas.width = innerWidth
canvas.height = innerHeight
addEventListener( 'resize', () => {
canvas.width = innerWidth
canvas.height = innerHeight
stars = []
init()
})
canvas.style.background = '#000'
var c = canvas.getContext('2d')
const randomInt = ( max, min) => Math.floor( Math.random() * (max - min) + min)
var bg = c.createRadialGradient( canvas.width/ 2, canvas.height * 3, canvas.height ,canvas.width/ 2,canvas.height , canvas.height * 4);
bg.addColorStop(0,"#32465E");
bg.addColorStop(.4,"#000814");
bg.addColorStop(.8,"#000814");
bg.addColorStop(1,"#000");
class Star {
constructor( x, y, radius, color) {
this.x = x || randomInt( 0, canvas.width)
this.y = y || randomInt( 0, canvas.height)
this.radius = radius || Math.random() * 1.1
this.color = color || colors[randomInt(0, colors.length)]
this.dy = -Math.random() * .3
}
draw () {
c.beginPath()
c.arc( this.x, this.y, this.radius, 0, Math.PI *2 )
c.shadowBlur = randomInt( 3, 15)
c.shadowColor = this.color
c.strokeStyle = this.color
c.fillStyle = 'rgba( 255, 255, 255, .5)'
c.fill()
c.stroke()
c.closePath()
}
update( arrayStars = [] ) {
if ( this.y - this.radius < 0 ) this.createNewStar( arrayStars )
this.y += this.dy
this.draw()
}
createNewStar( arrayStars = [] ) {
let i = arrayStars.indexOf( this )
arrayStars.splice( i, 1)
arrayStars.push( new Star( false, canvas.height + 5))
}
}
var stars = []
function init() {
for( let i = 0; i < n_stars; i++ ) {
stars.push( new Star( ) )
}
}
init()
function animate() {
requestAnimationFrame( animate)
c.clearRect( 0, 0, canvas.width, canvas.height)
c.fillStyle = bg
c.fillRect(0, 0, canvas.width, canvas.height)
stars.forEach( s => s.update( stars ))
}
animate()
Here is a live one: Codepen
You mean something like this (used a wrapper element and positioned div absolutely on top of canvas element):
var n_stars = 150
var colors = [ '#176ab6', '#fb9b39']
for ( let i = 0; i < 98; i++) {
colors.push( '#fff')
}
var canvas = document.querySelector('canvas')
canvas.width = innerWidth
canvas.height = innerHeight
addEventListener( 'resize', () => {
canvas.width = innerWidth
canvas.height = innerHeight
stars = []
init()
})
canvas.style.background = '#000'
var c = canvas.getContext('2d')
const randomInt = ( max, min) => Math.floor( Math.random() * (max - min) + min)
var bg = c.createRadialGradient( canvas.width/ 2, canvas.height * 3, canvas.height ,canvas.width/ 2,canvas.height , canvas.height * 4);
bg.addColorStop(0,"#32465E");
bg.addColorStop(.4,"#000814");
bg.addColorStop(.8,"#000814");
bg.addColorStop(1,"#000");
class Star {
constructor( x, y, radius, color) {
this.x = x || randomInt( 0, canvas.width)
this.y = y || randomInt( 0, canvas.height)
this.radius = radius || Math.random() * 1.1
this.color = color || colors[randomInt(0, colors.length)]
this.dy = -Math.random() * .3
}
draw () {
c.beginPath()
c.arc( this.x, this.y, this.radius, 0, Math.PI *2 )
c.shadowBlur = randomInt( 3, 15)
c.shadowColor = this.color
c.strokeStyle = this.color
c.fillStyle = 'rgba( 255, 255, 255, .5)'
c.fill()
c.stroke()
c.closePath()
}
update( arrayStars = [] ) {
if ( this.y - this.radius < 0 ) this.createNewStar( arrayStars )
this.y += this.dy
this.draw()
}
createNewStar( arrayStars = [] ) {
let i = arrayStars.indexOf( this )
arrayStars.splice( i, 1)
arrayStars.push( new Star( false, canvas.height + 5))
}
}
var stars = []
function init() {
for( let i = 0; i < n_stars; i++ ) {
stars.push( new Star( ) )
}
}
init()
function animate() {
requestAnimationFrame( animate)
c.clearRect( 0, 0, canvas.width, canvas.height)
c.fillStyle = bg
c.fillRect(0, 0, canvas.width, canvas.height)
stars.forEach( s => s.update( stars ))
}
animate()
body{
margin: 0;
overflow: hidden;
background: black
}
.wrapper{
position: relative;
display: block;
}
.wrapper > canvas {
position: relative;
display: block;
}
.wrapper > div {
position: absolute;
z-index: 10;
top: 0px;
left: 0px;
width: 100px;
height: 100px;
display: block;
}
<div class="wrapper">
<canvas></canvas>
<div style="background-color:red">this is a test</div>
</div>
I started to work on this animation and got the base from another animation, i have pretty much customized it all to my needs besides the lines. Currently the lines are pointy and i have gone through the code multiple times trying to find what creates these spiky lines. I would appreciate if someone could check both the provided code and the external code and identify what it is. All help is appreciated thanks.
// Settings
var particleCount = 35,
flareCount = 0,
motion = 0.05,
tilt = 0,
particleSizeBase = 1,
particleSizeMultiplier = 0.5,
flareSizeBase = 100,
flareSizeMultiplier = 100,
glareAngle = -60,
glareOpacityMultiplier = 0.4,
renderParticles = true,
renderParticleGlare = true,
renderFlares = false,
renderLinks = false,
renderMesh = false,
flicker = false,
flickerSmoothing = 15, // higher = smoother flicker
blurSize = 0,
orbitTilt = true,
randomMotion = true,
noiseLength = 1000,
noiseStrength = 3;
document.querySelectorAll('.stars').forEach(canvas => {
var context = canvas.getContext('2d'),
color = canvas.dataset['color'],
mouse = { x: 0, y: 0 },
m = {},
r = 0,
c = 1000, // multiplier for delaunay points, since floats too small can mess up the algorithm
n = 0,
nAngle = (Math.PI * 2) / noiseLength,
nRad = 100,
nScale = 1,
nPos = {x: 0, y: 0},
points = [],
vertices = [],
triangles = [],
links = [],
particles = [],
flares = [];
function init() {
var i, j, k;
// requestAnimFrame polyfill
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
// Size canvas
resize();
mouse.x = canvas.clientWidth / 2;
mouse.y = canvas.clientHeight / 2;
// Create particle positions
for (i = 0; i < particleCount; i++) {
var p = new Particle();
particles.push(p);
points.push([p.x*c, p.y*c]);
}
vertices = Delaunay.triangulate(points);
var tri = [];
for (i = 0; i < vertices.length; i++) {
if (tri.length == 2) {
triangles.push(tri);
tri = [];
}
tri.push(vertices[i]);
}
// Tell all the particles who their neighbors are
for (i = 0; i < particles.length; i++) {
// Loop through all tirangles
for (j = 0; j < triangles.length; j++) {
// Check if this particle's index is in this triangle
k = triangles[j].indexOf(i);
// If it is, add its neighbors to the particles contacts list
if (k !== -1) {
triangles[j].forEach(function(value, index, array) {
if (value !== i && particles[i].neighbors.indexOf(value) == -1) {
particles[i].neighbors.push(value);
}
});
}
}
}
var fps = 60;
var now;
var then = Date.now();
var interval = 1000/fps;
var delta;
// Animation loop
(function animloop(){
requestAnimFrame(animloop);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
resize();
render();
}
})();
}
function render() {
if (randomMotion) {
n++;
if (n >= noiseLength) {
n = 0;
}
nPos = noisePoint(n);
}
if (renderParticles) {
// Render particles
for (var i = 0; i < particleCount; i++) {
particles[i].render();
}
}
}
function resize() {
canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.width * (canvas.clientHeight / canvas.clientWidth);
}
// Particle class
var Particle = function() {
this.x = random(-0.1, 1.1, true);
this.y = random(-0.1, 1.1, true);
this.z = random(0,4);
this.color = color;
this.opacity = random(0.1,1,true);
this.flicker = 0;
this.neighbors = []; // placeholder for neighbors
};
Particle.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * particleSizeMultiplier) + particleSizeBase) * (sizeRatio() / 1000),
o = this.opacity;
context.fillStyle = this.color;
context.globalAlpha = o;
context.beginPath();
context.fill();
context.closePath();
if (renderParticleGlare) {
context.globalAlpha = o * glareOpacityMultiplier;
context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
}
context.globalAlpha = 1;
};
// Utils
function noisePoint(i) {
var a = nAngle * i,
cosA = Math.cos(a),
sinA = Math.sin(a),
rad = nRad;
return {
x: rad * cosA,
y: rad * sinA
};
}
function position(x, y, z) {
return {
x: (x * canvas.width) + ((((canvas.width / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * z) * motion),
y: (y * canvas.height) + ((((canvas.height / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * z) * motion)
};
}
function sizeRatio() {
return canvas.width >= canvas.height ? canvas.width : canvas.height;
}
function random(min, max, float) {
return float ?
Math.random() * (max - min) + min :
Math.floor(Math.random() * (max - min + 1)) + min;
}
// init
if (canvas) init();
});
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
background: #000;
background-image: linear-gradient(-180deg, rgba(0, 0, 0, 0.00) 0%, #000000 100%);
height: 100%;
}
#stars {
display: block;
position: relative;
width: 100%;
height: 100vh;
z-index: 1;
position: absolute;
}
<script src="https://rawgit.com/ironwallaby/delaunay/master/delaunay.js"></script>
<script src="http://requirejs.org/docs/release/2.1.15/minified/require.js"></script>
<canvas id="Stars" class="stars" width="300" height="300" data-color="#fff"></canvas>
// Tell all the particles who their neighbors are
for (i = 0; i < particles.length; i++) {
// Loop through all tirangles
for (j = 0; j < triangles.length; j++) {
// Check if this particle's index is in this triangle
k = triangles[j].indexOf(i);
// If it is, add its neighbors to the particles contacts list
if (k !== -1) {
triangles[j].forEach(function(value, index, array) { // <----- missing ')' here
if (value !== i && particles[i].neighbors.indexOf(value) == -1) {
particles[i].neighbors.push(value);
}
});
}
}
}
The reason you are getting spikes is the radius of the ellipse is too large so the ends are so small they look like points but it's just a small radius.
context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
After experimenting with different shapes like rect() and arc():
The code below instead of using ellipse uses a rotated rectangle. Since the shape is different the algorithm for getting the angle you wanted will need more work but this code solves the problem of the pointy ends.
//context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
context.rotate((glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180));
context.fillRect(pos.x, pos.y, r * 100, r)
context.closePath();
It will take more work to align them to match the first code, but the pointy ends are gone.
Also, rotate() is actually rotating the canvas not the rectangles so keep that in mind. I would start with a simple 45 degree angle and see what that generates.
I'm a canvas beginner, sorry if this is a trivial question. How can I make the fireworks in my work fade out once they've exploded?
https://jsfiddle.net/ccwhryvv/
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight,
mousePos = {
x: 400,
y: 300
},
// create canvas
canvas = document.createElement('canvas'),
context = canvas.getContext('2d'),
particles = [],
rockets = [],
MAX_PARTICLES = 400,
colorCode = 0;
// init
$(document).ready(function() {
$('#content')[0].appendChild(canvas);
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
setInterval(launch, 800);
setInterval(loop, 1000 / 50);
});
// update mouse position
$(document).mousemove(function(e) {
e.preventDefault();
mousePos = {
x: e.clientX,
y: e.clientY
};
});
// launch more rockets!!!
$(document).mousedown(function(e) {
for (var i = 0; i < 5; i++) {
launchFrom(Math.random() * SCREEN_WIDTH * 2 / 3 + SCREEN_WIDTH / 6);
}
});
function launch() {
launchFrom(SCREEN_WIDTH / 2);
}
function launchFrom(x) {
if (rockets.length < 10) {
var rocket = new Rocket(x);
rocket.explosionColor = Math.floor(Math.random() * 360 / 10) * 10;
rocket.vel.y = Math.random() * -3 - 4;
rocket.vel.x = Math.random() * 6 - 3;
rocket.size = 8;
rocket.shrink = 0.999;
rocket.gravity = 0.01;
rockets.push(rocket);
}
}
function loop() {
// update screen size
if (SCREEN_WIDTH != window.innerWidth) {
canvas.width = SCREEN_WIDTH = window.innerWidth;
}
if (SCREEN_HEIGHT != window.innerHeight) {
canvas.height = SCREEN_HEIGHT = window.innerHeight;
}
// clear canvas
context.fillStyle = "rgba(0, 0, 0, 0.0)";
context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
var existingRockets = [];
for (var i = 0; i < rockets.length; i++) {
// update and render
rockets[i].update();
rockets[i].render(context);
// calculate distance with Pythagoras
var distance = Math.sqrt(Math.pow(mousePos.x - rockets[i].pos.x, 2) + Math.pow(mousePos.y - rockets[i].pos.y, 2));
// random chance of 1% if rockets is above the middle
var randomChance = rockets[i].pos.y < (SCREEN_HEIGHT * 2 / 3) ? (Math.random() * 100 <= 1) : false;
/* Explosion rules
- 80% of screen
- going down
- close to the mouse
- 1% chance of random explosion
*/
if (rockets[i].pos.y < SCREEN_HEIGHT / 5 || rockets[i].vel.y >= 0 || distance < 50 || randomChance) {
rockets[i].explode();
} else {
existingRockets.push(rockets[i]);
}
}
rockets = existingRockets;
var existingParticles = [];
for (var i = 0; i < particles.length; i++) {
particles[i].update();
// render and save particles that can be rendered
if (particles[i].exists()) {
particles[i].render(context);
existingParticles.push(particles[i]);
}
}
// update array with existing particles - old particles should be garbage collected
particles = existingParticles;
while (particles.length > MAX_PARTICLES) {
particles.shift();
}
}
function Particle(pos) {
this.pos = {
x: pos ? pos.x : 0,
y: pos ? pos.y : 0
};
this.vel = {
x: 0,
y: 0
};
this.shrink = .97;
this.size = 2;
this.resistance = 1;
this.gravity = 0;
this.flick = false;
this.alpha = 1;
this.fade = 0;
this.color = 0;
}
Particle.prototype.update = function() {
// apply resistance
this.vel.x *= this.resistance;
this.vel.y *= this.resistance;
// gravity down
this.vel.y += this.gravity;
// update position based on speed
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
// shrink
this.size *= this.shrink;
// fade out
this.alpha -= this.fade;
};
Particle.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255,255,255," + this.alpha + ")");
gradient.addColorStop(0.8, "hsla(" + this.color + ", 100%, 50%, " + this.alpha + ")");
gradient.addColorStop(1, "hsla(" + this.color + ", 100%, 50%, 0.1)");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
Particle.prototype.exists = function() {
return this.alpha >= 0.1 && this.size >= 1;
};
function Rocket(x) {
Particle.apply(this, [{
x: x,
y: SCREEN_HEIGHT}]);
this.explosionColor = 0;
}
Rocket.prototype = new Particle();
Rocket.prototype.constructor = Rocket;
Rocket.prototype.explode = function() {
var count = Math.random() * 10 + 80;
for (var i = 0; i < count; i++) {
var particle = new Particle(this.pos);
var angle = Math.random() * Math.PI * 2;
// emulate 3D effect by using cosine and put more particles in the middle
var speed = Math.cos(Math.random() * Math.PI / 2) * 15;
particle.vel.x = Math.cos(angle) * speed;
particle.vel.y = Math.sin(angle) * speed;
particle.size = 10;
particle.gravity = 0.2;
particle.resistance = 0.92;
particle.shrink = Math.random() * 0.05 + 0.93;
particle.flick = true;
particle.color = this.explosionColor;
particles.push(particle);
}
};
Rocket.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255, 255, 255 ," + this.alpha + ")");
// gradient.addColorStop(1, "rgba(255, 255, 255, " + this.alpha + ")");
gradient.addColorStop(1, "rgba(255, 255, 255, 0)");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size / 2 + this.size / 2 : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
Thank you!
Creating gradients is expensive -- especially inside an animation loop.
It's more efficient is to pre-create a spritesheet of gradient exploding sprites before your app starts:
Create an in-memory canvas to act as a spritesheet,
Choose a dozen standard colors for you explosions.
Create gradient sprites in sequential order of exploding.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var ss=makeSpritesheet(10,15);
ctx.fillStyle='navy';
ctx.fillRect(0,0,cw,ch);
ctx.drawImage(ss,0,0);
function makeSpritesheet(maxRadius,colorCount){
var c=document.createElement('canvas');
var ctx=c.getContext('2d');
var spacing=maxRadius*2.5;
c.width=spacing*maxRadius;
c.height=spacing*(colorCount+1);
for(var colors=0;colors<colorCount;colors++){
var y=(colors)*spacing+spacing/2;
var color = parseInt(colors/colorCount*360);
for(r=2;r<=maxRadius;r++){
var x=(r-1)*spacing;
var gradient = ctx.createRadialGradient(x, y, 0, x, y, r);
gradient.addColorStop(0.2, "white");
gradient.addColorStop(0.7, 'hsla('+color+', 100%, 50%, 1)');
gradient.addColorStop(1.0, "rgba(0,0,0,0)");
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(x,y,r,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
}
return(c);
}
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=640 height=512></canvas>
During Animation, draw the sprites from the spritesheet to your canvas.
Fade the opacity of each sprite by setting context.globalAlpha before drawing each sprite.