Hero waves are cool
Leverage the power of canvas to create a beautiful hero section
Installation
Install dependencies
npm i framer-motion clsx tailwind-merge simplex-noise
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Copy the source code
components/ui/wavy-background.tsx
"use client";
import { cn } from "@/lib/utils";
import React, { useEffect, useRef, useState } from "react";
import { createNoise3D } from "simplex-noise";
export const WavyBackground = ({
children,
className,
containerClassName,
colors,
waveWidth,
backgroundFill,
blur = 10,
speed = "fast",
waveOpacity = 0.5,
...props
}: {
children?: any;
className?: string;
containerClassName?: string;
colors?: string[];
waveWidth?: number;
backgroundFill?: string;
blur?: number;
speed?: "slow" | "fast";
waveOpacity?: number;
[key: string]: any;
}) => {
const noise = createNoise3D();
let w: number,
h: number,
nt: number,
i: number,
x: number,
ctx: any,
canvas: any;
const canvasRef = useRef<HTMLCanvasElement>(null);
const getSpeed = () => {
switch (speed) {
case "slow":
return 0.001;
case "fast":
return 0.002;
default:
return 0.001;
}
};
const init = () => {
canvas = canvasRef.current;
ctx = canvas.getContext("2d");
w = ctx.canvas.width = window.innerWidth;
h = ctx.canvas.height = window.innerHeight;
ctx.filter = `blur(${blur}px)`;
nt = 0;
window.onresize = function () {
w = ctx.canvas.width = window.innerWidth;
h = ctx.canvas.height = window.innerHeight;
ctx.filter = `blur(${blur}px)`;
};
render();
};
const waveColors = colors ?? [
"#38bdf8",
"#818cf8",
"#c084fc",
"#e879f9",
"#22d3ee",
];
const drawWave = (n: number) => {
nt += getSpeed();
for (i = 0; i < n; i++) {
ctx.beginPath();
ctx.lineWidth = waveWidth || 50;
ctx.strokeStyle = waveColors[i % waveColors.length];
for (x = 0; x < w; x += 5) {
var y = noise(x / 800, 0.3 * i, nt) * 100;
ctx.lineTo(x, y + h * 0.5); // adjust for height, currently at 50% of the container
}
ctx.stroke();
ctx.closePath();
}
};
let animationId: number;
const render = () => {
ctx.fillStyle = backgroundFill || "black";
ctx.globalAlpha = waveOpacity || 0.5;
ctx.fillRect(0, 0, w, h);
drawWave(5);
animationId = requestAnimationFrame(render);
};
useEffect(() => {
init();
return () => {
cancelAnimationFrame(animationId);
};
}, []);
const [isSafari, setIsSafari] = useState(false);
useEffect(() => {
// I'm sorry but i have got to support it on safari.
setIsSafari(
typeof window !== "undefined" &&
navigator.userAgent.includes("Safari") &&
!navigator.userAgent.includes("Chrome")
);
}, []);
return (
<div
className={cn(
"h-screen flex flex-col items-center justify-center",
containerClassName
)}
>
<canvas
className="absolute inset-0 z-0"
ref={canvasRef}
id="canvas"
style={{
...(isSafari ? { filter: `blur(${blur}px)` } : {}),
}}
></canvas>
<div className={cn("relative z-10", className)} {...props}>
{children}
</div>
</div>
);
};
Prop | Type | Default | Description |
---|---|---|---|
children | any | - | The content to be displayed on top of the wavy background. |
className | string | - | The CSS class to apply to the content container. |
containerClassName | string | - | The CSS class to apply to the main container. |
colors | string[] | ["#38bdf8", "#818cf8", "#c084fc", "#e879f9", "#22d3ee"] | The colors of the waves. |
waveWidth | number | 50 | The width of the waves. |
backgroundFill | string | "black" | The background color. |
blur | number | 10 | The blur effect applied to the waves. |
speed | "slow" | "fast" | "fast" | The speed of the wave animation. |
waveOpacity | number | 0.5 | The opacity of the waves. |
[key: string] | any | - | Any other props not listed above. |