I'm creating particles for the first time with javascript and I'm not sure if the below code is optimized.
When I create 100 particles on the screen I don't notice that much of an fps drop.
When multiple clicks happen in a row the fps takes major dip.
This is understandable but is there a way to optimize this code more so that multiple clicks will maintain a higher frame rate?
var fps = document.getElementById("fps");
var startTime = Date.now();
var frame = 0;
function tick() {
var time = Date.now();
frame++;
if (time - startTime > 1000) {
fps.innerHTML = (frame / ((time - startTime) / 1000)).toFixed(1);
startTime = time;
frame = 0;
}
window.requestAnimationFrame(tick);
}
tick();
function pop (target) {
let amount = 100;
// Quick check if user clicked the button using a keyboard
const bbox = target.getBoundingClientRect();
const x = bbox.left + bbox.width / 2;
const y = bbox.top + bbox.height / 2;
for (let i = 0; i < 100; i++) {
createParticle(x, y);
}
}
function createParticle (x, y) {
const particle = document.createElement('particle');
document.body.appendChild(particle);
let width = Math.floor(Math.random() * 30 + 8);
let height = width;
let destinationX = (Math.random() - 0.5) * 1000;
let destinationY = (Math.random() - 0.5) * 1000;
let rotation = Math.random() * 2000;
let delay = Math.random() * 200;
particle.style.background = `hsl(${Math.random() * 90 + 270}, 70%, 60%)`;
particle.style.border = '1px solid white';
particle.style.width = `${width}px`;
particle.style.height = `${height}px`;
const animation = particle.animate([
{
transform: `translate(-50%, -50%) translate(${x}px, ${y}px) rotate(0deg)`,
opacity: 1
},
{
transform: `translate(-50%, -50%) translate(${x + destinationX}px, ${y + destinationY}px) rotate(${rotation}deg)`,
opacity: 0
}
], {
duration: Math.random() * 1000 + 5000,
easing: 'cubic-bezier(0, .9, .57, 1)',
delay: delay
});
animation.onfinish = removeParticle;
}
function removeParticle (e) {
e.srcElement.effect.target.remove();
}
document.querySelector(".confetti").addEventListener("click",()=>{pop(document.querySelector(".confetti"));}
);
particle {
position: fixed;
top: 0;
left: 0;
opacity: 0;
pointer-events: none;
background-repeat: no-repeat;
background-size: contain;
}
<div class="wrapper">
<span id="fps">--</span> FPS</div>
<div class="confetti">Square particles</div>
</div>
It would be much quicker if you could add a canvas and instead of adding the elements and styling them, you could just draw things onto the canvas. Then, it can be way faster. If you don't get how to use a canvas with JavaScript, you might want to search it up on something like W3Schools, because they have lots of things you could learn from them.
I hope this helps. Please give me any feedback if you have any. I am new to Stack Overflow, so I want to know how to improve. Thanks and happy coding!
Related
I have written a particles animation using the web animations API,when tested on any browser whether on desktop or mobile it animates smoothly except when run on chrome for ios.The first run on chrome for ios usually(not always) works fine but when other tabs are open alongside, the animation starts getting slower and freezes at certain points and that worsens whenever the number of other open tabs increases or when website visited with tabs already open in the browser.
I noticed that the transitions made with transform properties like (scale and translate) are the most impacted
I tried re-writing the animation using requestAnimationFrame,and had the same results
The only way to get the animation to run normally is by closing all tabs and relaunching chrome
screenshots:
normal animation:
freezing animation aftere opening more tabs:
Here is the function for particle creation and animation with web animations API
function createParticle(x, y) {
const particle = document.createElement("particle");
particles.push(particle)
document.body.appendChild(particle);
const size = Math.floor(Math.random() * 6 + 5);
particle.style.position = "fixed";
particle.style.left = "0";
particle.style.top = "0";
particle.style.borderRadius = "50%";
particle.style.pointerEvents = "none";
particle.style.opacity = "0";
particle.style.width = `${size}px`;
particle.style.height = `${size}px`;
rndColor = Math.floor(Math.random() * 3);
particle.style.background = `${colors[rndColor]}`;
let to_x = x + (Math.random() - 0.5) * 130;
let to_y = y + (Math.random() - 0.5) * 130;
const animation = particle.animate(
[
{
transform: `translate(-50%, -50%) translate(${x}px, ${y}px)`,
opacity: 0,
},
{
opacity:1
},
{
opacity: 0.5,
},
{
transform: `translate(${to_x}px, ${to_y}px)`,
opacity: 0,
},
],
{
duration: Math.random() * 700,
easing: "ease-in",
delay: Math.random() *300,
}
);
animation.onfinish = () => {
particle.remove();
};
}
}
Here the code for second try with requestAnimationFrame
var div = document.createElement("div");
div.classList.add("particle_container")
document.querySelector("body").appendChild(div);
div.style.left = e.pageX + "px";
div.style.top = e.pageY + "px";
var maxElems = 130;
for (i = 0; i < maxElems; i++) {
var span = document.createElement("span");
span.classList.add("particle")
var newSpan = div.appendChild(span);
var deg = i * (360 / maxElems) + Math.floor(Math.random() * 10);
var size = Math.floor(Math.random() * 10) + 4;
newSpan.style.height = size + "px";
newSpan.style.width = size + "px";
newSpan.style.borderRadius = "50%";
rndClr = colors[Math.floor(Math.random() * colors.length)]
newSpan.style.background = rndClr;
newSpan.style.transform = "rotate(" + deg + "deg)";
newSpan.style.transitionDuration = ((Math.random() * 1) + .2) + "s"
// newSpan.style.transitionDelay = (Math.random() *300) +"ms"
newSpan.style.transitionTimingFunction = "ease-out"
}
window.requestAnimationFrame(() => {
Array.from(div.querySelectorAll("span")).forEach((el) => {
var trasY = -50 - Math.floor(Math.random() * 50);
el.style.transform += "scaleY(1.3) translateY(" + trasY + "px)";
el.style.opacity = "0";
});
window.setTimeout(function () {
document.body.removeChild(div);
}, 1200)
});
So I wish to add an effect to my website of floating particles. Came across this codepen which does the exact thing - https://codepen.io/OliverKrieger/pen/EjLEVX.
I've been trying to change it a bit so that the particles only trigger when a checkbox is checked. I understand it might've been already answered on SO but in the codepen code, there is an window.onload function which automatically fires it when window is loaded. I want there to be a checkbox instead.
I tried putting the following in html -
<input type="checkbox" id="switch" onchange="function()"">
<label for="switch" >Toggle</label>
But it seems like the script still triggers automatically. Can someone help me with this please? I'm new to programming so I apologize for any vagueness. Please lemme know if I can provide more details. Any input would be really appreciated.
In your code, function is a keyword and not a function name. The function is still run as soon as the page loads because of the way it is defined in the javascript, and your HTML addition has no effect on this.
You need to change your function into a named function, declared using the function keyword:
function functionName() { ... }
And then you can call it by its name in your HTML:
<input type="checkbox" id="switch" onchange="functionName()">
This should work
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
window.requestAnimationFrame = requestAnimationFrame;
})();
$(window).resize(function() {
ParticleCanvas.width = ($(window).width() - 20);
ParticleCanvas.height = ($(window).height() - 10);
});
$(function() {
let request = null;
$("#switch").change(function() {
if (this.checked) {
animateDust();
} else {
window.cancelAnimationFrame(request);
}
})
var ParticleCanvas = document.getElementById("ParticleCanvas");
var context = ParticleCanvas.getContext("2d");
ParticleCanvas.width = ($(window).width() - 20);
ParticleCanvas.height = ($(window).height() - 10);
$("switch").click(function() {
animateDust();
})
// All the info stored into an array for the particles
var particles = {},
particleIndex = 0,
settings = {
density: 20,
particleSize: 2,
startingX: ParticleCanvas.width / 2,
startingY: ParticleCanvas.height,
gravity: -0.01
};
// Set up a function to create multiple particles
function Particle() {
// Establish starting positions and velocities from the settings array, the math random introduces the particles being pointed out from a random x coordinate
this.x = settings.startingX * (Math.random() * 10);
this.y = settings.startingY;
// Determine original X-axis speed based on setting limitation
this.vx = (Math.random() * 2 / 3) - (Math.random() * 3 / 3);
this.vy = -(Math.random() * 5 / 3);
// Add new particle to the index
// Object used as it's simpler to manage that an array
particleIndex++;
particles[particleIndex] = this;
this.id = particleIndex;
this.life = 0;
this.maxLife = 200;
this.alpha = 1;
this.red = 0;
this.green = 255;
this.blue = 255;
}
// Some prototype methods for the particle's "draw" function
Particle.prototype.draw = function() {
this.x += this.vx;
this.y += this.vy;
// Adjust for gravity
this.vy += settings.gravity;
// Age the particle
this.life++;
this.red += 2;
this.alpha -= 0.005;
// If Particle is old, it goes in the chamber for renewal
if (this.life >= this.maxLife) {
delete particles[this.id];
}
// Create the shapes
context.clearRect(settings.leftWall, settings.groundLevel, ParticleCanvas.width, ParticleCanvas.height);
context.beginPath();
context.fillStyle = "rgba(" + this.red + ", " + this.green + ", " + this.blue + ", " + this.alpha + ")";
// Draws a circle of radius 20 at the coordinates 100,100 on the ParticleCanvas
context.arc(this.x, this.y, settings.particleSize, 0, Math.PI * 2, true);
context.closePath();
context.fill();
}
function animateDust() {
context.clearRect(0, 0, ParticleCanvas.width, ParticleCanvas.height);
// Draw the particles
for (var i = 0; i < settings.density; i++) {
if (Math.random() > 0.97) {
// Introducing a random chance of creating a particle
// corresponding to an chance of 1 per second,
// per "density" value
new Particle();
}
}
for (var i in particles) {
particles[i].draw();
}
request = window.requestAnimationFrame(animateDust);
}
})
body {
background-color: #000000;
color: #555555;
margin: 0;
padding: 0;
}
#ParticleCanvas {
border: 1px solid white;
position: absolute;
z-index: -1;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
#container {
background: white;
padding: 1rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="ParticleCanvas"></canvas>
<div id="container">
<form>
<input type="checkbox" id="switch" name="switch">
</form>
<label for="switch">Toggle</label>
</div>
I wanna do some effect to my background so i tought i can add some shapes flying around but i do only 1 shape i cant loop or anything
i tried call function more than a one time but it doesnt help
function animasyon(a) {
window.onload=function(){
var id = setInterval(anim,5);
$('body').append('<div class=shape></div>');
$('body').append('<div class=shape></div>');
var kutu = document.getElementsByClassName("shape");
var pos = 0;
var x = window.innerWidth;
var y = window.innerHeight;
var borderSize = Math.floor(Math.random() * 3 ) + 1;
var ySize = Math.floor(Math.random() * y ) + 1;
var Size = Math.floor(Math.random() * 30 ) + 5;
var yon = Math.floor(Math.random() *2)+1;
var dolu = Math.floor(Math.random() *2)+1;
if (ySize > 50) { ySize-=20; }
function anim(){
if (pos == x) {
clearInterval(id);
document.getElementById("shape").remove();
}else{
pos++;
kutu[a].style.position = "absolute";
kutu[a].style.border = "solid rgb(119,38,53) "+borderSize+"px";
kutu[a].style.left = pos+"px";
kutu[a].style.width = Size+"px";
kutu[a].style.height = Size+"px";
if (yon == 1) { ySize-=0.2; } else { ySize+=0.2; }
if (dolu==1) {kutu[a].style.background = "rgb(119,38,53)";}
if (kutu[a].offsetTop < 0 || kutu[a].offsetTop > y-30) {document.getElementById("shape").remove();}
kutu[a].style.top = ySize+"px";
}
}
}
}
animasyon(0);
Try Calling 'anim' function in this way
setInterval(function(){anim()},5);
Your problem is that you are assigning window.onload function a value inside the function animasyon
window.onload holds only 1 function. If you call animation function more than once, then the last one will overwrite the first one.
Edit: You need to separate your animation logic from the page load logic. They are completely separate things.
Here is an example of adding multiple objects and animating each separately.
// HTML
<div id="container">
<div id="ball"></div>
<div id="ball2"></div>
<div id="ball3"></div>
<div id="ball4"></div>
</div>
// CSS
#ball, #ball2, #ball3, #ball4 {
background: red;
border: 1px solid #FAFDFA;
display: inline-block;
width: 1em;
height: 1em;
border-radius: 2em;
position: absolute;
}
#ball2 {
background: blue;
}
#ball3 {
background: green;
}
#ball4 {
background: purple;
}
#container {
width: 512px;
height: 512px;
background: black;
position: relative;
}
// JS
const container = document.getElementById('container');
const stageWidth = 512;
const stageHeight = 512;
const makeFall = (elementId) => {
// this function makes an enlement fall in random direction
const animationSpeed = 4;
// your onload function
const ball = document.getElementById(elementId);
let directionX = (Math.random() * animationSpeed * 2) - animationSpeed;
let directionY = Math.random() * animationSpeed;
const setRandomStart = () => {
ball.style.top = '10px';
ball.style.left = (Math.random() * (stageWidth / 2)) + 'px';
directionX = (Math.random() * animationSpeed * 2) - animationSpeed;
directionY = Math.random() * animationSpeed;
}
setRandomStart();
let animationInterval = setInterval(() => {
let px = parseFloat(ball.style.left);
let py = parseFloat(ball.style.top);
px += directionX;
py += directionY;
if (px > stageWidth - 20 || py > stageHeight - 20 || px < -20) {
setRandomStart();
} else {
ball.style.left = px + 'px';
ball.style.top = py + 'px';
}
}, 48);
}
// In Your onload function you can add the elements and then animate
makeFall('ball');
makeFall('ball2');
makeFall('ball3');
makeFall('ball4');
https://jsfiddle.net/753oL8re/4/
I want to develop a kind of wheel of fortune with javascript. I want to define both the output and the start. If the user turns the wheel with a minimum speed, the wheel should rotate a few times depending on the rotation speed, before it stops on the desired field.
So far I have only been able to implement the basics, but I hope that somebody can help me to implement the complete process, since I can't get any further now.
HOW TO MAKE WHEEL ROTATING A SERVERAL TIMES ENDING WITH THE targetAngle ?
let element = document.getElementById("wheel");
let region = new ZingTouch.Region(element);
let currentAngle = 0, // this is the current angle of the wheel by the rotate gesture
targetAngle = 0; // blue field is the target where the wheel should stop
let minimumAngle = 30, // minimumAngle to make the wheel spinning
rotatedAngle = 0, // total spinned angle of the wheel by the rotate gesture
spinning = false
setInterval(() => {
rotatedAngle = 0;
}, 200)
region.bind(element, 'rotate', function(e) {
if (rotatedAngle > minimumAngle && rotatedAngle > 180) {
spinning = true;
document.getElementById("spinning").innerHTML = "spinning = true"
// HOW TO MAKE WHEEL ROTATING A SERVERAL TIMES ENDING WITH THE targetAngle ?
} else if (!spinning) {
currentAngle += e.detail.distanceFromLast;
rotatedAngle += e.detail.distanceFromLast;
element.style.transform = 'rotate(' + currentAngle + 'deg)';
}
})
#wheel {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: 20%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/zingtouch/1.0.6/zingtouch.min.js"></script>
<img id="wheel" src="https://reel-mkt.com/templates/fortune_reel_lp/img/wheel.png">
<p id="spinning">spinning = false</p>
This is how I would do it:
I need an event like "click" to begin spinning
I'm calculating the targetAngle as a random angle: targetAngle = currentAngle + Math.random() * 360 + minimumAngle;
I'm using requestAnimationFrame for the animation.
I'm calculating the speed as 1/10 of the distance between the targetAngle and the currentAngle
I'm not using ZingTouch as you intended; I don't use the rotatedAngle either.
I hope this is what you need.
let element = document.getElementById("wheel");
/*let region = new ZingTouch.Region(element);*/
let currentAngle = 0, // this is the current angle of the wheel by the rotate gesture
targetAngle = 0; // blue field is the target where the wheel should stop
let minimumAngle = 30, // minimumAngle to make the wheel spinning
//rotatedAngle = 0, // total spinned angle of the wheel by the rotate gesture
spinning = false;
let rid = null;
element.addEventListener("click", () => {
targetAngle = currentAngle + Math.random() * 360 + minimumAngle;
if (spinning) {
spinning = false;
if (rid) {
cancelAnimationFrame(rid);
rid = null;
}
} else {
spinning = true;
Animation();
}
is_spinning.innerHTML = spinning;
});
function Animation() {
rid = requestAnimationFrame(Animation);
if (currentAngle <= targetAngle) {
let speed = (targetAngle - currentAngle)/10;
currentAngle += speed;
element.style.transform = `rotate(${currentAngle}deg)`;
if(speed <= 1){// if the speed is < 1 stop the wheel
cancelAnimationFrame(rid);
spinning = false;
}
} else {//stop the wheel
cancelAnimationFrame(rid);
spinning = false;
}
is_spinning.innerHTML = spinning;
}
#wheel {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: 20%;
}
<img id="wheel" src="https://reel-mkt.com/templates/fortune_reel_lp/img/wheel.png">
<p>spinning = <span id="is_spinning">false</span></p>
**An observation"": Your question is tagged as canvas but in your code you positioned the image in the center of the screen. So I assumed that you wanted to rotate the image.
I want to animate a div (say height 50 px and width 50 px) from left to right in the browser.
I can share my html css part here:
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.box{
width:100px;
height:100px;
position: absolute;
}
.blue{
background:#00f;
}
.position{
margin-top: 50px;
}
</style>
</head>
<body>
<div class="box blue position" id="move_box"> </div>
<script>
</script>
</body>
</html>
How can I animate the div from left to right according to the condition "moves 10 pixels right and 10 pixels down per second"?
Note: only in JavaScript.
My script:
<script>
var box = document.getElementById('move_box'),
boxPos = 0,
boxLastPos = 0,
boxVelocity = 0.01,
limit = 300,
lastFrameTimeMs = 0,
maxFPS = 60,
delta = 0,
timestep = 1000 / 60,
fps = 60,
framesThisSecond = 0,
lastFpsUpdate = 0,
running = false,
started = false,
frameID = 0;
function update(delta) {
boxLastPos = boxPos;
boxPos += boxVelocity * delta;
// Switch directions if we go too far
if (boxPos >= limit || boxPos <= 0) boxVelocity = -boxVelocity;
}
function draw(interp) {
box.style.left = (boxLastPos + (boxPos - boxLastPos) * interp) + 'px';
box.style.top = (boxLastPos + (boxPos - boxLastPos) * interp) + 'px';
console.log(box.style.top);
}
function panic() {
delta = 0;
}
function begin() {
}
function end(fps) {
/*box.style.backgroundColor = 'blue';*/
}
function stop() {
running = false;
started = false;
cancelAnimationFrame(frameID);
}
function start() {
if (!started) {
started = true;
frameID = requestAnimationFrame(function(timestamp) {
draw(1);
running = true;
lastFrameTimeMs = timestamp;
lastFpsUpdate = timestamp;
framesThisSecond = 0;
frameID = requestAnimationFrame(mainLoop);
});
}
}
function mainLoop(timestamp) {
// Throttle the frame rate.
if (timestamp < lastFrameTimeMs + (1000 / maxFPS)) {
frameID = requestAnimationFrame(mainLoop);
return;
}
delta += timestamp - lastFrameTimeMs;
lastFrameTimeMs = timestamp;
begin(timestamp, delta);
if (timestamp > lastFpsUpdate + 1000) {
fps = 0.25 * framesThisSecond + 0.75 * fps;
lastFpsUpdate = timestamp;
framesThisSecond = 0;
}
framesThisSecond++;
var numUpdateSteps = 0;
while (delta >= timestep) {
update(timestep);
delta -= timestep;
if (++numUpdateSteps >= 240) {
panic();
break;
}
}
draw(delta / timestep);
end(fps);
frameID = requestAnimationFrame(mainLoop);
}
start();
</script>
Check that:
var i = 1;
var div = document.getElementById('move_box');
function myLoop() {
setTimeout(function() {
div.style.top = i * 10 + 'px'
div.style.left = i * 10 + 'px'
i++;
if (i < 5) {
myLoop();
}
}, 1000)
}
myLoop(); // start the loop
.box {
width: 100px;
height: 100px;
position: absolute;
top: 0;
left: 0;
}
.blue {
background: #00f;
}
.position {
margin-top: 50px;
}
<div class="box blue position" id="move_box"> </div>
JSFiddle