I'm stuck in a project about snow animation in javascript - javascript

I have to create a script that simulates the animation of snow in javascript (I would prefer without using canvas if possible). I'm stuck at this point and I don't know where the mistake is. when I run the program google chrome crashes and the console does not give me errors, I think the error is due to a loop or some incorrect statements, I am attaching the code hoping for your help!
var direction=true; //true=right false =left
var active=true; //if true run the cycle,false stops the cycle
function startSnow() {
var _snowflakes = new Array(30);
var wid=50; //distance from a snowflake to another
var counterX; //var for editing the x position
var counterY; //var for editing the y position
for (var i=0; i< 30; i++) //cycle that initializes snowflakes
{
_snowflakes[i] = document.createElement("img");
_snowflakes[i].setAttribute("src","snowflake.png"); // setting the image
_snowflakes[i].setAttribute("class", "snowflake"); //setting the css style
_snowflakes[i].style.visibility ="inherit"; //when the function is running snowflakes have to be visible, when it finishes they have to be invisible
_snowflakes[i].style.right = (wid*i)+"px"; //set the distance from the left margin
_snowflakes[i].style.top = "30px"; // set the distance from the top
document.getElementById("background").appendChild(_snowflakes[i]);
}
while(active)
{
move();
}
function move() //function that moves the snowflake
{
for(;;)
{
if(counterY>=600) //when the snowflake reaches 600px from the top the function has to stop and hide snowflakes
{
for(var i=0;i<30;i++)
_snowflakes[i].style.visibility = "hidden";
active=false;
break;
}
else
{
if ((counterY%50)==0)
{
direction=!direction //every 50 Y pixels the snoflake change direction
}
counterY++;
for(var i=0;i<30;i++)
{
_snowflakes[i].style.top = counterY+"px"; //Y movement
if (direction==true)
{
_snowflakes[i].style.right = (_snowflakes[i].offsetLeft+counterX) + "px"; //x right movement
counterX++;
}
else
{
_snowflakes[i].style.right = (_snowflakes[i].offsetLeft+counterX) + "px"; //x left movement
counterX--;
}
}
}
}
}
}

Indeed, your code gets into an infinite loop because your variable counterY is not initialised and so it has the value undefined. Adding one to undefined gives NaN, and so you never get to that break statement.
But more importantly, you need a different approach, because even if you fix this, you'll never see an animation happening. For actually seeing the animation, you need to give the browser time to update the display. So a while and for(;;) loop are out of the question.
Instead call move once, and inside that function, in the else block, call requestAnimationFrame(move). This will give the browser time to repaint before move is called a gain. Remove the for(;;) loop, and remove the break.
There is also a logical error in how you move horizontally. As you read the current offsetLeft it makes no sense to increment counterX as that will lead to increasing (relative) jumps to the right (or left). Instead you should just add (or subtract) 1 to offsetLeft -- you can do away with counterX. Also, don't assign that to style.right, but to style.left.
So everything put together (cf. comments where there is a change):
var direction=true;
var active=true;
function startSnow() {
var _snowflakes = new Array(30);
var wid=50;
// var counterX = 0; -- not used.
var counterY = 0; // Need to initialise!
for (var i=0; i< 30; i++) {
_snowflakes[i] = document.createElement("img");
_snowflakes[i].setAttribute("src", "snowflake.png");
_snowflakes[i].setAttribute("alt", "❄"); // added for when image not found
_snowflakes[i].setAttribute("class", "snowflake");
_snowflakes[i].style.visibility = "inherit";
_snowflakes[i].style.left = (wid*i)+"px"; // assign to style.left
_snowflakes[i].style.top = "30px";
document.getElementById("background").appendChild(_snowflakes[i]);
}
// No while loop. Just call move
move();
function move() {
//No for (;;) loop
if (counterY>=600) {
for (var i=0;i<30;i++) {
_snowflakes[i].style.visibility = "hidden"; // you forgot the underscore
}
active=false;
// No break -- function will just return
} else {
if ((counterY%50)==0) {
direction=!direction
}
counterY++;
for (var i=0;i<30;i++) {
_snowflakes[i].style.top = counterY+"px";
if (direction==true) {
// assign to style.left,
_snowflakes[i].style.left = (_snowflakes[i].offsetLeft+1) + "px"; // add 1
} else {
_snowflakes[i].style.left = (_snowflakes[i].offsetLeft-1) + "px"; // sub 1
}
}
requestAnimationFrame(move); // schedule next run of this function
}
}
}
startSnow(); // Need to start it!
#background { width: 800px; height: 800px }
.snowflake { position: absolute; font-size: 40px }
<div id="background">
</div>

Related

Dom element infinitely incrementing

I'm in my first steps of creating an auto aim system for the enemy bot but I can't even seem to get him to shoot properly.
I create a div element, and move it in a direction. This happens every 4 seconds. I delete the div before the next one gets created. This works. But somehow it creates more and more elements over time and soon turns into a massive amount of divs all flying in the same direction.
** make the bullet here **
function makeBullet() {
if (player.enemy) {
if (player.enemyBullet.bulletInterval == true) {
console.log("working");
player.enemyBullet.bullet = document.createElement('div');
player.enemyBullet.bullet.className = 'bullet';
gameArea.appendChild(player.enemyBullet.bullet);
player.enemyBullet.bullet.x = player.enemy.x;
player.enemyBullet.bullet.y = player.enemy.y;
player.enemyBullet.bullet.style.left = player.enemyBullet.bullet.x + 'px';
player.enemyBullet.bullet.style.top = player.enemyBullet.bullet.y + 'px';
player.enemyBullet.bulletInterval = false;
setInterval(function () {
player.enemyBullet.bulletInterval = true;
}, 4000);
}
}
}
** Move Bullet **
function moveBullet() {
let bullets = document.querySelectorAll('.bullet');
bullets.forEach(function (item) {
item.x += 3;
item.y -= 3;
item.style.left = item.x + 'px';
item.style.top = item.y + 'px';
if(item.y < 200){
item.parentElement.removeChild(item);
player.enemyBullet.bullet = null;
}
})
}
** invoked in request Animation function **
function playGame() {
if (player.inplay) {
moveBomb();
moveBullet();
makeBullet();
window.requestAnimationFrame(playGame);
}
}
** Initiate interval boolean here **
let player = {
enemyBullet: {
bulletInterval: true
}
}
** LINK TO JS FIDDLE FULL PROJECT ** (click here to see what's happening)
https://jsfiddle.net/mugs17/j7f12a0n/
You are using setInterval like set timeout. However setInterval does not only execute one time. Once you've called setInterval the first time, calling it again makes a new different interval that also executes every 4 seconds.
So each time your function gets invoked its creating a new Interval which is why your div elements are increasing as time goes on
Instead wrap the entire create bullet code inside setInterval and make it execute only once by setting a boolean conditional and then immediately changing that conditional to false. Like this:
if (player.enemy) {
if (player.enemyBullet.bulletInterval == true) {
player.enemyBullet.bulletInterval = false;
console.log("working");
setInterval(function () {
player.enemyBullet.bullet = document.createElement('div');
player.enemyBullet.bullet.className = 'bullet';
gameArea.appendChild(player.enemyBullet.bullet);
player.enemyBullet.bullet.x = player.enemy.x;
player.enemyBullet.bullet.y = player.enemy.y;
player.enemyBullet.bullet.style.left = player.enemyBullet.bullet.x + 'px';
player.enemyBullet.bullet.style.top = player.enemyBullet.bullet.y + 'px';
}, 4000);
}
}
}

How to remove CSS animation from element in vanilla JS

I've got a square grid of n x n smaller square div elements that I want to illuminate in a sequence with a CSS background color animation. I have a function to generate a random array for the sequence. The trouble I'm having is that once a certain square has been illuminated once, if it occurs again within the array it won't illuminate a second time. I believe it's because once the element has been assigned the CSS animation, the animation can't trigger again on that element, and I can't figure a way to make it work. It's for a Responsive Web Apps course I'm taking, and the assessment stipulates that we're only to use vanilla JS, and that all elements must be created in JS and appended to a blank <body> in our index.html.
Each flash according to the sequence is triggered through a setTimeout function that loops through all elements in the array increasing it's timer by 1s for each loop (the animation length is 1s also).
Defining containers and child divs:
function createGameContainer(n, width, height) {
var container = document.createElement('div');
//CSS styling
container.style.margin = '50px auto'
container.style.width = width;
container.style.height = height;
container.style.display = 'grid';
// loop generates string to create necessary number of grid columns based on the width of the grid of squares
var columns = '';
for (i = 0; i < n; i++) {
columns += ' calc(' + container.style.width + '/' + n.toString() + ')'
}
container.style.gridTemplateColumns = columns;
container.style.gridRow = 'auto auto';
// gap variable to reduce column and row gap for larger grid sizes
// if n is ever set to less than 2, gap is hardcoded to 20 to avoid taking square root of 0 or a negative value
var gap;
if (n > 1) {
gap = 20/Math.sqrt(n-1);
} else {
gap = 20;
}
container.style.gridColumnGap = gap.toString() + 'px';
container.style.gridRowGap = gap.toString() + 'px';
container.setAttribute('id', 'game-container');
document.body.appendChild(container);
}
/*
function to create individual squares to be appended to parent game container
*/
function createSquare(id) {
var square = document.createElement('div');
//CSS styling
square.style.backgroundColor = '#333';
//square.style.padding = '20px';
square.style.borderRadius = '5px';
square.style.display = 'flex';
square.style.alignItems = 'center';
//set class and square id
square.setAttribute('class', 'square');
square.setAttribute('id', id);
return square;
}
/*
function to create game container and and squares and append squares to parent container
parameter n denotes dimensions of game grid - n x n grid
*/
function createGameWindow(n, width, height) {
window.dimension = n;
createGameContainer(n, width, height);
/*
loop creates n**2 number of squares to fill game container and assigns an id to each square from 0 at the top left square to (n**2)-1 at the bottom right square
*/
for (i = 0; i < n**2; i++) {
var x = createSquare(i);
document.getElementById('game-container').appendChild(x);
}
}
The CSS animation:
#keyframes flash {
0% {
background: #333;
}
50% {
background: orange
}
100% {
background: #333;
}
}
.flashing {
animation: flash 1s;
}
The code to generate the array:
function generateSequence(sequenceLength) {
var sequence = [];
for (i = 0; i < sequenceLength; i++) {
var random = Math.floor(Math.random() * (dimension**2));
// the following loop ensures each element in the sequence is different than the previous element
while (sequence[i-1] == random) {
random = Math.floor(Math.random() * (dimension**2));
}
sequence[i] = random;
};
return sequence;
}
Code to apply animation to square:
function flash(index, delay) {
setTimeout( function() {
flashingSquare = document.getElementById(index);
flashingSquare.style.animation = 'flashOne 1s';
flashingSquare.addEventListener('animationend', function() {
flashingSquare.style.animation = '';
}, delay);
}
I've also tried removing and adding a class again to try and reset the animation:
function flash(index, delay) {
setTimeout( function() {
flashingSquare = document.getElementById(index);
flashingSquare.classList.remove('flashing');
flashingSquare.classList.add('flashing');
}, delay);
}
And the function to generate and display the sequence:
function displaySequence(sequenceLength) {
var sequence = generateSequence(sequenceLength);
i = 0;
while (i < sequence.length) {
index = sequence[i].toString();
flash(index, i*1000);
i++;
}
}
Despite many different attempts and a bunch of research I can't figure a way to get the animations to trigger multiple times on the same element.
Try this one:
function flash(index, delay){
setTimeout( function() {
flashingSquare = document.getElementById(index);
flashingSquare.classList.add('flashing');
flashingSquare.addEventListener('animationend', function() {
flashingSquare.classList.remove('flashing');
}, delay);
});
}
Don't remove the animation, remove the class.
Remove the class direct AFTER the animation is done. So the browser have time to handle everything to do so. And when you add the class direct BEFORE you want the animation, the browser can trigger all needed steps to do so.
Your attempt to remove and add the class was good but to fast. I think the browser and the DOM optimize your steps and do nothing.
After some research, I figured out a work around. I rewrote the function so that the setTimeout was nested within a for loop, and the setTimeout nested within an immediately invoked function expression (which I still don't fully understand, but hey, if it works). The new function looks like this:
/*
function to display game sequence
length can be any integer greater than 1
speed is time between flashes in ms and can presently be set to 1000, 750, 500 and 250.
animation length for each speed is set by a corresponding speed class
in CSS main - .flashing1000 .flashing750 .flashing500 and .flashing250
*/
function displaySequence(length, speed) {
var sequence = generateSequence(length);
console.log(sequence);
for (i = 0; i < sequence.length; i++) {
console.log(sequence[i]);
// immediately invoked function expression
(function(i) {
setTimeout( function () {
var sq = document.getElementById(sequence[i]);
sq.classList.add('flashing' + speed.toString());
sq.addEventListener('animationend', function() {
sq.classList.remove('flashing' + speed.toString());
})
}, (speed * i))
})(i);
}
}
the CSS for each class:
#keyframes flash {
0% {
background: #333;
}
50% {
background: orange
}
100% {
background: #333;
}
}
.flashing1000 {
animation: flash 975ms;
}
.flashing750 {
animation: flash 725ms;
}
.flashing500 {
animation: flash 475ms;
}
.flashing250 {
animation: flash 225ms;
}
A few lazy work arounds, I know, but it works well enough.

"Uncaught TypeError:<function name> is not a function" after it got used once

I have a function, to make a div-Box "jump". The function works the first time, but after that I get the error "Uncaught TypeError: jump is not a function" after it gets used once. Can someone please explain why it doesn't work?
already = false;
function jump() {
if (already == false) { //So he can't jump 2 times in a row
try {
clearInterval(t); //<--this is my gravity function, where the div-Box falls down until it hits solid ground
} catch (err) {
console.log("not activated");
}
jump = setInterval("jump2();", 200);
already = true;
}
}
}
anz = 0;
function jump2() {
//Getting coordinates of div-Boy(they work)
var step = 10;
var bottom = getBottom("itBoy");
var right = getRight("itBoy");
var top = getTop("itBoy");
var left = getLeft("itBoy");
//lets see if he hits an object
if (anz <= 100) { //<-- anz = so he cant jump higher than 100 px
if (top - step >= 0) {
var a = hittest("itBoy", "up", 10); //if div wont hit a solid object --> return -1 | else return coordinates of bordes which collide (this function works too)
if (a == -1) {
anz += step;
document.getElementById("itBoy").style.top = (top -= step) + "px";
} else {
document.getElementById(itBoy).style.top = a + "px";
clearInterval(jump); // div stops moving upwards
t = setInterval("move('down');", 50); //gravity gets Activated again
}
}
} else {
clearInterval(jump);
t = setInterval("move('down');", 50);
}
}
It's because, you're overriding the jump:
function jump(){
// ...
jump = setInterval("jump2();",200);
// ^^ give it a different name
Also, a good approach to use function inside setInterval like:
setInterval(jump2, 200); // faster

How to change the img of a cloned div onClick

Right now I have a lovely code (that was initially for flowing snowflakes) that lets cloned divs fall from the top of the window, to the bottom and repeat. The thing is that I want the content to change on click. However, this is not yet working.
I know how to change content of a div without it being cloned, but I have serious trouble figuring out how to do the same with one div being cloned within the code.
Anyone have any tips what I should do, even a hint in which direction to go. I'm clueless.
This is the fiddle:
http://jsfiddle.net/4yaxvt7h/
the javascript:
function changeImage(){
document.getElementById('toChange').src='https://i.pinimg.com/originals/6e/19/56/6e195649034f042d1dea5230234570a8.gif';
}
// Array to store our Snowflake objects
var snowflakes = [];
// Global variables to store our browser's window size
var browserWidth;
var browserHeight;
// Specify the number of snowflakes you want visible
var numberOfSnowflakes = 15;
// Flag to reset the position of the snowflakes
var resetPosition = false;
// Handle accessibility
var enableAnimations = false;
var reduceMotionQuery = matchMedia("(prefers-reduced-motion)");
// Handle animation accessibility preferences
function setAccessibilityState() {
if (reduceMotionQuery.matches) {
enableAnimations = false;
} else {
enableAnimations = true;
}
}
setAccessibilityState();
reduceMotionQuery.addListener(setAccessibilityState);
//
// It all starts here...
//
function setup() {
if (enableAnimations) {
window.addEventListener("DOMContentLoaded", generateSnowflakes, false);
window.addEventListener("resize", setResetFlag, false);
}
}
setup();
//
// Constructor for our Snowflake object
//
function Snowflake(element, speed, xPos, yPos) {
// set initial snowflake properties
this.element = element;
this.speed = speed;
this.xPos = xPos;
this.yPos = yPos;
this.scale = 1;
// declare variables used for snowflake's motion
this.counter = 0;
this.sign = Math.random() < 0.5 ? 1 : -1;
// setting an initial opacity and size for our snowflake
this.element.style.opacity = 1;
}
//
// The function responsible for actually moving our snowflake
//
Snowflake.prototype.update = function () {
// using some trigonometry to determine our x and y position
this.counter += this.speed / 5000;
this.xPos += this.sign * this.speed * Math.cos(this.counter) / 40;
this.yPos += Math.sin(this.counter) / 40 + this.speed / 30;
this.scale = .5 + Math.abs(10 * Math.cos(this.counter) / 20);
// setting our snowflake's position
setTransform(Math.round(this.xPos), Math.round(this.yPos), this.scale, this.element);
// if snowflake goes below the browser window, move it back to the top
if (this.yPos > browserHeight) {
this.yPos = -50;
}
}
//
// A performant way to set your snowflake's position and size
//
function setTransform(xPos, yPos, scale, el) {
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0) scale(${scale}, ${scale})`;
}
//
// The function responsible for creating the snowflake
//
function generateSnowflakes() {
// get our snowflake element from the DOM and store it
var originalSnowflake = document.querySelector(".snowflake");
// access our snowflake element's parent container
var snowflakeContainer = originalSnowflake.parentNode;
snowflakeContainer.style.display = "block";
// get our browser's size
browserWidth = document.documentElement.clientWidth;
browserHeight = document.documentElement.clientHeight;
// create each individual snowflake
for (var i = 0; i < numberOfSnowflakes; i++) {
// clone our original snowflake and add it to snowflakeContainer
var snowflakeClone = originalSnowflake.cloneNode(true);
snowflakeContainer.appendChild(snowflakeClone);
// set our snowflake's initial position and related properties
var initialXPos = getPosition(50, browserWidth);
var initialYPos = getPosition(50, browserHeight);
var speed = 5 + Math.random() * 40;
// create our Snowflake object
var snowflakeObject = new Snowflake(snowflakeClone,
speed,
initialXPos,
initialYPos);
snowflakes.push(snowflakeObject);
}
// remove the original snowflake because we no longer need it visible
snowflakeContainer.removeChild(originalSnowflake);
moveSnowflakes();
}
//
// Responsible for moving each snowflake by calling its update function
//
function moveSnowflakes() {
if (enableAnimations) {
for (var i = 0; i < snowflakes.length; i++) {
var snowflake = snowflakes[i];
snowflake.update();
}
}
// Reset the position of all the snowflakes to a new value
if (resetPosition) {
browserWidth = document.documentElement.clientWidth;
browserHeight = document.documentElement.clientHeight;
for (var i = 0; i < snowflakes.length; i++) {
var snowflake = snowflakes[i];
snowflake.xPos = getPosition(50, browserWidth);
snowflake.yPos = getPosition(50, browserHeight);
}
resetPosition = false;
}
requestAnimationFrame(moveSnowflakes);
}
//
// This function returns a number between (maximum - offset) and (maximum + offset)
//
function getPosition(offset, size) {
return Math.round(-1 * offset + Math.random() * (size + 2 * offset));
}
//
// Trigger a reset of all the snowflakes' positions
//
function setResetFlag(e) {
resetPosition = true;
}
html
<div id="snowflakeContainer">
<div class="snowflake"><img src="element1.png"
</div>
css
body{
background-color: black;
}
#snowflakeContainer {
position: absolute;
left: 0px;
top: 0px;
display: none;
}
.snowflake {
position: fixed;
background-color: red;
user-select: none;
z-index: 1000;
pointer-events: none;
border-radius: 50%;
width: 200px;
height: 200px;
}
img{
max-width: 100%;
}
any tips are super welcome!!
You have several different problems here. First, your CSS sets pointer-events: none; for all .snowflake elements. This will stop any clicks from triggering, so you have to remove it if you want mouse interactivity.
Second, your changeImage() function uses document.getElementById('toChange') to get the element to change the source of. No such element exists and even if it did, that would mean that clicking on any of the snowflakes would just change the source of that one image. You need to reference the snowflake that you clicked on. An easy way to do this is by passing this as an argument to the function in the onclick attribute:
HTML:
<div class="snowflake"><img src="https://vignette.wikia.nocookie.net/dragonballfanon/images/7/70/Random.png/revision/latest?cb=20161221030547" onclick="changeImage(this)"></div>
JS:
function changeImage(img) {
img.src = 'https://i.pinimg.com/originals/6e/19/56/6e195649034f042d1dea5230234570a8.gif';
}
Finally, your JSFiddle is configured to delay execution of the JavaScript until after the DOM is ready. This means that the code will be wrapped in a callback function, and therefore the changeImage() function will no longer be in the global scope. This makes it inaccessible to the elements that want to call it in their handlers (the snowflakes). Because you are already listening for the DOMContentLoaded event on your own, you can just change the code to execute normally and it should work. Click on the dropdown that says "JavaScript + jQuery 3.2.1" above your JS code and change the load type to any "No wrap" option and it should work.

Javascript "For" Loop not working?

I have a Javascript file that I am using to try to animate a dropdown menu. I have the "Toggle" function in that file set to run when I click on a certain div. Here's the script I'm using:
var up = true;
function Toggle(x)
{
if (up)
{
for (var i = x.offsetTop; i <= 0; i++)
{
x.style.top = i;
if (i == 0)
{
up = false;
}
}
}
else if (up == false)
{
for (var i = x.offsetTop; i >= -50; i--)
{
x.style.top = i;
if (i == -50)
{
up = true;
}
}
}
}
In the HTML div I want to animate, I have the "onclick" property set to "onclick=Toggle(this)". The first for loop works as it should (it sets the div's top offset to 0). However, the second for loop doesn't set the offsetTop. I know that the for loop is activating because I've tested it and it gives me every integer between 0 and -50. Why isn't it setting the offset position?
1) You must specify a unit to the top ie: x.style.top = i +"px"
2) Your function won't animate instead of you use a setInterval or a setTimeout
Like you asked, an example. I wouldn't do it like this for one of my project, but i kept your function to make you more familiar with the code.
I Used setTimeout instead of setInterval because setInterval must be cleared when not needed and setTimeout is just launched one time :
var Toggle = (function() { // use scope to define up/down
var up = true;
return function(element) {
var top = parseInt(element.style.top, 10); // element.offsetTop ?
if ( !top ) {
top = 0;
}
if (up) {
if (element.offsetTop < 0) { // if we are not at 0 and want to get up
element.style.top = (top+1) + "px";
setTimeout(function() { Toggle(element); }, 10); // recall the function in 10 ms
} else { // we change up value
up = !up;
}
}
else {
if (element.offsetTop > -50) {
element.style.top = (top-1) + "px";
setTimeout(function() { Toggle(element); }, 10); // recall the function in 10 ms
} else {
up=!up;
}
}
}
})();
You'd have to use x.style.top = i + 'px' as top and similar css properties must define the type (px, em, %, etc.) unless they are 0, as this is 0 in any case.
But your script would actually snap the div directly to -50px, as you do not wait between those iteration steps.
I'd recommend to use a library like jQuery to use it's animate() method.
function Toggle(obj) {
$(obj).animate({
top: parseInt($(obj).css('top')) === 0 ? '-50px' : '0px'
})
}

Categories

Resources