webdevpuneet.com

Wavy Background — Free HTML CSS JS Canvas Hero Snippet

Wavy Background · Animations · Plain HTML, CSS & JS · Live preview

CategoryAnimations
TagsCanvas

Copy & paste

Source Code

<section class="wb-hero">
  <canvas class="wb-canvas" id="wbCanvas" aria-hidden="true"></canvas>
  <div class="wb-content">
    <h1>Ride the wave</h1>
    <p>Layered animated waves rendered on a canvas — smooth at 60fps, zero images.</p>
    <button type="button" class="wb-btn">Get the kit</button>
  </div>
</section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

HiDPI-sharp canvas
devicePixelRatio scaling keeps waves crisp.
Filled wave paths
Each wave fills the area beneath its curve.
Summed-sine crests
Two sines per layer avoid a mechanical look.
Additive glow blend
lighter compositing brightens overlaps.
Independent layers
Per-layer amplitude, speed, and anchor.
Cheap 6px sampling
Smooth curves at a fraction of the cost.
Capped DPR
Limits pixel work on phones for performance.
Legible overlay
Content sits above with a soft text-shadow.

About this UI Snippet

Wavy Background — Layered Animated Canvas Waves

Screenshot of the Wavy Background snippet rendered live

The wavy background is the flowing, colorful hero backdrop that gives landing pages a sense of motion and depth: several translucent waves of different colors rolling at different speeds, blended into a soft glow. This snippet renders it on an HTML <canvas> with plain vanilla JavaScript — smooth at 60fps, sharp on retina displays, and weighing nothing because there are no images.

Sharp on every display

Canvas is resolution-dependent, so the resize function reads devicePixelRatio (capped at 2 to protect performance on phones), sets the canvas's internal pixel buffer to clientWidth * dpr by clientHeight * dpr, then calls ctx.setTransform(dpr, 0, 0, dpr, 0, 0) so all drawing is done in CSS pixels but rendered at device resolution. That's the standard recipe for crisp canvas graphics — without it the waves would look blurry on HiDPI screens. The handler also re-runs on window resize so the scene always fits.

Drawing a wave as a filled path

Each wave is a filled shape, not a stroked line. The path starts at the bottom-left corner, walks across the width sampling a y value every 6 pixels, then closes back along the bottom edge — so the area under the curve fills with color. Sampling every 6px instead of every pixel is a deliberate trade: the curve stays visually smooth while doing a fraction of the work, which keeps the loop cheap even with three layers.

Organic crests from summed sines

A single Math.sin produces a mechanical, obviously-repeating wave. To make the motion feel natural, each layer sums two sines of different wavelengths and speeds — a primary wave plus a smaller, faster harmonic at 2.3× the frequency. The superposition means crests vary in height and the pattern doesn't visibly loop, which is what separates a convincing wave from a clearly synthetic one. Each layer has its own amplitude, wavelength, speed, and vertical anchor so they roll independently.

Additive glow blending

Where the translucent waves overlap, they should brighten rather than muddy. Setting ctx.globalCompositeOperation = 'lighter' before filling makes the colors add together, so intersections glow — purple over cyan reads as a luminous blend instead of a dull mix. The mode is reset to source-over after the layers so it doesn't affect anything else. The waves' own rgba alpha keeps each one translucent.

The animation loop

A requestAnimationFrame loop increments a time counter t, clears the canvas, and redraws all layers each frame. Because each layer's phase is t * speed, the waves drift at their own rates, and the cheap 6px sampling plus additive fills keep the whole thing comfortably at 60fps. The text content sits above the canvas at a higher z-index with a soft text-shadow so it stays legible over the bright waves.

Customizing it

Edit the LAYERS array to add waves, recolor them, or change their amplitude, wavelength, speed, and vertical position. Remove the lighter blend for a flatter, layered-paper look, or increase the harmonic factor for choppier water. Lower the DPR cap or sample step if you target very low-end devices. Pair it with a text generate headline or a shimmer button for a vivid hero.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA hero fills with layered colored waves rolling across the bottom.
  2. 2
    Watch the motionEach wave drifts at its own speed and they glow where they overlap.
  3. 3
    Resize the windowThe canvas re-scales and stays crisp on retina screens.
  4. 4
    Recolor the wavesEdit the LAYERS array colors and alpha values.
  5. 5
    Change the shapeTune amplitude, wavelength, speed, and anchor per layer.
  6. 6
    Adjust the blendRemove lighter for a flat layered look.

Real-world uses

Common Use Cases

Flowing hero canvases
Put flowing translucent waves behind a text generate headline, with each wave rolling at its own speed for depth.
Music and audio sites
Pair with an audio waveform visualiser so a decorative background and a functional one share a visual theme.
App marketing sections
Frame an app hero with gentle motion, using HiDPI scaling so wave edges stay crisp on retina screens.
Conference and launch microsites
Add a shimmer button over the waves, where additive lighter blending brightens overlaps into a soft glow.
Section divider alternatives
Offer a richer, animated option than a static wave divider, with two summed sines per layer avoiding a mechanical look.

Got questions?

Frequently Asked Questions

The resize function reads devicePixelRatio (capped at 2), sets the canvas pixel buffer to the CSS size times that ratio, and calls setTransform(dpr,0,0,dpr,0,0). Drawing is then done in CSS pixels but rendered at device resolution. Without this, canvas graphics look blurry on HiDPI displays.

A single sine produces an obviously mechanical, repeating wave. Adding a smaller, faster harmonic at 2.3 times the frequency makes crests vary in height and stops the pattern from visibly looping, which is what makes the water look organic rather than synthetic.

Before filling the waves, the code sets globalCompositeOperation to lighter, which adds overlapping colors together instead of painting over them. So where translucent purple crosses cyan, the pixels brighten into a luminous blend. The mode is reset to source-over afterward so it doesn't affect other drawing.

Yes. The curve is sampled every 6 pixels rather than every pixel, the device pixel ratio is capped at 2, and each wave is a single filled path. Together these keep three animated layers comfortably at 60fps. For very low-end targets you can raise the sample step or lower the DPR cap further.

Put the canvas behind your content with a ref, and run the resize plus rAF draw loop in a mount effect, cancelling the frame and removing the resize listener on unmount. Keep the time counter and layer config in refs or module scope so re-renders don't reset the animation. The component is framework-agnostic; in Tailwind just position the canvas absolute inset-0 behind a relative z-10 content layer.