Cookies Psst! Do you accept cookies?

We use cookies to enhance and personalise your experience.
Please accept our cookies. Checkout our Cookie Policy for more information.

How to Create a Tap Filling a FishBowl with CSS and JS

Imagine having a cool animation on your webpage, like a little tap filling a fishbowl! That's exactly what we'll be building in this guide. We'll use two special tools: CSS (like adding colorful styles to your house) and JavaScript (like adding cool blinking lights). Don't worry, even if you're new to this, we'll break it down step by step in simple words.

First, Let's Set It Up

For this demonstration, we will be needing this elements:

  • A container for the whole thing.
  • A fishbowl with a fish inside.
  • A tap.
  • And finally, the water itself (which will be empty at first).

Also it's important to know SCSS (Sassy CSS) is a preprocessor, which means it's a special type of code that gets converted into regular CSS before your browser can understand it. Here's how to use SCSS in an HTML file:

Set Up Your Files

Create two folders: css and scss (or any names you prefer).
Inside the scss folder, create a file with a .scss extension (e.g., style.scss).
In your HTML file (e.g., index.html), create a tag to reference the compiled CSS file.

Write Your SCSS Styles

Write your styles inside the .scss file using SCSS syntax (similar to CSS which we will be using for this article with some added features).

Compile the SCSS

You'll need a tool like sass to convert your SCSS into CSS. You can install it using a package manager like npm.
Once installed, use a command like sass style.scss:style.css in your terminal. This will compile style.scss into style.css.

Link the Compiled CSS

In your HTML file, update the tag to point to the compiled CSS file (e.g., ).

Our full Html code:

<div id="fishbowl" class="fishbowl">
    <div class="fishbowl__pool"></div>
    <div class="fishbowl__background"></div>
    <div class="fishbowl__bottom"></div>
    <div class="fishbowl__decoration">
        <div class="fishbowl__seaweed fishbowl__seaweed--1"></div>
        <div class="fishbowl__seaweed fishbowl__seaweed--2"></div>
        <div class="fishbowl__seaweed fishbowl__seaweed--3"></div>
    </div>
    <div class="fishbowl__water">
        <div id="fish" class="fishbowl__fish">
            <div class="fishbowl__fish-tail"></div>
        </div>
        <div class="fishbowl__water-color"></div>
    </div>
    <div class="fishbowl__top"></div>
    <div id="tap" class="fishbowl__tap">
        <div class="fishbowl__tap-base"></div>
        <div class="fishbowl__tap-handle"></div>
        <div class="fishbowl__tap-stream"></div>
        <div class="fishbowl__tap-end"></div>
        <div class="fishbowl__tap-head"></div>
        <div class="fishbowl__tap-text">Click to refill</div>
    </div>
</div>

Adding Some Style

Let's make it look nice! We'll use SCSS for this, just like applying colors and effects to our picture. We'll define how the box, fishbowl, tap, and empty space for the water will look. For the water, we'll keep it short at first (since it's empty) and choose a cool blue color.

The Full SCSS code:

$green: #80c0a1;
$yellow: #F5FCCD;
$brown: #ff7d66;
$black: #000038;
$white: #FFF;
$pink: #eb80b1;
$blue: #419197;
$dark-blue: #12486B;
$orange: orange;
$grey: #919ea3;
$dark-grey: #66777F;

html {
    //Scaling drawing to fit on screen (thanks to rems)
    font-size: 5vh;
    @media screen and (min-height: 400px) {
        // Avoiding really big versions of the illustration
        font-size: 19px;
    }
}

body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: $pink;
}

@keyframes swimming {
    0%,
    100% {
        transform: translateX(0);
    }
    22.5% {
        transform: translateX(2.5rem) skewY(-5deg);
    }
    45% {
        transform: translateX(6rem) translateY(-1rem) skewY(5deg);
    }
    55% {
        transform: translateX(5rem) translateY(-0.5rem) scaleX(-1);
    }
    95% {
        transform: translateX(0) scaleX(-1) skewY(10deg);
    }
}

@keyframes dead {
    0%,
    100% {
        transform: translateX(2.5rem);
    }
    22.5% {
        transform: translateX(2.5rem) translateY(-1rem) skewY(-5deg);
    }
    45% {
        transform: translateX(2.5rem) skewY(5deg);
    }
    55% {
        transform: translateX(2.5rem) translateY(-1rem) skewY(-5deg);
    }
    95% {
        transform: translateX(2.5rem) skewY(5deg);
    }
}

.fishbowl {
    $component-class: &;
    position: relative;
    width: 15rem;
    height: 15rem;
    &__background {
        position: relative;
        width: 100%;
        height: 100%;
        border-radius: 50% 50% 40% 40%;
        background: linear-gradient(transparent 10%, $white 150%);
        border-bottom: 1px solid $white;
    }
    &:before {
        content: '';
        position: absolute;
        bottom: 9.5rem;
        left: 2rem;
        width: 100%;
        height: 30%;
        border-radius: 50%;
        box-shadow: -4rem 10rem 1rem 0 rgba($black, 0.3);
        transform: rotate(5deg);
    }
    &:after {
        content: '';
        position: absolute;
        top: 12.5%;
        left: 2.5%;
        width: 95%;
        height: 85%;
        border-radius: 40%;
        background: linear-gradient(135deg, transparent 50%, $white 150%);
    }

    &__bottom {
        position: absolute;
        bottom: 5%;
        left: 17.5%;
        width: 65%;
        height: 20%;
        border-radius: 50%;
        background: linear-gradient($yellow, $brown 200%);
    }
    &__decoration {
        position: absolute;
        top: 20%;
        left: 5%;
        width: 90%;
        height: 75%;
    }
    &__seaweed {
        position: absolute;
        width: 0;
        height: 0;
        border-left: 0.5rem solid transparent;
        border-right: 0.5rem solid transparent;
        border-bottom: 5rem solid $green;
        &--1 {
            bottom: 15%;
            right: 20%;
            border-bottom: 5rem solid $green;
        }
        &--2 {
            bottom: 10%;
            right: 30%;
            border-bottom: 8rem solid $green;
        }
        &--3 {
            bottom: 15%;
            right: 40%;
            border-bottom: 6rem solid $green;
        }
    }
    &__water {
        position: absolute;
        bottom: 5%;
        left: 5%;
        width: 90%;
        height: 80%;
        border-radius: 40% 40% 4.8rem 4.8rem;
        transition: height 0.3s ease;
        overflow: hidden;
    }
    &__water-color {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: calc(1% * var(--filling, 90));
        background: linear-gradient(transparent -50%, $blue 250%);
        border-radius: 20% 20% 4rem 4rem;
        transition: height 0.5s linear;
        &:after {
            content: '';
            position: absolute;
            top: 0;
            left: 5%;
            width: 90%;
            height: 3rem;
            border-radius: 50%;
            background: linear-gradient(transparent 0%, $blue 250%);
            box-shadow: inset 0 -1px 0 0 rgba($white, 0.5);
        }
    }
    &__top {
        position: absolute;
        top: 5%;
        left: 15%;
        width: 70%;
        height: 20%;
        border-radius: 50%;
        box-shadow: 0 2px 2px 3px rgba($white, 0.3); 
    }
    &__fish {
        position: absolute;
        bottom: 25%;
        left: 18%;
        width: 2rem;
        height: 1rem;
        border-radius: 50%;
        background: linear-gradient($white -200%, $orange);
        box-shadow: 0 2rem 4px -2px rgba($black, 0.3);
        transition-property: bottom, transform, box-shadow;
        transition-duration: 1s;
        transition-timing-function: ease;
        animation: swimming;
        animation-duration: 5s;
        animation-iteration-count: infinite;
        animation-fill-mode: forwards;
        &:after {
            content: '';
            position: absolute;
            top: 15%;
            right: 15%;
            width: 0.25rem;
            height: 0.25rem;
            border-radius: 50%;
            background: radial-gradient(circle at 0 0, $white -100%, $dark-blue);
            transition: height 0.5s ease;
        }
        &--dying {
            bottom: 10%;
            box-shadow: 0 1rem 4px -2px rgba($black, 0.3);
            #{$component-class}__fish-tail {
                box-shadow: 0 1rem 4px -2px rgba($black, 0.3);
            }
        }
        &--dead {
            animation: dead;
            animation-duration: 2s;
            animation-iteration-count: 2;
            animation-fill-mode: forwards;
            box-shadow: none;
            #{$component-class}__fish-tail {
                box-shadow: none;
            }
            &:after {
                height: 0.125rem;
            }
        }
        &--floating {
            bottom: max(calc(var(--filling, 0) * 1% - 15%), 10%);
            transform: translateX(2.5rem);
            animation: none;
            box-shadow: none;
            &:after {
                height: 2px;
            }
            #{$component-class}__fish-tail {
                box-shadow: none;
            }
        }
    }
    &__fish-tail {
        position: absolute;
        top: 0;
        left: -0.75rem;
        width: 0;
        height: 0;
        border-top: 0.5rem solid transparent;
        border-bottom: 0.5rem solid transparent;
        border-left: 0.94rem solid $orange;
        box-shadow: 0 2rem 4px -2px rgba($black, 0.3);
        transition: box-shadow 1s ease;
    }
    &__pool {
        position: absolute;
        right: 0;
        bottom: -5%;
        width: 50%;
        height: 15%;
        border-radius: 50%;
        background: linear-gradient($white -100%, $blue);
        opacity: 0.5;
        &:after {
            @keyframes wave {
                from {
                    top: 25%;
                    left: 25%;
                    width: 50%;
                    height: 50%;
                }
                to {
                    top: 10%;
                    left: 10%;
                    width: 80%;
                    height: 80%;
                }
            }
            content: '';
            position: absolute;
            top: 25%;
            left: 25%;
            width: 50%;
            height: 50%;
            border-right: 2px solid $white;
            border-radius: 50%;
            animation: wave;
            animation-duration: 3s;
            animation-iteration-count: infinite;
            opacity: 0.5;
        }
    }
    &__tap {
        position: absolute;
        bottom: 0;
        left: -3rem;
        width: 12rem;
        height: 15.9rem;
        cursor: pointer;
        &--active {
            #{$component-class}__tap-stream {
                @keyframes stream {
                    0% {
                        height: 0;
                    }
                    50% {
                        top: 2rem;
                        height: calc(14rem - var(--filling) * 0.1rem);
                    }
                    100% {
                        top: calc(2rem + 14rem - var(--filling) * 0.1rem);
                        height: 0;
                    }
                }
                animation: stream;
                animation-duration: 0.5s;
            }
            #{$component-class}__tap-handle {
                @keyframes handle {
                    from {
                        transform: rotate(45deg);
                    }
                    to {
                        transform: rotate(405deg);
                    }
                }
                animation: handle;
                animation-duration: 0.5s;
            }
        }
    }
    &__tap-base {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 2rem;
        height: 14rem;
        border-radius: 0 0 1.2rem 1.2rem;
        box-shadow: inset -1px -1px 0 0px rgba($white, 0.5);
        background: linear-gradient($grey, $dark-grey 150%);
        &:before {
            content: '';
            position: absolute;
            z-index: -1;
            bottom: 4rem;
            right: 15rem;
            width: 100%;
            height: 100%;
            border-radius: 1rem 1rem 0 0;
            box-shadow: -4rem 10rem 1rem 0 rgba($black, 0.3);
            transform: rotate(-70deg);
        }
        &:after {
            content: '';
            position: absolute;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(90deg, transparent, rgba($white, 0.4) 60%, transparent 200%);
            border-radius: 0 0 1.2rem 1.2rem;
        }
    }
    &__tap-handle {
        position: absolute;
        left: -1rem;
        bottom: 6rem;
        width: 2rem;
        height: 2rem;
        border-radius: 0.2rem;
        border-top: 1px solid $white;
        border-right: 1px solid rgba($white, 0.5);
        background: radial-gradient(circle at 0 0, $white -100%, $grey);
        transform: rotate(45deg);
        &:after {
            content: '';
            position: absolute;
            top: 0.5rem;
            left: 0.5rem;
            width: 1rem;
            height: 1rem;
            border-radius: 50%;
            background: radial-gradient(circle at 0 0, $white -200%, $blue);
        }
    }
    &__tap-stream {
        position: absolute;
        top: 2rem;
        left: 6.25rem;
        width: 1.5rem;
        height: 0;
        background: linear-gradient($white -100%, $blue);
        border-radius: 1rem;
        opacity: 0.3;
    }
    &__tap-head {
        position: absolute;
        top: 0;
        left: 0;
        width: 4rem;
        height: 0rem;
        border-radius: 50% 50% 0 0;
        border-top: 2rem solid $grey; 
        border-left: 2rem solid $grey; 
        border-right: 2rem solid $grey;
        box-shadow: 1px -1px 0 0 $white;
        &:before {
            content: '';
            position: absolute;
            z-index: -1;
            bottom: 3rem;
            right: 23rem;
            width: 150%;
            height: 200%;
            box-shadow: -4rem 10rem 1rem 1rem rgba($black, 0.3);
            transform: rotate(2deg) skewX(60deg);
        }
        &:after {
            content: '';
            position: absolute;
            top: -2rem;
            left: -2rem;
            width: 8rem;
            height: 2rem;
            background: linear-gradient(transparent, rgba($white, 0.4) 60%, transparent 200%);
            border-radius: 50% 50% 0 50%;
        }
    }
    &__tap-end {
        position: absolute;
        left: 6rem;
        top: 1.5rem;
        width: 2rem;
        height: 1rem;
        border-radius: 50%;
        background: linear-gradient($white -70%, $grey);
    }
    &__tap-text {
        position: absolute;
        top: 4rem;
        left: -6rem;
        color: $white;
        font-family: 'Arial', sans-serif;
        font-size: 0.875rem;
        &:after {
            content: '';
            position: absolute;
            bottom: -1rem;
            right: -1rem;
            width: 2rem;
            height: 1px;
            background-color: $white;
            transform: rotate(45deg);
        }
    }
    @media screen and (max-width: 420px) {
        // Centering the whole illustration on mobile
        left: 13%;
        &__tap-text {
            width: 2rem;
            top: 1rem;
            left: -3.5rem;
        }
    }
}

Bringing it to Life with Animation

Here's the fun part! We'll use JavaScript to tell the empty space for the water to slowly grow taller, like it's filling up from the tap. Imagine it like a magic trick where we can change our picture over time! We'll use a loop (like repeating a step) to make the water space taller and taller, bit by bit. We can even add a little overflow effect to show the water splashing a bit, for extra coolness.

let fill = 90;
let intervalId = null;
const fishbowl = document.getElementById('fishbowl');
const fish = document.getElementById('fish');
const tap = document.getElementById('tap');

const emptyingFn = () => setInterval(() => {
    fill = fill - 1;
    fishbowl.style = `--filling: ${fill}`;
    if (fill <= 0) {
        clearInterval(intervalId);
    } else if (fill < 20) {
        fish.classList.add('fishbowl__fish--dead');
    } else if (fill < 50) {
        fish.classList.add('fishbowl__fish--dying');
    } else {
        fish.classList.remove('fishbowl__fish--dying');
        fish.classList.remove('fishbowl__fish--dead');
    }
}, 200);

intervalId = emptyingFn();

tap.addEventListener('click', () => {
    tap.classList.add('fishbowl__tap--active');
    setTimeout(() => tap.classList.remove('fishbowl__tap--active'), 500);
    if (fill <= 0) {
        intervalId = emptyingFn();
        fish.classList.add('fishbowl__fish--floating');
    }
    fill = Math.min(fill + 20, 90);
});

Your result should look something like this:

Image description

We've built a cool animation with just code! Here's a quick recap: we used HTML to set up the different parts of our animation, SCSS to style it, and JavaScript to make the water magically rise. Animations like this can be used for all sorts of things on webpages, like decorations or even interactive elements. So why not try it out and add some life to your webpage?

Last Stories

What's your thoughts?

Please Register or Login to your account to be able to submit your comment.