Interaction Design with Riot - Making of Rain Animation

10. Lesson of RiotJS

We are going to see how together with Riot, we can do awesome animations without bending our minds around the DOM.

Martin Muzatko Written by Martin Muzatko
on

Intro

Lately, I've created a little animation I featured in Stay determined Human. Surprisingly, it took me just a few minutes to figure out the animation itself.

The original Artwork by Makameowart, inspired me to create that little animation.

What do we need?

It is as simple as creating a few divs. We just iterate over an array of 16 items so we can attach values to the elements later.

Riot doesn't loop through undefined arrays, as it looks like. So new Array(16) won't work.

Designing the Droplet

The droplets are just simple div elements with a subtle styling.

CSS
.rain .droplet {
    background: linear-gradient(to bottom, transparent, #2e20aa);
    width: 1%;
    height: 5%;
    border-radius: 100%;
    position: absolute;
    animation: rain 1.5s linear;
    animation-iteration-count: infinite;
    z-index: 1;
}

The animation doesn't contain anything than a simple top: 100%; We are going to randomize the base offset and animation speed, so we can have that simplicity in our animation.

CSS
@keyframes rain {
    100% {
        top: 100%;
    }
}

To spread them apart from left to right, we add an inline style.

RIOT
<div each={droplet in droplets} style="left: {Math.random()*100}%" class="droplet"></div>
<script type="riot/tag"> <rain> <div class="rain align_center"> <div each=\{droplet in droplets\} style="left: \{random(100) | 0\}%" class="droplet"></div> </div> <style> .rain \{ background-color: #171054; width: 30em; height: 50em; position: relative; overflow: hidden; \} .rain .droplet \{ background: linear-gradient(to bottom, transparent, #2e20aa); width: 1%; height: 5%; border-radius: 100%; position: absolute; animation: rain 1.5s linear; animation-iteration-count: infinite; z-index: 1; top: 0; \} @keyframes rain \{ 100% \{ top: 100%; \} \} img \{ max-width: 100%; \} </style> this.droplets = 'x'.repeat(16).split('') this.random = function(val) \{ return Math.random() * val | 0 \} </rain> </script> <rain></rain>

This doesn't look that great. We need to randomize the offset of the drops.

Randomizing to make it feel more natural

As you can see, the drops all land at the bottom at the same time. We do that by adding another inline style to set their top value:

RIOT
<script type="riot/tag">
    <rain2>
    	<div class="rain">
            <div each={droplet in droplets} 
    style="top: -{Math.random()*20}%; left: {Math.random()*100}%"
    class="droplet"></div>
    	</div>
    	<style>
    		.rain {
    			background-color: #171054;
    			width: 30em;
    			height: 50em;
    			position: relative;
    			overflow: hidden;
    		}
    		.rain .droplet {
    			background: linear-gradient(to bottom, transparent, #2e20aa);
    			width: 1%;
    			height: 5%;
    			border-radius: 100%;
    			position: absolute;
    			animation: rain 1.5s linear;
    			animation-iteration-count: infinite;
    			z-index: 1;
    			top: 0;
    		}
    		@keyframes rain {
    			100% {
    				top: 100%;
    			}
    		}
    		img {
    			max-width: 100%;
    		}

    	</style>
		this.droplets = 'x'.repeat(16).split('')
		this.random = function(val)
		{
			return Math.random() * val | 0
		}
    </rain2>
</script>
<rain2></rain2>
<script type="riot/tag"> <rain2> <div class="rain"> <div each=\{droplet in droplets\} style="top: -\{Math.random()*20\}%; left: \{Math.random()*100\}%" class="droplet"></div> </div> <style> .rain \{ background-color: #171054; width: 30em; height: 50em; position: relative; overflow: hidden; \} .rain .droplet \{ background: linear-gradient(to bottom, transparent, #2e20aa); width: 1%; height: 5%; border-radius: 100%; position: absolute; animation: rain 1.5s linear; animation-iteration-count: infinite; z-index: 1; top: 0; \} @keyframes rain \{ 100% \{ top: 100%; \} \} img \{ max-width: 100%; \} </style> this.droplets = 'x'.repeat(16).split('') this.random = function(val) \{ return Math.random() * val | 0 \} </rain2> </script> <rain2></rain2>

We have to add an extra 0.5 seeconds, so we don't have too fast rain droplets. Due to the random animation-speed, drops are now having random starting and endpoints.

<script type="riot/tag"> <rain3> <div class="rain"> <div each=\{droplet in droplets\} style="animation-duration: \{(Math.random()*2)+0.5\}s; top: -\{Math.random()*20\}%; left: \{Math.random()*100\}%" class="droplet"></div> </div> <style> .rain \{ background-color: #171054; width: 30em; height: 50em; position: relative; overflow: hidden; \} .rain .droplet \{ background: linear-gradient(to bottom, transparent, #2e20aa); width: 1%; height: 5%; border-radius: 100%; position: absolute; animation: rain 1.5s linear; animation-iteration-count: infinite; z-index: 1; top: 0; \} @keyframes rain \{ 100% \{ top: 100%; \} \} img \{ max-width: 100%; \} </style> this.droplets = 'x'.repeat(16).split('') this.random = function(val) \{ return Math.random() * val | 0 \} </rain3> </script> <rain3></rain3>

Why not canvas?

The ultimate pro of this approach is that you can easily create simple particle effects with just a bunch of HTML and CSS. There isn't even that much javascript involved, just for making it more random. Depending on the amount of items, this can quickly become performance intense, but I think for games or websites that need ambience effects, this could be a great solution.

Summary

This post is just a quick followup of my recent article Stay determined Human. More lessons to continue our previous problems and solutions will follow soon.

Previous Lesson

Reusable Components - Observing Events


9. Lesson of RiotJS

We are finally going to obtain the data in the picker and we upgrade our component with an Event-API. Many more awesome things happen when we combine our tag with other components.

See all Lessons