Animated Clouds - Part 2

In Part 1 we covered how to set up animated clouds using CSS animations and variables.

In Part 2 we will cover how you can use JavaScript to programmatically spawn clouds at random intervals with randomised positions, speeds, sizes and depths.

Part 2: Improving with JavaScript permalink#

  1. Adding a cloud programmatically
  2. Spawning clouds at a regular interval
  3. Spawning clouds at varying intervals
  4. Randomising cloud positions
  5. Randomising cloud speed, size and depth
  6. Adding more cloud images
  7. Potential improvements

All examples from the steps below are available on CodePen.


1. Adding a cloud programmatically permalink#

Before you get started you might want to familiarise yourself with everything covered in Part 1.


The following HTML sets up an empty sky where we will programmatically add a cloud. Ensure that you add id="sky" so we can access the sky element from our code.

HTML
<div class="sky" id="sky"></div>

The following CSS is all the styling required and is the result from Part 1. Make sure to replace the cloud background-image with your chosen image.

CSS
.sky {
background: #95d2ec;
overflow: hidden;
position: relative;
width: 100%;
height: 150px;
}

.cloud {
--depth: 1;
--scale: 1;
--speed: 1;

animation-name: move;
animation-duration: calc(60s / (var(--speed) * var(--depth)));
animation-timing-function: linear;
animation-iteration-count: infinite;

background-image: url(/cloud.png);
background-size: contain;
background-repeat: no-repeat;
background-position: center;

position: absolute;
transform: translateY(-50%);

opacity: var(--depth);

width: calc(50px * var(--scale) * var(--depth));
height: calc(50px * var(--scale) * var(--depth));
z-index: var(--depth);
}

@keyframes move {
0% {
margin-left: calc(-50px * var(--scale) * var(--depth));
}
100% {
margin-left: 100%;
}
}

We will start off with this first piece of JavaScript which will create a cloud element and add it to the sky.

JS
// Get the Sky element
const sky = document.getElementById('sky');

// Create and set up the cloud
const cloud = document.createElement('div');
cloud.setAttribute('class', 'cloud');
// Vertically center the cloud
cloud.style.top = '50%';

// Add the cloud to the sky
sky.appendChild(cloud);

2. Spawning clouds at a regular interval permalink#

Next we need to start spawning clouds regularly. We will do this by creating a function to add a cloud and call that at specified intervals using the setInterval method. We will also remove each cloud when its animation ends otherwise we would see a build up of clouds.


We need to update the CSS for the cloud, changing animation-iteration-count to 1. This means the animation has an end which we will use as a trigger for removing the cloud.

CSS
.cloud {
...
animation-iteration-count: 1;
}

We wrap our code which creates a cloud inside a function called addCloud which can then be called every eight seconds using setInterval. We also add an animationend event listener to every cloud we create so we can remove the cloud from the sky when it has reached the end.

JS
const sky = document.getElementById('sky');

// Function creates and adds a cloud to the sky
const addCloud = () => {
const cloud = document.createElement('div');
cloud.setAttribute('class', 'cloud');
cloud.style.top = '50%';

sky.appendChild(cloud);

// Remove the cloud when it reaches the other end of the sky
cloud.addEventListener('animationend', () => {
cloud.remove();
});
};

// Add the first cloud immediately
addCloud();
// Add a cloud every eight seconds
setInterval(addCloud, 8000);

3. Spawning clouds at varying intervals permalink#

Let's add a bit of variation to the time between cloud spawning.


Here we create a setRandomInterval function which takes a function which will be called at random intervals between the min and max seconds values provided. We then call setRandomInterval, passing addCloud and the min and max time to wait. Now we have clouds spawning every five to ten seconds instead of every eight seconds.

JS
const sky = document.getElementById('sky');

const addCloud = () => {
const cloud = document.createElement('div');
cloud.setAttribute('class', 'cloud');
cloud.style.top = '50%';

sky.appendChild(cloud);

cloud.addEventListener('animationend', () => {
cloud.remove();
});
};

// Calls a function at random intervals between the min and max
const setRandomInterval = (fn, min, max) => {
const range = 1 + max - min;
const seconds = Math.floor(Math.random() * range + min);
const milliseconds = seconds * 1000;
setTimeout(() => {
fn();
setRandomInterval(fn, min, max);
}, milliseconds);
};

// Add the first cloud immediately
addCloud();
// Add a cloud every five to ten seconds
setRandomInterval(addCloud, 5, 10);

4. Randomising cloud positions permalink#

Now let's randomise the vertical position of the clouds so it starts to look more like a real sky.


Here we update the element style of the created cloud, setting the top to a random percentage between 0% and 100%

JS
const sky = document.getElementById('sky');

const addCloud = () => {
const cloud = document.createElement('div');
cloud.setAttribute('class', 'cloud');

// Select random vertical position for the cloud in the sky
cloud.style.top = `${Math.floor(Math.random() * 101)}%`;

sky.appendChild(cloud);

cloud.addEventListener('animationend', () => {
cloud.remove();
});
};

const setRandomInterval = (fn, min, max) => {
const range = 1 + max - min;
const seconds = Math.floor(Math.random() * range + min);
const milliseconds = seconds * 1000;
setTimeout(() => {
fn();
setRandomInterval(fn, min, max);
}, milliseconds);
};

addCloud();
setRandomInterval(addCloud, 5, 10);

5. Randomising cloud speed, size and depth permalink#

Next we will add some variety to the clouds speed, size and depth, making use of the --speed, --scale and -depth CSS variables from Part 1.


We create a new function selectRandom which allows us to select a random value from an array. Using this new function we can set the CSS variables for each created cloud to a random value from an array of acceptable values.

JS
const sky = document.getElementById('sky');

// Function selects a random element from an array
const selectRandom = array => {
return array[Math.floor(Math.random() * array.length)];
};

const addCloud = () => {
const cloud = document.createElement('div');
cloud.setAttribute('class', 'cloud');

cloud.style.top = `${Math.floor(Math.random() * 101)}%`;

// Give the cloud a random speed, scale and depth
cloud.style.setProperty('--speed', selectRandom([1, 1.5, 2]));
cloud.style.setProperty('--scale', selectRandom([1, 1.5, 2]));
cloud.style.setProperty('--depth', selectRandom([0.55, 0.75, 0.95]));

sky.appendChild(cloud);

cloud.addEventListener('animationend', () => {
cloud.remove();
});
};

const setRandomInterval = (fn, min, max) => {
const range = 1 + max - min;
const seconds = Math.floor(Math.random() * range + min);
const milliseconds = seconds * 1000;
setTimeout(() => {
fn();
setRandomInterval(fn, min, max);
}, milliseconds);
};

addCloud();
setRandomInterval(addCloud, 5, 10);

6. Adding more cloud images permalink#

Finally let's add some more cloud images with cloud_1.png, cloud_2.png and cloud_3.png


Here we programmatically override the background-image of the cloud, randomly setting it to one of the three cloud images.

JS
const sky = document.getElementById('sky');

const selectRandom = array => {
return array[Math.floor(Math.random() * array.length)];
};

const addCloud = () => {
const cloud = document.createElement('div');
cloud.setAttribute('class', 'cloud');

cloud.style.top = `${Math.floor(Math.random() * 101)}%`;

cloud.style.setProperty('--speed', selectRandom([1, 1.5, 2]));
cloud.style.setProperty('--scale', selectRandom([1, 1.5, 2]));
cloud.style.setProperty('--depth', selectRandom([0.55, 0.75, 0.95]));

// Selecting a random background image for the cloud
const cloudImagePath = `/cloud_${selectRandom([1, 2, 3])}.png`;
cloud.style.backgroundImage = `url(${cloudImagePath})`;

sky.appendChild(cloud);

cloud.addEventListener('animationend', () => {
cloud.remove();
});
};

const setRandomInterval = (fn, min, max) => {
const range = 1 + max - min;
const seconds = Math.floor(Math.random() * range + min);
const milliseconds = seconds * 1000;
setTimeout(() => {
fn();
setRandomInterval(fn, min, max);
}, milliseconds);
};

addCloud();
setRandomInterval(addCloud, 5, 10);

7. Potential improvements permalink#

Hopefully that's everything you need to set up your own animated clouds.

Here's a few ideas to make the clouds even better:

  1. Randomise the animation-timing-function for each cloud to be either linear, ease-in or ease-out. This means a cloud's speed would either remain constant, speed up, or slow down over the course of its animation.
JS
const addCloud = () => {
...
const timing = selectRandom(['linear', 'ease-in', 'ease-out']);
cloud.style.animationTimingFunction = timing;
...
};

  1. Using CSS media queries you could adjust the cloud speed so they don't move too slowly on small devices or too quickly on larger screens. Remember to consider the width of the sky since that might not extend the full width of the screen.
CSS
.cloud {
--animation-duration: 45s;
animation-duration: calc(var(--animation-duration) / (var(--speed) * var(--depth)));
}

@media (min-width: 640px) {
.cloud {
--animation-duration: 60s;
}
}

@media (min-width: 960px) {
.cloud {
--animation-duration: 75s;
}
}

@media (min-width: 1440px) {
.cloud {
--animation-duration: 100s;
}
}

  1. Stop clouds from spawning when the window is no longer visible to prevent a build up of spawned clouds since they will continue spawning but their animation will not start.
JS
let canSpawnClouds = true;

const addCloud = () => {
if (!canSpawnClouds) return;
...
};

window.addEventListener('visibilitychange', () => {
canSpawnClouds = document.visibilityState === 'visible';
});


All examples from this article are available on CodePen.


Back to top