Animating array of divs; only the final element is modified - javascript

This is baffling me slightly at the moment.
I'm a fairly experienced programmer who is looking to throw a bit of a surprise for my fiancee on the countdown page for our wedding. The desired effect essentially simulated confetti falling behind the main page. My setup is as follows:
I have an array of divs in javascript. Each div is absolutely positioned on the page. I have an interval set that updates every 50 or so milliseconds. On each tick, I run a for loop over my array of divs, and do calculations and update their positions.
Problem
The problem I am having, however, is that even though I can clearly see that all divs are created, stored into the array, and modified (in this case, each has a slight randomized rotation) during the initialization phase, once the tick to update their position starts, only the div stored in whatever the last slot of the array is gets its position updated. I even tried hard-coding each div by index for testing purposes, but it is strictly the last element that will actually update its position. Even upon printing the current "left" and "top" style values for each div before and after the calculations shows that the value has been changed, but no visible change is noticeable. Here's my basic code:
Javascript
var container; // the div that will have all the other divs attached to it
var conf = new Array(); // the array of divs to modify
var rots = new Array(); // the rotation of each div
var vels = new Array(); // the velocity of each div
var xPos = new Array(); // the current x value of position in pixels
var yPos = new Array(); // the current y value of position in pixels
var xVels = new Array(); // the x portion of that velocity
var yVels = new Array(); // the y portion of that velocity
function initialize() // this is attached to the onload event on my <body> tag
{
container = document.getElementById("conf_container");
confettiInit(); // creates, initializes, and displays each div
setInterval(updateConfetti, 42);
}
function confettiInit()
{
screenHeight = window.innerHeight;
screenWidth = window.innerWidth;
for (var i = 0; i < 25; i ++) // create 25 confetti flakes
{
// append a div in the container with a unique ID
container.innerHTML += "<div class='conf' id='conf_" + i + "'></div>";
// store the element in an array
conf[i] = document.getElementById("conf_" + i);
// ensure confetti is in the background, and each z index is unique
conf[i].style.zIndex = -i - 1;
xPos[i] = window.innerWidth * Math.random(); // calculate random x position
conf[i].style.left = xPos[i] + "px"; // set x position of div
yPos[i] = -40; // y position above screen
conf[i].style.top = yPos[i] + "px"; // set y position of div
// calculate the random amount of rotation (-30 to 30 degrees)
rots[i] = Math.random() * 60*(Math.PI/180) - 30*(Math.PI/180);
// set rotation of div
conf[i].style.webkitTransform = "rotate(" + -rots[i] + "rad)";
vels[i] = Math.random() * 3 + 2; // calculate random velocity (2-5)
xVels[i] = vels[i] * Math.sin(rots[i]); // get x portion of velocity
yVels[i] = vels[i] * Math.cos(rots[i]); // get y portion of velocity
}
}
function updateConfetti()
{
for (var i = 0; i < 25; i ++)
{
xPos[i] += xVels[i]; // calculate new x position
yPos[i] += yVels[i]; // calculate new y position
conf[i].style.left = xPos[i] + "px"; // set div's x position
conf[i].style.top = yPos[i] + "px"; // set div's y position
// if the confetti piece leaves the viewing area...
if (xPos[i] < -50 ||
xPos[i] > window.screenWidth + 10 ||
yPos[i] > window.screenHeight + 10)
{
// send it back to the top and reinitialize it
xPos[i] = window.innerWidth * Math.random();
conf[i].style.left = xPos[i] + "px";
yPos[i] = -40;
conf[i].style.top = yPos[i] + "px";
rots[i] = Math.random() * 60*(Math.PI/180) - 30*(Math.PI/180);
conf[i].style.webkitTransform = "rotate(" + -rots[i] + "rad)";
vels[i] = Math.random() * 3 + 2;
xVels[i] = vels[i] * Math.sin(rots[i]);
yVels[i] = vels[i] * Math.cos(rots[i]);
}
}
}
CSS
div.conf
{
height: 29px;
width: 50px;
background-image: url("images/ConfettiYellow.gif");
position: absolute;
}
#conf_container
{
left: 0px;
top: 0px;
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
}

It's something wrong with your divs creation. It start works if you do it in conventional way, like this:
var d = document.createElement("div");
d.className = "conf";
container.appendChild(d);
conf[i] = d;

Related

How to avoid overlapping of random numbers?

On Clicking button Click Me! How to avoid overlapping of random numbers?
It will generate random numbers but there are some numbers which are overlapping how to avoid this... , Here I am not talking about duplicacy of random numbers , I want to avoid overlapping of random numbers....
const circle = document.querySelector(".circle");
const addNumBtn = document.querySelector('#addNumBtn')
function randomValue() {
let random = Math.floor(Math.random() * 50) + 1;
return random;
}
function randomColor(){
let r = Math.floor(Math.random() * 255) + 1;
let g = Math.floor(Math.random() * 255) + 1;
let b = Math.floor(Math.random() * 255) + 1;
return `rgba(${r},${g},${b})`
}
function randomSize(){
let size = Math.floor(Math.random() * 30) + 8;
return `${size}px`
}
function randNum() {
let randnum = document.createElement("p");
randnum.classList.add('numStyle')
randnum.style.color=randomColor();
randnum.style.fontSize=randomSize();
randnum.textContent = randomValue();
randnum.style.position = 'absolute'
randnum.style.top = `${randomValue()}px`
randnum.style.left = `${randomValue()}px`
randnum.style.bottom = `${randomValue()}px`
randnum.style.right = `${randomValue()}px`
circle.append(randnum);
}
addNumBtn.addEventListener('click',randNum)
.circle {
aspect-ratio: 1;
width: 300px;
background-color: black;
border-radius: 50%;
position: relative;
overflow:hidden;
}
#addNumBtn{
position:absolute;
top:0;
left:0;
}
.numStyle{
font-family:"roboto",sans-serif;
}
//On Clicking button Click Me! I want to place all the random numbers inside the circle
//together not one by one, at a time all numbers should be placed inside the circle by
//clicking
the button Click Me!
<div id="container"></div>
<div class="circle"></div>
</div>
<button id="addNumBtn">Click Me!</button>
You need to loop over the random number generator for the amount of times you wish to display the created number element in the circle. You can create and return an array of values in randomValue() function, then loop over that array in randNum() function.
Also, I assume you want the output of the appended element to spread out over the entirety of the circle element yes? If so, you need to randomize the position of the element relative to the size of the circle, not the randomValue functions returned value.
EDIT:
OP asked after posting answer: There are some numbers at circles border side which are not properly visible how to solve that?
Because of the box model of elements, the border of the circle element is square, not round though the viewable content of the circle element is round due to its border-radius css.
You can use a helper function that will determine if the randomly generated numbers for x and y, or left and top, are within the border-radius of the circle element using the size of the radius and the center point of the x and y coords of the circle.
Credit for helper function: Simon Sarris:
function pointInCircle(x, y, cx, cy, rad) {
const distancesquared = (x - cx) * (x - cx) + (y - cy) * (y - cy);
return distancesquared <= rad * rad;
}
We wrap everything inside a while loop that checks if a counter has reached 50. Inside the loop we run the function that creates the randomly generated positions -> randomPos(). Then we run a conditional that will check to see if the randomly generated points are within the circle using the function pointInCircle(x, y, cx, cy, rad). If this conditional returns true we create the number elements, style and append the number elements to the circle and iterate our counter value by one.
const circle = document.querySelector(".circle");
const addNumBtn = document.querySelector('#addNumBtn');
// we set the width of the circle element in JS and pass
// it to CSS using a css variable and the root element
const circleWidth = 350;
document.documentElement.style.setProperty('--circleWidth', `${circleWidth}px`);
// NOTE: '~~' -> is quicker form of writing Math.floor()
// runs faster on most browsers.
function randomPos(min, max) {
return ~~(Math.random() * (max - min + 1) + min)
}
// return a random value between 1 and 50
function randomValue(){
return ~~(Math.random() * 50) + 1;
}
function randomColor() {
let r = ~~(Math.random() * 255) + 1;
let g = ~~(Math.random() * 255) + 1;
let b = ~~(Math.random() * 255) + 1;
return `rgba(${r},${g},${b})`
}
function randomSize() {
let size = ~~(Math.random() * 30) + 8;
return `${size}px`
}
// add a helper function to find the center of the
// circle and calculate distance to the edge of circle
// x and y are the randomly generated points
// cx, cy are center of circle x and y repsectively
// rad is circle radius
function pointInCircle(x, y, cx, cy, rad) {
const distancesquared = (x - cx) * (x - cx) + (y - cy) * (y - cy);
return distancesquared <= rad * rad;
}
// you can remove valArray array if you do not want the 50 numbers to not be repeated
const valArray = [];
function randNum() {
// add a counter
let count = 1;
// while loop to check if the counter has reached the target 50
while (count <= 50) {
// set the randomly generated val
let val = randomValue();
// random size = string with px added
let randSize = randomSize();
// random size = the exact number => typeof number
let posSize = randSize.split('px')[0];
// an array to hold our randomly positioned number values
// uses helper function randonPos
// pass in the 1 and the circle width minus the font size
let ran = [randomPos(1, circleWidth - posSize), randomPos(1, circleWidth - posSize)];
// conditional to check bounds of circle using helper function
// we pass in the ran array, center points and radius
// we add buffers to accommodate for the font size
// you can remove !valArray.includes(val) if you do not want the 50 numbers to not be repeated
if (pointInCircle(ran[0], ran[1], circleWidth/2-posSize, circleWidth/2-posSize, circleWidth / 2 - posSize) && !valArray.includes(val)) {
// you can remove valArray.push(val); if you do not want the 50 numbers to not be repeated
valArray.push(val);
// if the conditional returns true,
// create the element, style and append
let randnum = document.createElement("p");
randnum.classList.add('numStyle');
randnum.style.color = randomColor();
randnum.style.fontSize = randSize;
randnum.textContent = val;
randnum.style.position = 'absolute';
randnum.style.top = `${ran[0]}px`;
randnum.style.left = `${ran[1]}px`;
circle.append(randnum);
// iterate the counter
count++;
}
}
}
addNumBtn.addEventListener('click', randNum)
html {
--circle-width: 300px;
}
.circle {
aspect-ratio: 1;
width: var(--circleWidth);
background-color: black;
border-radius: 50%;
position: relative;
overflow: hidden;
padding: .5rem;
}
#addNumBtn {
position: absolute;
top: 0;
left: 0;
}
.numStyle {
font-family: "roboto", sans-serif;
}
//On Clicking button Click Me! I want to place all the random numbers inside the circle //together not one by one, at a time all numbers should be placed inside the circle by //clicking the button Click Me!
<div id="container"></div>
<div class="circle">
</div>
<button id="addNumBtn">Click Me!</button>

How can I make an image fall off the screen?

I have this code to allow me to place a random image next to the user's cursor using javascript and styled using CSS. I would like images to fall down off the page after a few seconds, my first thought was to animate position but apparently that's not possible?
How could I achieve this? Here is my javascript and CSS code
Javascript
<script>
var myPix = new Array("/img/portfolio/30day.jpg", "/img/portfolio/animationposter.jpg","/img/portfolio/beetle.jpg","/img/portfolio/board.jpg","/img/portfolio/cyanotype.jpg","/img/portfolio/dissent.jpg")
document.addEventListener("click", showCoords);
function showCoords(event)
{
var randomNum = Math.floor(Math.random() * myPix.length);
var yourImage = document.createElement("img");
yourImage.src = myPix[randomNum] ;
yourImage.classList.add("mystyle");
yourImage.style.cssText = " width:360px;height:auto;position:fixed;top:" + event.clientY + "px;left:" + event.clientX + "px;";
document.body.appendChild(yourImage);
}
jQuery.fn.reverse = [].reverse;
</script>
CSS
.mystyle {
border-radius: 20px;
box-shadow: 0px 0px 10px 0 rgba(0, 0, 0, 0.1);
z-index: -2;
width: 360px;
height: auto;
position: fixed;
}
First create an images array so you can easily access all your images. Whenever you create an image, push it to the array:
var images = [];
function showCoords(event)
{
var randomNum = Math.floor(Math.random() * myPix.length);
var yourImage = document.createElement("img");
yourImage.src = myPix[randomNum] ;
yourImage.classList.add("mystyle");
yourImage.style.cssText = " width:360px;height:auto;position:fixed;top:" + event.clientY + "px;left:" + event.clientX + "px;";
images.push([yourImage,0,0]); // this line is where we add the image.
//In the same sub-array, put a number, 0, to store the image's age, and a velocity, 0, to make the physics look good. These will be used later.
document.body.appendChild(yourImage);
}
In order to animate your images, you need to set up some kind of animate loop and call it in a setInterval:
animate = function(){}
setInterval(animate,5); // choose whatever interval you want. Here, it is called every 5 milliseconds
Inside the animate function, we need to add the logic to change every image's position:
animate = function(){
for(image of images){ // loop over all elements of our array
image[1] += 1; //increase the age
if(image[1] > 400){ //if old enough, fall
image[2] += 0.1; //accelerate, tweak this value to change how strong gravity is
currentTop = parseFloat(image[0].style.top.replace("px","")); // get the current y position
currentTop += image[2]; //move
newTop = String(currentTop) + "px"; //change back to string
image[0].style.top = newTop; //set the attribute
if(newTop > document.documentElement.clientHeight){ //if off-screen, remove it
document.body.removeChild(image[0]);
images.splice(images.indexOf(image),1); //remove from array
}
}
}
}
Now you should be good to go. I tested this out in Chrome and it worked for the simple case where I just had one image on screen; hopefully I haven't made any typos writing it up here. To change the speed, either change the acceleration value or change the time in the setInterval. Hope this helps!
Edit: here is a working jsFiddle. I had to use spans instead of images because I don't have your exact image files, but everything else is the same.

I am having trouble placing a div in a random position

I am trying to randomly place a div, but I am getting a type error. It says the element is undefined, but I thought that I was defining it with the const line of line 1. In my CSS file, the position of the div is absolute and there's a background image set. I can tell in the console.log that I am getting the random numbers that I want. I am just having a little trouble attaching those coordinates to the div.
const animal = document.querySelectorAll('.animal');
function getRandomPos(animal) {
var x = screen.width;
var y = screen.height;
var randomX = Math.floor(Math.random() * x);
var randomY = Math.floor(Math.random() * y);
console.log(randomX, randomY);
let rect = animal.getBoundingClientRect();
console.log(rect.top, rect.right, rect.bottom, rect.left);
animal.style.left = randomX;
animal.style.top = randomY;
}
One major issue was already addressed in the comments - querySelectorAll returns an HTMLCollection rather than a single element, so you'll need querySelector.
Also, you'll need to add "px" units to the lines where you're setting the element's style (see below).
UPDATE
Questioner mentioned having multiple randomly moving elements, so I've added an example that includes how that might be done using IDs as an argument to the setRandomPos function.
function setRandomPos(target) {
const element = document.getElementById(target);
// var x = screen.width;
var x = 400; // Just smaller numbers for the example
// var y = screen.height;
var y = 200;
var randomX = Math.floor(Math.random() * x);
var randomY = Math.floor(Math.random() * y);
// Remember to add px for units
element.style.left = randomX + 'px';
element.style.top = randomY + 'px';
}
.randMove {
position: fixed;
}
button {
font-size: 20px;
z-index: 1;
position: relative;
}
<div id="animal" class="randMove">
<img src="https://upload.wikimedia.org/wikipedia/en/thumb/e/e7/Animal_%28Muppet%29.jpg/220px-Animal_%28Muppet%29.jpg" alt="Animal">
</div>
<div id="grover" class="randMove">
<img src="https://vignette.wikia.nocookie.net/muppet/images/f/f7/GroverFullFigure2.png/revision/latest?cb=20190222025220" alt="Grover">
</div>
<button onClick="setRandomPos('animal')">Move Animal</button>
<button onClick="setRandomPos('grover')">Move Grover</button>

Javascript elements only snapping to grid from right side

I am trying to resize elements so that they snap to a grid and so far it is working if I resize from the right side and bottom but for some reason it doesn't work from the left side. I am sure it's just my math but here is the code:
var dimension = win.getDimension();
var newW = Math.round(dimension[0] / 10) * 10;
var newH = Math.round(dimension[1] / 10) * 10;
win.setDimension(newW, newH);
Any ideas?
So what I ended up doing was having an event called onBeforeResizeStart which saved the current x position for the window and in the onResizeFinish event I checked to see if the new x position matched the saved x position. If it did not then I just updated the x position and did the same snap calculation.
onBeforeResizeStart:
var position = getCoords(win);
window.CurrentX = position.left;
onResizeFinish
var position = getCoords(win);
if(position.left != window.currentX)
{
var newX = Math.round(position.left / 10) * 10;
win.setPosition(newX, position.top);
}
var dimension = win.getDimension();
var newW = Math.round(dimension[0] / 10) * 10;
var newH = Math.round(dimension[1] / 10) * 10;
win.setDimension(newW, newH);

Water ripple - The image cannot load on first time

I follow the instructions to create water ripple effects, I replaced the background by my image. The effect run pretty good. But it have a problem. When load it first time, the browser cannot load my background (my image), you have to reload browser (F5) or enter again on address bar to see the effect. How to solve it?
My Code:
<!DOCTYPE html>
<html>
<head>
<title>Water Ripple HTML5</title>
<meta name="author" content="Brent Dingle">
<meta name="description" content="HTML5 canvas example of water ripple effect">
<style >
.waterCanvasStyle
{
border-width: 1px;
border-style: solid;
border-color:#a1a1d0;
border-radius: 8px;
box-shadow: #c6c6d0 4px 4px 10px;
}
</style>
</head>
<body>
<canvas id="waterCanvas0" width="400" height="400" >
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
var canvas = document.getElementById('waterCanvas0');
var ctx = canvas.getContext('2d');
var width = canvas.width;
var height = canvas.height;
var halfWidth = width >> 1;
var halfHeight = height >> 1;
var size = width * (height + 2) * 2; // space for 2 images (old and new), +2 to cover ripple radius <= 3
var delay = 30; // delay is desired FPS
var oldIdx = width;
var newIdx = width * (height + 3); // +2 from above size calc +1 more to get to 2nd image
var rippleRad = 3;
var rippleMap = [];
var lastMap = [];
var mapIdx;
// texture and ripple will hold the image data to be displayed
var ripple;
var texture;
// Any image can be used, but we will create a simple pattern instead
// So need some variables to create the background/underwater image
var stripeWidth = 25;
var step = stripeWidth * 2;
var count = height / stripeWidth;
canvas.width = width;
canvas.height = height;
var img = new Image();
img.src = "sea.jpg";
// Here is a neat trick so you don't have to type ctx.blah over and over again
with (ctx)
{
drawImage(img,0,0);
save();
restore();
}
// Initialize the texture and ripple image data
// Texture will never be changed
// Ripple is what will be altered and displayed --> see run() function
texture = ctx.getImageData(0, 0, width, height);
ripple = ctx.getImageData(0, 0, width, height);
// Initialize the maps
for (var i = 0; i < size; i++)
{
lastMap[i] = 0;
rippleMap[i] = 0;
}
// -------------------------------------------------------
// --------------------- Main Run Loop --------------
// -------------------------------------------------------
function run()
{
newframe();
ctx.putImageData(ripple, 0, 0);
}
// -------------------------------------------------------
// Drop something in the water at location: dx, dy
// -------------------------------------------------------
function dropAt(dx, dy)
{
// Make certain dx and dy are integers
// Shifting left 0 is slightly faster than parseInt and math.* (or used to be)
dx <<= 0;
dy <<= 0;
// Our ripple effect area is actually a square, not a circle
for (var j = dy - rippleRad; j < dy + rippleRad; j++)
{
for (var k = dx - rippleRad; k < dx + rippleRad; k++)
{
rippleMap[oldIdx + (j * width) + k] += 512;
}
}
}
// -------------------------------------------------------
// Create the next frame of the ripple effect
// -------------------------------------------------------
function newframe()
{
var i;
var a, b;
var data, oldData;
var curPixel, newPixel;
// Store indexes - old and new may be misleading/confusing
// - current and next is slightly more accurate
// - previous and current may also help in thinking
i = oldIdx;
oldIdx = newIdx;
newIdx = i;
// Initialize the looping values - each will be incremented
i = 0;
mapIdx = oldIdx;
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
// Use rippleMap to set data value, mapIdx = oldIdx
// Use averaged values of pixels: above, below, left and right of current
data = (
rippleMap[mapIdx - width] +
rippleMap[mapIdx + width] +
rippleMap[mapIdx - 1] +
rippleMap[mapIdx + 1]) >> 1; // right shift 1 is same as divide by 2
// Subtract 'previous' value (we are about to overwrite rippleMap[newIdx+i])
data -= rippleMap[newIdx + i];
// Reduce value more -- for damping
// data = data - (data / 32)
data -= data >> 5;
// Set new value
rippleMap[newIdx + i] = data;
// If data = 0 then water is flat/still,
// If data > 0 then water has a wave
data = 1024 - data;
oldData = lastMap[i];
lastMap[i] = data;
if (oldData != data) // if no change no need to alter image
{
// Recall using "<< 0" forces integer value
// Calculate pixel offsets
a = (((x - halfWidth) * data / 1024) << 0) + halfWidth;
b = (((y - halfHeight) * data / 1024) << 0) + halfHeight;
// Don't go outside the image (i.e. boundary check)
if (a >= width) a = width - 1;
if (a < 0) a = 0;
if (b >= height) b = height - 1;
if (b < 0) b = 0;
// Set indexes
newPixel = (a + (b * width)) * 4;
curPixel = i * 4;
// Apply values
ripple.data[curPixel] = texture.data[newPixel];
ripple.data[curPixel + 1] = texture.data[newPixel + 1];
ripple.data[curPixel + 2] = texture.data[newPixel + 2];
}
mapIdx++;
i++;
}
}
}
// -------------------------------------------------------
// Select random location to create drops
// So if user is doing nothing, water still
// gets ripples.
// -------------------------------------------------------
function randomDrop()
{
// Make it a little, irregular in timing
if ( Math.random() > 0.3 )
{
dropAt(Math.random() * width, Math.random() * height);
}
}
// -------------------------------------------------------
// Event handler for mouse motion
// -------------------------------------------------------
canvas.onmousemove = function(/* Event */ evt)
{
dropAt(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY);
}
// -------------------------------------------------------
// Begin our infinite loop
// For user interaction and display updates
// -------------------------------------------------------
setInterval(run, delay);
// -------------------------------------------------------
// Create random ripples
// Note: this is NOT at same rate as display refresh
// -------------------------------------------------------
setInterval(randomDrop, 1250);
</script>
</body>
A common error when this type of issue occurs, is that you are trying to draw the image before it loads. Try utilizing the img.onload function to wait until the image has loaded to do all of the drawing and then to start running the main loop.

Categories