Kinetic Water Surface
🔵 InteractiveA high-performance dark-mode fluid simulation. Generate perfectly circular, intersecting ripple waves on click, or drag the cursor to leave beautiful glowing neon wakes.
Responsive Horizontal Leaderboard
Slot ID: ca-pub-detail-below-preview
Interactive Settings
Medium Rectangle Block
Slot ID: ca-pub-detail-after-customization
Quick Copy Actions
Download File Packages
Keyboard Shortcuts
Responsive Horizontal Leaderboard
Slot ID: ca-pub-detail-before-related
More in Interactive & Cursor-Driven
Loading Canvas Shader...
Magnetic Fluid Grid
A highly responsive minimalist geometric grid that warps, bends, and reacts under the user's cursor using custom spring-mass physics and high-performance distance calculation.
Loading Canvas Shader...
Semantic Warp Lens
A gorgeous, high-fidelity monospaced text grid that warps, rotates, and inverts colors like an optical lens centered directly on the user's cursor.
Loading Canvas Shader...
Ethereal Smoke Trails
An ultra-responsive interactive canvas trail that renders smooth, glowing fluid-like ribbons accompanied by drifting and slowly dissolving micro-smoke particles.
Kinetic Water Surface - HTML5 Canvas & JavaScript Interactive & Cursor-Driven background for React & Tailwind
Integrate the Kinetic Water Surface directly into your website. This asset is rendered using HTML5 Canvas 2D render loop. It is optimized for zero layout-shifts and runs with high-performance hardware-accelerated processing.
⚡ Performance Specifications
- Render Mode: INTERACTIVE (HTML5 Canvas & JavaScript)
- Fluidity: Locked at 60fps dynamic loop
- Bundle Footprint: Zero external NPM dependencies
- SEO Indexing status: 100% Crawlable static semantic HTML
🛠️ Integration Capabilities
Our templates expose inline design tokens like --color-1 and --color-2 for infinite color palettes. This template is designed to fit inside hero elements, full-screen landing pages, and interactive presentation cards.
Technical Code Reference & Syntaxes for Googlebot & Crawlers
The snippets below display the direct, unminified source code utilized for rendering this background.
HTML & Inline CSS Snippet (Vanilla)
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kinetic Water Surface</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #09090b;
}
.kinetic-water-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #050608;
overflow: hidden;
}
#canvas-kinetic-water {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
display: block;
}
</style>
</head>
<body>
<div class="kinetic-water-container">
<canvas id="canvas-kinetic-water"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-kinetic-water');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let active = true;
// Simulation dimensions
const cols = 160;
const rows = 120;
const size = cols * rows;
// Heightmap buffers
let buffer1 = new Float32Array(size);
let buffer2 = new Float32Array(size);
const offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = cols;
offscreenCanvas.height = rows;
const offscreenCtx = offscreenCanvas.getContext('2d');
if (!offscreenCtx) return;
const imgData = offscreenCtx.createImageData(cols, rows);
const data = imgData.data;
const damping = 0.985;
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
let lastX = -1000;
let lastY = -1000;
function resize() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
width = canvas.width = rect ? rect.width : window.innerWidth;
height = canvas.height = rect ? rect.height : window.innerHeight;
}
function addRipple(x, y, isClick) {
const rect = canvas.getBoundingClientRect();
const mapX = Math.floor(((x - rect.left) / rect.width) * cols);
const mapY = Math.floor(((y - rect.top) / rect.height) * rows);
const radius = isClick ? 6 : 3;
const strength = isClick ? 1200 : 70;
if (mapX > radius && mapX < cols - radius && mapY > radius && mapY < rows - radius) {
if (!isClick) {
const distToLast = Math.hypot(x - lastX, y - lastY);
if (distToLast < 6) return;
lastX = x;
lastY = y;
}
for (let j = -radius; j <= radius; j++) {
const ry = mapY + j;
const yOffset = ry * cols;
for (let i = -radius; i <= radius; i++) {
const rx = mapX + i;
const dist = Math.sqrt(i * i + j * j);
if (dist < radius) {
const idx = yOffset + rx;
const cosValue = Math.cos((dist / radius) * Math.PI) * 0.5 + 0.5;
buffer1[idx] += cosValue * strength;
}
}
}
}
}
function handleMouseDown(e) {
addRipple(e.clientX, e.clientY, true);
}
function handleMouseMove(e) {
addRipple(e.clientX, e.clientY, false);
}
function handleTouchStart(e) {
if (e.touches.length > 0) {
addRipple(e.touches[0].clientX, e.touches[0].clientY, true);
}
}
function handleTouchMove(e) {
if (e.touches.length > 0) {
addRipple(e.touches[0].clientX, e.touches[0].clientY, false);
}
}
function handleMessage(e) {
if (e.data) {
if (e.data.type === 'mousemove') {
addRipple(e.data.x, e.data.y, false);
} else if (e.data.type === 'mousedown' || e.data.type === 'click') {
addRipple(e.data.x, e.data.y, true);
}
}
}
window.addEventListener('resize', resize);
window.addEventListener('mousedown', handleMouseDown);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('touchstart', handleTouchStart, { passive: true });
window.addEventListener('touchmove', handleTouchMove, { passive: true });
window.addEventListener('message', handleMessage);
let observer;
if (canvas.parentNode) {
observer = new MutationObserver(resize);
observer.observe(canvas.parentNode, { attributes: true });
}
resize();
function render() {
if (!active) return;
if (!canvas.isConnected) {
cleanup();
return;
}
for (let y = 1; y < rows - 1; y++) {
const yOffset = y * cols;
for (let x = 1; x < cols - 1; x++) {
const idx = yOffset + x;
let val = (
buffer1[idx - cols] +
buffer1[idx + cols] +
buffer1[idx - 1] +
buffer1[idx + 1]
) * 0.5 - buffer2[idx];
val *= damping;
buffer2[idx] = val;
}
}
const temp = buffer1;
buffer1 = buffer2;
buffer2 = temp;
const cx = cols / 2;
const cy = rows / 2;
const maxDistSq = (cols * cols + rows * rows) / 4;
for (let y = 1; y < rows - 1; y++) {
const yOffset = y * cols;
for (let x = 1; x < cols - 1; x++) {
const idx = yOffset + x;
const dx = buffer1[idx + 1] - buffer1[idx - 1];
const dy = buffer1[idx + cols] - buffer1[idx - cols];
const spec = Math.max(0, -dx + dy);
const highlight = Math.pow(Math.min(1.0, spec * 0.04), 4.0) * 160;
const rx = Math.max(0, Math.min(cols - 1, x + Math.floor(dx * 0.12)));
const ry = Math.max(0, Math.min(rows - 1, y + Math.floor(dy * 0.12)));
const distSq = (rx - cx) * (rx - cx) + (ry - cy) * (ry - cy);
const radialGlow = Math.max(0, 1.0 - distSq / maxDistSq);
let r = 6 + radialGlow * 12;
let g = 8 + radialGlow * 10;
let b = 14 + radialGlow * 18;
const h = buffer1[idx];
if (h > 0) {
r += h * 0.12;
g += h * 0.28;
b += h * 0.45;
} else if (h < 0) {
r += -h * 0.25;
g += -h * 0.08;
b += -h * 0.45;
}
r += highlight;
g += highlight;
b += highlight;
const pixelIdx = idx * 4;
data[pixelIdx] = r < 0 ? 0 : (r > 255 ? 255 : r);
data[pixelIdx + 1] = g < 0 ? 0 : (g > 255 ? 255 : g);
data[pixelIdx + 2] = b < 0 ? 0 : (b > 255 ? 255 : b);
data[pixelIdx + 3] = 255;
}
}
offscreenCtx.putImageData(imgData, 0, 0);
ctx.drawImage(
offscreenCanvas,
1, 1, cols - 2, rows - 2,
0, 0, width, height
);
requestAnimationFrame(render);
}
function cleanup() {
active = false;
window.removeEventListener('resize', resize);
window.removeEventListener('mousedown', handleMouseDown);
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('touchstart', handleTouchStart);
window.removeEventListener('touchmove', handleTouchMove);
window.removeEventListener('message', handleMessage);
if (observer) {
observer.disconnect();
}
}
requestAnimationFrame(render);
})();
</script>
<script>
// Throttled interactive cursor coordinates mapping
(function() {
document.documentElement.style.setProperty('--mouse-x', '0');
document.documentElement.style.setProperty('--mouse-y', '0');
let targetX = 0, targetY = 0;
let currentX = 0, currentY = 0;
window.addEventListener('mousemove', (e) => {
targetX = (e.clientX / window.innerWidth) - 0.5;
targetY = (e.clientY / window.innerHeight) - 0.5;
});
window.addEventListener('mouseleave', () => {
targetX = 0;
targetY = 0;
});
function update() {
currentX += (targetX - currentX) * 0.08;
currentY += (targetY - currentY) * 0.08;
document.documentElement.style.setProperty('--mouse-x', currentX.toFixed(4));
document.documentElement.style.setProperty('--mouse-y', currentY.toFixed(4));
requestAnimationFrame(update);
}
requestAnimationFrame(update);
})();
</script>
</body>
</html>React Component Wrapper (TSX)
import React, { useEffect, useRef } from 'react';
export default function KineticWaterSurfaceBackground() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let targetX = 0, targetY = 0;
let currentX = 0, currentY = 0;
let frameId: number;
const handleMouseMove = (e: MouseEvent) => {
targetX = (e.clientX / window.innerWidth) - 0.5;
targetY = (e.clientY / window.innerHeight) - 0.5;
};
const handleMouseLeave = () => {
targetX = 0;
targetY = 0;
};
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseleave', handleMouseLeave);
const updateCoordinates = () => {
currentX += (targetX - currentX) * 0.08;
currentY += (targetY - currentY) * 0.08;
if (containerRef.current) {
containerRef.current.style.setProperty('--mouse-x', currentX.toFixed(4));
containerRef.current.style.setProperty('--mouse-y', currentY.toFixed(4));
}
frameId = requestAnimationFrame(updateCoordinates);
};
frameId = requestAnimationFrame(updateCoordinates);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseleave', handleMouseLeave);
cancelAnimationFrame(frameId);
};
}, []);
return (
<div
ref={containerRef}
style={{ width: '100%', height: '100%', position: 'relative', overflow: 'hidden' }}
>
<style dangerouslySetInnerHTML={{ __html: `
.kinetic-water-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #050608;
overflow: hidden;
}
#canvas-kinetic-water {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
display: block;
}
` }} />
{/* HTML Structure */}
<div
style={{ width: '100%', height: '100%' }}
dangerouslySetInnerHTML={{ __html: `
<div class="kinetic-water-container">
<canvas id="canvas-kinetic-water"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-kinetic-water');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let active = true;
// Simulation dimensions
const cols = 160;
const rows = 120;
const size = cols * rows;
// Heightmap buffers
let buffer1 = new Float32Array(size);
let buffer2 = new Float32Array(size);
const offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = cols;
offscreenCanvas.height = rows;
const offscreenCtx = offscreenCanvas.getContext('2d');
if (!offscreenCtx) return;
const imgData = offscreenCtx.createImageData(cols, rows);
const data = imgData.data;
const damping = 0.985;
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
let lastX = -1000;
let lastY = -1000;
function resize() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
width = canvas.width = rect ? rect.width : window.innerWidth;
height = canvas.height = rect ? rect.height : window.innerHeight;
}
function addRipple(x, y, isClick) {
const rect = canvas.getBoundingClientRect();
const mapX = Math.floor(((x - rect.left) / rect.width) * cols);
const mapY = Math.floor(((y - rect.top) / rect.height) * rows);
const radius = isClick ? 6 : 3;
const strength = isClick ? 1200 : 70;
if (mapX > radius && mapX < cols - radius && mapY > radius && mapY < rows - radius) {
if (!isClick) {
const distToLast = Math.hypot(x - lastX, y - lastY);
if (distToLast < 6) return;
lastX = x;
lastY = y;
}
for (let j = -radius; j <= radius; j++) {
const ry = mapY + j;
const yOffset = ry * cols;
for (let i = -radius; i <= radius; i++) {
const rx = mapX + i;
const dist = Math.sqrt(i * i + j * j);
if (dist < radius) {
const idx = yOffset + rx;
const cosValue = Math.cos((dist / radius) * Math.PI) * 0.5 + 0.5;
buffer1[idx] += cosValue * strength;
}
}
}
}
}
function handleMouseDown(e) {
addRipple(e.clientX, e.clientY, true);
}
function handleMouseMove(e) {
addRipple(e.clientX, e.clientY, false);
}
function handleTouchStart(e) {
if (e.touches.length > 0) {
addRipple(e.touches[0].clientX, e.touches[0].clientY, true);
}
}
function handleTouchMove(e) {
if (e.touches.length > 0) {
addRipple(e.touches[0].clientX, e.touches[0].clientY, false);
}
}
function handleMessage(e) {
if (e.data) {
if (e.data.type === 'mousemove') {
addRipple(e.data.x, e.data.y, false);
} else if (e.data.type === 'mousedown' || e.data.type === 'click') {
addRipple(e.data.x, e.data.y, true);
}
}
}
window.addEventListener('resize', resize);
window.addEventListener('mousedown', handleMouseDown);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('touchstart', handleTouchStart, { passive: true });
window.addEventListener('touchmove', handleTouchMove, { passive: true });
window.addEventListener('message', handleMessage);
let observer;
if (canvas.parentNode) {
observer = new MutationObserver(resize);
observer.observe(canvas.parentNode, { attributes: true });
}
resize();
function render() {
if (!active) return;
if (!canvas.isConnected) {
cleanup();
return;
}
for (let y = 1; y < rows - 1; y++) {
const yOffset = y * cols;
for (let x = 1; x < cols - 1; x++) {
const idx = yOffset + x;
let val = (
buffer1[idx - cols] +
buffer1[idx + cols] +
buffer1[idx - 1] +
buffer1[idx + 1]
) * 0.5 - buffer2[idx];
val *= damping;
buffer2[idx] = val;
}
}
const temp = buffer1;
buffer1 = buffer2;
buffer2 = temp;
const cx = cols / 2;
const cy = rows / 2;
const maxDistSq = (cols * cols + rows * rows) / 4;
for (let y = 1; y < rows - 1; y++) {
const yOffset = y * cols;
for (let x = 1; x < cols - 1; x++) {
const idx = yOffset + x;
const dx = buffer1[idx + 1] - buffer1[idx - 1];
const dy = buffer1[idx + cols] - buffer1[idx - cols];
const spec = Math.max(0, -dx + dy);
const highlight = Math.pow(Math.min(1.0, spec * 0.04), 4.0) * 160;
const rx = Math.max(0, Math.min(cols - 1, x + Math.floor(dx * 0.12)));
const ry = Math.max(0, Math.min(rows - 1, y + Math.floor(dy * 0.12)));
const distSq = (rx - cx) * (rx - cx) + (ry - cy) * (ry - cy);
const radialGlow = Math.max(0, 1.0 - distSq / maxDistSq);
let r = 6 + radialGlow * 12;
let g = 8 + radialGlow * 10;
let b = 14 + radialGlow * 18;
const h = buffer1[idx];
if (h > 0) {
r += h * 0.12;
g += h * 0.28;
b += h * 0.45;
} else if (h < 0) {
r += -h * 0.25;
g += -h * 0.08;
b += -h * 0.45;
}
r += highlight;
g += highlight;
b += highlight;
const pixelIdx = idx * 4;
data[pixelIdx] = r < 0 ? 0 : (r > 255 ? 255 : r);
data[pixelIdx + 1] = g < 0 ? 0 : (g > 255 ? 255 : g);
data[pixelIdx + 2] = b < 0 ? 0 : (b > 255 ? 255 : b);
data[pixelIdx + 3] = 255;
}
}
offscreenCtx.putImageData(imgData, 0, 0);
ctx.drawImage(
offscreenCanvas,
1, 1, cols - 2, rows - 2,
0, 0, width, height
);
requestAnimationFrame(render);
}
function cleanup() {
active = false;
window.removeEventListener('resize', resize);
window.removeEventListener('mousedown', handleMouseDown);
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('touchstart', handleTouchStart);
window.removeEventListener('touchmove', handleTouchMove);
window.removeEventListener('message', handleMessage);
if (observer) {
observer.disconnect();
}
}
requestAnimationFrame(render);
})();
</script>
` }}
/>
</div>
);
}Next.js App Router Component (use client)
'use client';
import React, { useEffect, useRef } from 'react';
export default function KineticWaterSurfaceBackground() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
let targetX = 0, targetY = 0;
let currentX = 0, currentY = 0;
let frameId: number;
const handleMouseMove = (e: MouseEvent) => {
targetX = (e.clientX / window.innerWidth) - 0.5;
targetY = (e.clientY / window.innerHeight) - 0.5;
};
const handleMouseLeave = () => {
targetX = 0;
targetY = 0;
};
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseleave', handleMouseLeave);
const updateCoordinates = () => {
currentX += (targetX - currentX) * 0.08;
currentY += (targetY - currentY) * 0.08;
if (containerRef.current) {
containerRef.current.style.setProperty('--mouse-x', currentX.toFixed(4));
containerRef.current.style.setProperty('--mouse-y', currentY.toFixed(4));
}
frameId = requestAnimationFrame(updateCoordinates);
};
frameId = requestAnimationFrame(updateCoordinates);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseleave', handleMouseLeave);
cancelAnimationFrame(frameId);
};
}, []);
return (
<div
ref={containerRef}
className="w-full h-full relative overflow-hidden"
>
<style dangerouslySetInnerHTML={{ __html: `
.kinetic-water-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #050608;
overflow: hidden;
}
#canvas-kinetic-water {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
display: block;
}
` }} />
{/* HTML Structure */}
<div
className="w-full h-full"
dangerouslySetInnerHTML={{ __html: `
<div class="kinetic-water-container">
<canvas id="canvas-kinetic-water"></canvas>
</div>
<script>
(function() {
const canvas = document.getElementById('canvas-kinetic-water');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
let active = true;
// Simulation dimensions
const cols = 160;
const rows = 120;
const size = cols * rows;
// Heightmap buffers
let buffer1 = new Float32Array(size);
let buffer2 = new Float32Array(size);
const offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = cols;
offscreenCanvas.height = rows;
const offscreenCtx = offscreenCanvas.getContext('2d');
if (!offscreenCtx) return;
const imgData = offscreenCtx.createImageData(cols, rows);
const data = imgData.data;
const damping = 0.985;
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
let lastX = -1000;
let lastY = -1000;
function resize() {
const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
width = canvas.width = rect ? rect.width : window.innerWidth;
height = canvas.height = rect ? rect.height : window.innerHeight;
}
function addRipple(x, y, isClick) {
const rect = canvas.getBoundingClientRect();
const mapX = Math.floor(((x - rect.left) / rect.width) * cols);
const mapY = Math.floor(((y - rect.top) / rect.height) * rows);
const radius = isClick ? 6 : 3;
const strength = isClick ? 1200 : 70;
if (mapX > radius && mapX < cols - radius && mapY > radius && mapY < rows - radius) {
if (!isClick) {
const distToLast = Math.hypot(x - lastX, y - lastY);
if (distToLast < 6) return;
lastX = x;
lastY = y;
}
for (let j = -radius; j <= radius; j++) {
const ry = mapY + j;
const yOffset = ry * cols;
for (let i = -radius; i <= radius; i++) {
const rx = mapX + i;
const dist = Math.sqrt(i * i + j * j);
if (dist < radius) {
const idx = yOffset + rx;
const cosValue = Math.cos((dist / radius) * Math.PI) * 0.5 + 0.5;
buffer1[idx] += cosValue * strength;
}
}
}
}
}
function handleMouseDown(e) {
addRipple(e.clientX, e.clientY, true);
}
function handleMouseMove(e) {
addRipple(e.clientX, e.clientY, false);
}
function handleTouchStart(e) {
if (e.touches.length > 0) {
addRipple(e.touches[0].clientX, e.touches[0].clientY, true);
}
}
function handleTouchMove(e) {
if (e.touches.length > 0) {
addRipple(e.touches[0].clientX, e.touches[0].clientY, false);
}
}
function handleMessage(e) {
if (e.data) {
if (e.data.type === 'mousemove') {
addRipple(e.data.x, e.data.y, false);
} else if (e.data.type === 'mousedown' || e.data.type === 'click') {
addRipple(e.data.x, e.data.y, true);
}
}
}
window.addEventListener('resize', resize);
window.addEventListener('mousedown', handleMouseDown);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('touchstart', handleTouchStart, { passive: true });
window.addEventListener('touchmove', handleTouchMove, { passive: true });
window.addEventListener('message', handleMessage);
let observer;
if (canvas.parentNode) {
observer = new MutationObserver(resize);
observer.observe(canvas.parentNode, { attributes: true });
}
resize();
function render() {
if (!active) return;
if (!canvas.isConnected) {
cleanup();
return;
}
for (let y = 1; y < rows - 1; y++) {
const yOffset = y * cols;
for (let x = 1; x < cols - 1; x++) {
const idx = yOffset + x;
let val = (
buffer1[idx - cols] +
buffer1[idx + cols] +
buffer1[idx - 1] +
buffer1[idx + 1]
) * 0.5 - buffer2[idx];
val *= damping;
buffer2[idx] = val;
}
}
const temp = buffer1;
buffer1 = buffer2;
buffer2 = temp;
const cx = cols / 2;
const cy = rows / 2;
const maxDistSq = (cols * cols + rows * rows) / 4;
for (let y = 1; y < rows - 1; y++) {
const yOffset = y * cols;
for (let x = 1; x < cols - 1; x++) {
const idx = yOffset + x;
const dx = buffer1[idx + 1] - buffer1[idx - 1];
const dy = buffer1[idx + cols] - buffer1[idx - cols];
const spec = Math.max(0, -dx + dy);
const highlight = Math.pow(Math.min(1.0, spec * 0.04), 4.0) * 160;
const rx = Math.max(0, Math.min(cols - 1, x + Math.floor(dx * 0.12)));
const ry = Math.max(0, Math.min(rows - 1, y + Math.floor(dy * 0.12)));
const distSq = (rx - cx) * (rx - cx) + (ry - cy) * (ry - cy);
const radialGlow = Math.max(0, 1.0 - distSq / maxDistSq);
let r = 6 + radialGlow * 12;
let g = 8 + radialGlow * 10;
let b = 14 + radialGlow * 18;
const h = buffer1[idx];
if (h > 0) {
r += h * 0.12;
g += h * 0.28;
b += h * 0.45;
} else if (h < 0) {
r += -h * 0.25;
g += -h * 0.08;
b += -h * 0.45;
}
r += highlight;
g += highlight;
b += highlight;
const pixelIdx = idx * 4;
data[pixelIdx] = r < 0 ? 0 : (r > 255 ? 255 : r);
data[pixelIdx + 1] = g < 0 ? 0 : (g > 255 ? 255 : g);
data[pixelIdx + 2] = b < 0 ? 0 : (b > 255 ? 255 : b);
data[pixelIdx + 3] = 255;
}
}
offscreenCtx.putImageData(imgData, 0, 0);
ctx.drawImage(
offscreenCanvas,
1, 1, cols - 2, rows - 2,
0, 0, width, height
);
requestAnimationFrame(render);
}
function cleanup() {
active = false;
window.removeEventListener('resize', resize);
window.removeEventListener('mousedown', handleMouseDown);
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('touchstart', handleTouchStart);
window.removeEventListener('touchmove', handleTouchMove);
window.removeEventListener('message', handleMessage);
if (observer) {
observer.disconnect();
}
}
requestAnimationFrame(render);
})();
</script>
` }}
/>
</div>
);
}Raw CSS Stylesheet Snippet
.kinetic-water-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #050608;
overflow: hidden;
}
#canvas-kinetic-water {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
display: block;
}