CSS-Only Raindrops on Window Effect

Making fancy demos using only CSS might not be useful per se. However, it’s a good opportunity to explore the capabilities of CSS, to try new tools, and to practice working within constraints. Today, we will take a look at how to make a raindrops on window effect, using HAML and SASS.

View the CodePen demo.

Preprocessors

First of all, let me explain why we’re using HAML/SASS instead of plain HTML/CSS. Even though they are simply useful all-around, the reason we need preprocessors here is that they allow us to, among other things, use variables, create loops, and generate random values, so we don’t have to deal with hundreds of raindrops individually by hand.

More information about setup and syntax can be found on their respective webpages. Alternately, you can try this demo on Codepen, creating a new pen, and then selecting SCSS for the CSS preprocessor and HAML for HTML.

The Window

The first step is to display the window itself.

.container
    .window
// Our background image.
// Will be used for the window as well as
// for the raindrops themselves
$image: 'http://i.imgur.com/xQdYC7x.jpg';

// container width and height.
// 100vw/vh so it fills the entire window
$width:100vw;
$height:100vh;

.container{
  position:relative;
  width:$width;
  height:$height;
  overflow:hidden;
}
.window{
  position:absolute;
  width:$width;
  height:$height;
  background:url($image);
  background-size:cover;
  background-position:50%;
  filter:blur(10px);
}

window-optimized

Here we simply draw a div with the background image of your choice. We are also applying a blur filter to it so the drops will become more visible.

Notice that we are storing the background image URL on the variable $image. We are doing this because we’ll use the same image for the raindrops themselves. More on that later.

In Real Life

Before we proceed, let’s take a look at how a raindrop on a window looks in real life:

real

Source: Wikipedia

Due to refraction, the raindrop flips the image behind it. Also, when its shape is more or less that of a half sphere, it looks like it has a black border.

Raindrops

Based on what we’ve seen, let’s try to create a single raindrop:

.container
    .window
    .raindrop
$drop-width:15px;

// our raindrops are not going to be perfectly round, so we will stretch them a bit
// (not using transform:scale so our background doesn't get stretched)
$drop-stretch:1.1;
$drop-height:$drop-width*$drop-stretch;

.raindrop{
    position:absolute;
    top:$height/2;
    left:$width/2;

    width:$drop-width;
    height:$drop-height;

    // border radius 100% instead of 100px, so the raindrop is elliptical rather than a capsule-shaped
    border-radius:100%;
    background-image:url($image);
    background-size:$width*0.05 $height*0.05;
    transform:rotate(180deg);
}

borderless-optimized

This is pretty straightforward: all we do is draw an elliptical div, fill it with the background image we used before, scale the background down, and turn the object upside down.

Now we’re going to add a little border around it, to make it look like the raindrop has volume.


...

    .border
    .raindrop
...

.border{
    position:absolute;
    top:$height/2;
    left:$width/2;

    margin-left:2px; 
    margin-top:1px;

    width:$drop-width - 4;
    height:$drop-height;

    border-radius:100%;

    box-shadow:0 0 0 2px rgba(0,0,0,0.6);
}

bordered-optimized

Notice that we didn’t just add a perfect border around the drop, we shifted and squished it a bit so it looks more natural.

So now that our raindrop is looking pretty good, let’s see how we can add hundreds of them.

...

.raindrops
    .borders
        - (1..100).each do
            .border
    .drops
        - (1..100).each do
            .raindrop

This is the HAML syntax for loops. Here we are simply adding a hundred .raindrop and .border objects inside their respective wrappers.

The SASS part is trickier, so will look at it step by step.

First, here’s how we’ll create our loop and select each element individually:

@for $i from 1 through 100{

    // using the $i variable with the CSS nth-child pseudo-class
    .raindrop:nth-child(#{$i}){

    }

    .border:nth-child(#{$i}){

    }
}

Now, we’ll generate and apply random positions and sizes to our raindrops:

@for $i from 1 through 200{

    // generates a random number from 0 to 1, for the positioning
    $x:random();
      $y:random();

      // Random raindrop size and stretching.
      // Since each raindrop has different sizes, we'll do our sizing
      // calculations here instead of on the main .raindrop selector
     $drop-width:5px+random(11);
     $drop-stretch:0.7+(random()*0.5);
    $drop-height:$drop-width*$drop-stretch;

    .raindrop:nth-child(#{$i}){
        // multiply the random position value by the container's size
        left:$x * $width;
        top:$y * $height;

        width:$drop-width;
        height:$drop-height;
    }

    .border:nth-child(#{$i}){
        // we'll do the same positioning for the drop's border
        left:$x * $width;
        top:$y * $height;

        width:$drop-width - 4;
        height:$drop-height;
    }
}

raindrops-nopos-optimized

Finally, there’s an important detail missing: we have to change the position of the background of each raindrop according to the drop’s position, so our refraction effect looks nice.

...

.raindrop:nth-child(#{$i}){
    ...

    background-position:percentage($x) percentage($y);
}
...

raindrops-nofilter-optimized

And with that, the main effect is done! We can then add small details, like increasing the brightness of the raindrops a bit to make them look shiny, animate them falling, change the focus, change the image, and so on.

Here’s the demo of the version we made here, along with the complete code:

View the CodePen demo.

Hope you could learn something useful from this tutorial, and hope you enjoyed it!

Get Started

Free TrialInspired by what you've read? Kick off your next project with Dreamweaver. It's part of Creative Cloud. Download a trial for free!

Share your thoughts

Your email address will not be published. Required fields are marked *

*