Let’s create a “Fishbowl – Save the Fish” project using HTML, CSS, and JavaScript. This interactive project will feature a fish that users can “save” by clicking on it, moving it to a safe part of the screen.
We’ll use HTML to structure the fishbowl and fish, CSS to style them and create a visually appealing scene, and JavaScript to add interactivity for saving the fish.
Let’s get started on building the “Fishbowl – Save the Fish” project. Whether you’re a beginner or an experienced developer, this project offers a fun way to practice your web development skills and create an engaging user experience. Let’s dive in!
HTML :
This HTML code creates a fishbowl animation on a webpage. It includes various div elements to represent parts of the fishbowl such as the pool, background, bottom, seaweed decorations, water, and a fish with a tail. It also features an interactive tap to “refill” the fishbowl, prompting animation and style changes defined in an external CSS file (`style.css`). A linked JavaScript file (`script.js`) will handle interactive behaviors.
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <title>Fishbowl - Save the fish</title> <link rel="stylesheet" href="./style.css"> </head> <body> <!-- partial:index.partial.html --> <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> <!-- partial --> <script src="./script.js"></script> </body> </html>
CSS :
This CSS code creates an animated fishbowl on a webpage. It sets font sizes based on viewport height and centers content using flexbox. It defines animations for a fish (`swimming` and `dead` keyframes) and styles various fishbowl components, including the background, water, and decorations like seaweed. Additionally, it animates fish and water tap elements and adjusts the layout for smaller screens.
html { font-size: 5vh; } @media screen and (min-height: 400px) { html { font-size: 19px; } } body { display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #eb80b1; } @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 { position: relative; width: 15rem; height: 15rem; } .fishbowl__background { position: relative; width: 100%; height: 100%; border-radius: 50% 50% 40% 40%; background: linear-gradient(transparent 10%, #FFF 150%); border-bottom: 1px solid #FFF; } .fishbowl:before { content: ""; position: absolute; bottom: 9.5rem; left: 2rem; width: 100%; height: 30%; border-radius: 50%; box-shadow: -4rem 10rem 1rem 0 rgba(0, 0, 56, 0.3); transform: rotate(5deg); } .fishbowl:after { content: ""; position: absolute; top: 12.5%; left: 2.5%; width: 95%; height: 85%; border-radius: 40%; background: linear-gradient(135deg, transparent 50%, #FFF 150%); } .fishbowl__bottom { position: absolute; bottom: 5%; left: 17.5%; width: 65%; height: 20%; border-radius: 50%; background: linear-gradient(#F5FCCD, #ff7d66 200%); } .fishbowl__decoration { position: absolute; top: 20%; left: 5%; width: 90%; height: 75%; } .fishbowl__seaweed { position: absolute; width: 0; height: 0; border-left: 0.5rem solid transparent; border-right: 0.5rem solid transparent; border-bottom: 5rem solid #80c0a1; } .fishbowl__seaweed--1 { bottom: 15%; right: 20%; border-bottom: 5rem solid #80c0a1; } .fishbowl__seaweed--2 { bottom: 10%; right: 30%; border-bottom: 8rem solid #80c0a1; } .fishbowl__seaweed--3 { bottom: 15%; right: 40%; border-bottom: 6rem solid #80c0a1; } .fishbowl__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; } .fishbowl__water-color { position: absolute; bottom: 0; left: 0; width: 100%; height: calc(1% * var(--filling, 90)); background: linear-gradient(transparent -50%, #419197 250%); border-radius: 20% 20% 4rem 4rem; transition: height 0.5s linear; } .fishbowl__water-color:after { content: ""; position: absolute; top: 0; left: 5%; width: 90%; height: 3rem; border-radius: 50%; background: linear-gradient(transparent 0%, #419197 250%); box-shadow: inset 0 -1px 0 0 rgba(255, 255, 255, 0.5); } .fishbowl__top { position: absolute; top: 5%; left: 15%; width: 70%; height: 20%; border-radius: 50%; box-shadow: 0 2px 2px 3px rgba(255, 255, 255, 0.3); } .fishbowl__fish { position: absolute; bottom: 25%; left: 18%; width: 2rem; height: 1rem; border-radius: 50%; background: linear-gradient(#FFF -200%, orange); box-shadow: 0 2rem 4px -2px rgba(0, 0, 56, 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; } .fishbowl__fish:after { content: ""; position: absolute; top: 15%; right: 15%; width: 0.25rem; height: 0.25rem; border-radius: 50%; background: radial-gradient(circle at 0 0, #FFF -100%, #12486B); transition: height 0.5s ease; } .fishbowl__fish--dying { bottom: 10%; box-shadow: 0 1rem 4px -2px rgba(0, 0, 56, 0.3); } .fishbowl__fish--dying .fishbowl__fish-tail { box-shadow: 0 1rem 4px -2px rgba(0, 0, 56, 0.3); } .fishbowl__fish--dead { animation: dead; animation-duration: 2s; animation-iteration-count: 2; animation-fill-mode: forwards; box-shadow: none; } .fishbowl__fish--dead .fishbowl__fish-tail { box-shadow: none; } .fishbowl__fish--dead:after { height: 0.125rem; } .fishbowl__fish--floating { bottom: max(calc(var(--filling, 0) * 1% - 15%), 10%); transform: translateX(2.5rem); animation: none; box-shadow: none; } .fishbowl__fish--floating:after { height: 2px; } .fishbowl__fish--floating .fishbowl__fish-tail { box-shadow: none; } .fishbowl__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(0, 0, 56, 0.3); transition: box-shadow 1s ease; } .fishbowl__pool { position: absolute; right: 0; bottom: -5%; width: 50%; height: 15%; border-radius: 50%; background: linear-gradient(#FFF -100%, #419197); opacity: 0.5; } .fishbowl__pool:after { content: ""; position: absolute; top: 25%; left: 25%; width: 50%; height: 50%; border-right: 2px solid #FFF; border-radius: 50%; animation: wave; animation-duration: 3s; animation-iteration-count: infinite; opacity: 0.5; } @keyframes wave { from { top: 25%; left: 25%; width: 50%; height: 50%; } to { top: 10%; left: 10%; width: 80%; height: 80%; } } .fishbowl__tap { position: absolute; bottom: 0; left: -3rem; width: 12rem; height: 15.9rem; cursor: pointer; } .fishbowl__tap--active .fishbowl__tap-stream { animation: stream; animation-duration: 0.5s; } @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; } } .fishbowl__tap--active .fishbowl__tap-handle { animation: handle; animation-duration: 0.5s; } @keyframes handle { from { transform: rotate(45deg); } to { transform: rotate(405deg); } } .fishbowl__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(255, 255, 255, 0.5); background: linear-gradient(#919ea3, #66777F 150%); } .fishbowl__tap-base: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(0, 0, 56, 0.3); transform: rotate(-70deg); } .fishbowl__tap-base:after { content: ""; position: absolute; bottom: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4) 60%, transparent 200%); border-radius: 0 0 1.2rem 1.2rem; } .fishbowl__tap-handle { position: absolute; left: -1rem; bottom: 6rem; width: 2rem; height: 2rem; border-radius: 0.2rem; border-top: 1px solid #FFF; border-right: 1px solid rgba(255, 255, 255, 0.5); background: radial-gradient(circle at 0 0, #FFF -100%, #919ea3); transform: rotate(45deg); } .fishbowl__tap-handle:after { content: ""; position: absolute; top: 0.5rem; left: 0.5rem; width: 1rem; height: 1rem; border-radius: 50%; background: radial-gradient(circle at 0 0, #FFF -200%, #419197); } .fishbowl__tap-stream { position: absolute; top: 2rem; left: 6.25rem; width: 1.5rem; height: 0; background: linear-gradient(#FFF -100%, #419197); border-radius: 1rem; opacity: 0.3; } .fishbowl__tap-head { position: absolute; top: 0; left: 0; width: 4rem; height: 0rem; border-radius: 50% 50% 0 0; border-top: 2rem solid #919ea3; border-left: 2rem solid #919ea3; border-right: 2rem solid #919ea3; box-shadow: 1px -1px 0 0 #FFF; } .fishbowl__tap-head:before { content: ""; position: absolute; z-index: -1; bottom: 3rem; right: 23rem; width: 150%; height: 200%; box-shadow: -4rem 10rem 1rem 1rem rgba(0, 0, 56, 0.3); transform: rotate(2deg) skewX(60deg); } .fishbowl__tap-head:after { content: ""; position: absolute; top: -2rem; left: -2rem; width: 8rem; height: 2rem; background: linear-gradient(transparent, rgba(255, 255, 255, 0.4) 60%, transparent 200%); border-radius: 50% 50% 0 50%; } .fishbowl__tap-end { position: absolute; left: 6rem; top: 1.5rem; width: 2rem; height: 1rem; border-radius: 50%; background: linear-gradient(#FFF -70%, #919ea3); } .fishbowl__tap-text { position: absolute; top: 4rem; left: -6rem; color: #FFF; font-family: "Arial", sans-serif; font-size: 0.875rem; } .fishbowl__tap-text:after { content: ""; position: absolute; bottom: -1rem; right: -1rem; width: 2rem; height: 1px; background-color: #FFF; transform: rotate(45deg); } @media screen and (max-width: 420px) { .fishbowl { left: 13%; } .fishbowl__tap-text { width: 2rem; top: 1rem; left: -3.5rem; } }
JavaScript:
This JavaScript code manages the water level in the fishbowl and the fish’s state. It decreases the water level (`fill`) by 1 every 200ms, updating the fishbowl’s visual state using CSS variables. If the water level drops below certain thresholds, the fish’s state changes to “dying” or “dead”. Clicking the tap refills the water by 20 units (up to a maximum of 90) and reactivates the emptying interval if the fishbowl was empty. The tap also animates briefly when clicked.
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); });
In conclusion, creating the “Fishbowl – Save the Fish” project using HTML, CSS, and JavaScript has been an enjoyable and educational journey. By combining HTML for structure, CSS for styling, and JavaScript for interactivity, we’ve crafted a fun and engaging experience where users can interact with the fishbowl to save the fish.
If your project has problems, don’t worry. Just click to download the source code and face your coding challenges with excitement. Have fun coding!