// FREE TOOLSView all →
◈Keyword ResearchFreeReal Google + Bing suggestions, grouped by intent.◆Image MakerNewBlog thumbnails, OG images & social cards in seconds.
▣Meta & SERP PreviewsoonSee how your page looks in Google, fix title & description.
◐AI Visibility ChecksoonWhat AI assistants say about your brand.
❖Schema GeneratorsoonCopy-paste JSON-LD for FAQ, Article, Product & more.
← all components
#08
Aurora flow background
A living aurora made of layered, sine-driven ribbons painted on canvas with additive blending, so where the ribbons overlap they brighten into real northern lights. It breathes on its own, and your cursor warps the field like a gust of wind. A premium hero or section background in React or plain HTML.
ReactCanvasJSHTML
// LIVE DEMO
Northern lights
Move your cursor to bend the aurora.
// THE CODE
React
"use client";
import { useEffect, useRef } from "react";
export default function AuroraFlow() {
const ref = useRef(null);
useEffect(() => {
const cv = ref.current, ctx = cv.getContext("2d");
if (matchMedia("(prefers-reduced-motion: reduce)").matches) return;
let w, h, raf, t = 0;
const DPR = Math.min(devicePixelRatio || 1, 2);
const m = { x: .5, y: .5, tx: .5, ty: .5 };
const R = [
{ hue: 190, amp: .14, speed: .5, y: .42, w: .20 },
{ hue: 250, amp: .11, speed: .7, y: .52, w: .16 },
{ hue: 300, amp: .09, speed: .9, y: .60, w: .13 },
];
function resize() {
const r = cv.getBoundingClientRect(); w = r.width; h = r.height;
cv.width = w * DPR; cv.height = h * DPR; ctx.setTransform(DPR,0,0,DPR,0,0);
}
function frame() {
t += .006;
m.x += (m.tx - m.x) * .06; m.y += (m.ty - m.y) * .06;
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "#05070f"; ctx.fillRect(0,0,w,h);
ctx.globalCompositeOperation = "lighter";
const wx = (m.x - .5) * .6, wy = (m.y - .5) * .4;
for (const rb of R) {
ctx.beginPath();
const n = 48;
for (let i = 0; i <= n; i++) {
const px = i / n;
const wave = Math.sin(px*6 + t*rb.speed*3)*rb.amp + Math.sin(px*11 - t*rb.speed*2)*rb.amp*.5;
const bend = wx * Math.sin(px * Math.PI);
const y = (rb.y + wave + wy*.3 + bend*.15) * h, x = px * w;
i ? ctx.lineTo(x,y) : ctx.moveTo(x,y);
}
const my = (rb.y + wy*.3) * h;
const g = ctx.createLinearGradient(0, my - rb.w*h, 0, my + rb.w*h);
g.addColorStop(0, `hsla(${rb.hue},90%,60%,0)`);
g.addColorStop(.5, `hsla(${rb.hue},95%,65%,.28)`);
g.addColorStop(1, `hsla(${rb.hue},90%,60%,0)`);
ctx.strokeStyle = g; ctx.lineWidth = rb.w*h; ctx.lineCap = "round";
ctx.shadowBlur = 40; ctx.shadowColor = `hsla(${rb.hue},95%,60%,.5)`; ctx.stroke();
}
ctx.shadowBlur = 0;
const gx = m.x*w, gy = m.y*h, gg = ctx.createRadialGradient(gx,gy,0,gx,gy,120);
gg.addColorStop(0,"rgba(129,140,248,.18)"); gg.addColorStop(1,"rgba(0,0,0,0)");
ctx.fillStyle = gg; ctx.fillRect(0,0,w,h);
ctx.globalCompositeOperation = "source-over";
raf = requestAnimationFrame(frame);
}
function onMove(e){ const r = cv.getBoundingClientRect(); m.tx = (e.clientX-r.left)/w; m.ty = (e.clientY-r.top)/h; }
resize(); frame();
addEventListener("resize", resize); cv.addEventListener("pointermove", onMove);
return () => { cancelAnimationFrame(raf); removeEventListener("resize", resize); cv.removeEventListener("pointermove", onMove); };
}, []);
return <canvas ref={ref} className="w-full h-[380px] rounded-2xl bg-[#05070f]" />;
}// HOW IT WORKS
- 01Three ribbons are drawn as thick, soft gradient strokes; each follows a sum of sine waves so it undulates organically.
- 02Drawing with the 'lighter' blend mode means overlapping ribbons add up into brighter aurora bands instead of covering each other.
- 03The cursor position is eased and fed back into the wave math, bending the ribbons toward it and adding a soft radial glow.
- 04A shadowBlur on each stroke gives the glow depth. Respects prefers-reduced-motion.
Want a custom component for your product?
Let's build it →