Simulating A Cool Water Ripple Effect In JavaScript
Have you ever wanted to add a little extra magic to your website? Well, a water ripple effect can do just that! It is a simple effect, but it can be quite mesmerizing. You can use this effect to make your site feel alive and interactive. In this blog, we are going to look at how you can create a stunning water ripple distortion effect using HTML5 and JavaScript.
Prerequisites
Before we get started, make sure you have a basic understanding of HTML, CSS, and JavaScript.
- Basic Understanding of HTML, CSS, and JavaScript: Familiarity with the fundamental concepts of HTML for structuring web pages, CSS for styling, and JavaScript for adding interactivity is essential. This tutorial assumes a basic knowledge of these languages.
- Web Browser: Use a modern web browser like Google Chrome, Mozilla Firefox, or Microsoft Edge.
For the purpose of this tutorial, you can download the source files for the demo to help you follow along:
Note: As you test the source files, ensure that you load them within a suitable environment such that all the files are loaded. For offline use, we recommend that you use Live Server and vscode to load the files. Simply open the files in Vscode and run “index.html” using Live Server.
Demo
Here is what we are going to create. Move the mouse over the image to see the effect in action.
How it works
This project uses an HTML5 Canvas element to generate realistic ripple distortions that respond to user interactions. The effect works by simulating wave movements through pixel manipulation. When a user moves their mouse or clicks, ripples form and spread across the canvas, mimicking real water waves. The effect is achieved through dynamic pixel manipulation, where image data is shifted to create a refraction effect. To keep the animation smooth and performant, the ripples animate at a controlled frame rate of approximately 30 frames per second (fps).
1.Setting Up the Project
Let’s start by setting up the basic HTML structure for our project. Create an HTML called “index.html”, with a basic HTML web format, and add a canvas element.
HTML5’s <canvas> element allows you to draw graphics directly on a webpage using JavaScript. It’s essentially a blank space where we can paint anything we like 2D shapes, images, and even 3D projections!
Creating the Canvas Element
To get started, we need a <canvas> element in our HTML file:
<canvas id="canvas" width="500" height="500" style="background: black;"></canvas>
This creates a 500×500-pixel canvas with a black background. The next step is to access this canvas through JavaScript and set up a 2D rendering context for drawing.
Also, add a script tag for our javascript code, and you are good to go!
Here is our full code template.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Projection</title>
</head>
<body>
<!-- Create a canvas element to draw on -->
<canvas id="canvas" width="500" height="500" style="background: black;"></canvas>
<script type="text/javascript">
</script>
</body>
</html>
2. Defining Constants and Variables
Before we create the ripple effect, we need to set up some global variables to manage the canvas, its dimensions, and the ripple properties.
// Get the canvas element and its 2D rendering context
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// Store the canvas width and height for easier reference
const width = canvas.width;
const height = canvas.height;
// Create a new image object for the background image
const background = new Image();
background.src = "image.jpg"; // Replace with your image path
// Define a timeout to control the frame rate (~30fps)
const timeout = 33;
// Array to store all the ripples
let ripples = [];
// === Global Ripple Settings ===
// These settings define the behavior of all ripples on the canvas
const GLOBAL_SETTINGS = {
growthSpeed: 2, // Speed at which the ripple grows (in pixels per frame)
amplitude: 50, // Maximum distortion of pixels in pixels
wavelength: 500, // Determines the frequency of the wave
ringWidth: 10, // Thickness of the ripple rings
maxAge: 100, // Maximum lifespan of a ripple (in frames)
maxRipples: 50, // Max number of ripples on the screen at once (for performance)
};These variables set up the canvas and store ripple effects dynamically
3. Creating the Ripple Class
The Ripple class is responsible for managing the properties and behavior of each ripple on the canvas. Each ripple grows, distorts the image, and then fades out smoothly. So the ripple class has functions to grow the ripple radius, fade it, and change its age.
// The Ripple class defines the properties and behavior of each ripple
class Ripple {
constructor(x, y, settings = GLOBAL_SETTINGS) {
// Initialize ripple properties
this.x = x; // X position of the ripple
this.y = y; // Y position of the ripple
this.age = 0; // Age of the ripple (used for lifespan and fading)
this.maxAge = settings.maxAge; // Max age of this ripple
this.growthSpeed = settings.growthSpeed; // How fast the ripple grows
this.amplitude = settings.amplitude; // Maximum distortion caused by the ripple
this.wavelength = settings.wavelength; // The wavelength of the wave effect
this.ringWidth = settings.ringWidth; // The width of the ripple ring
this.initialRadius = settings.initialRadius || 0; // Optional initial radius, used for larger ripples on click
}
// Calculate the current radius of the ripple based on its age and growth speed
get radius() {
return this.initialRadius + this.age * this.growthSpeed; // Adding initial radius for larger starting size
}
// Calculate the fade effect based on the age of the ripple
get fade() {
return 1 - this.age / this.maxAge; // Fade the ripple over time
}
// Check if the ripple is still alive (i.e., not past its maximum age)
isAlive() {
return this.age < this.maxAge;
}
// Increment the age of the ripple
incrementAge() {
this.age++;
}
}4. Handling User Interaction with Event Listeners
Now we need to make the ripples appear when the user moves their mouse or clicks on the canvas.
// Event listener for mouse movement
canvas.addEventListener("mousemove", function (e) {
const rect = canvas.getBoundingClientRect(); // Get canvas bounds
const x = e.clientX - rect.left; // Calculate mouse X position on canvas
const y = e.clientY - rect.top; // Calculate mouse Y position on canvas
// Create a ripple at the mouse position with faster growth and shorter lifespan
const settings = {
growthSpeed: 4, // Faster growth speed for movement ripples
maxAge: 100, // Shorter lifespan for movement ripples
amplitude: 30, // Smaller distortion amplitude for movement ripples
wavelength: 50,
ringWidth: 6
};
ripples.push(new Ripple(x, y, settings)); // Add the new ripple to the array
// If there are more than the maximum allowed ripples, remove the oldest one for performance
if (ripples.length > GLOBAL_SETTINGS