Liquid Smoke
Slow plumes of smoke rising from the bottom of a section, drawn with warped noise on the GPU.
Installation
pnpm dlx shadcn@latest add @uigrove/liquid-smokeDependencies: none. Installs @uigrove/dot-canvas alongside.
First time? Add the registry to components.json
{
"registries": {
"@uigrove": "https://uigrove.me/r/{name}.json"
}
}Usage
import { LiquidSmoke } from '@/components/uigrove/liquid-smoke'
<LiquidSmoke className="absolute inset-0" />Props
| Name | Type | Default | Description |
|---|---|---|---|
| color | string | '#f5f5f7' | Smoke color at full intensity |
| background | string | '#09090c' | Background |
| intensity | number | 0.7 | Intensity |
| scale | number | 2.2 | Noise frequency, higher is finer |
| speed | number | 0.35 | Speed |
| height | number | 0.7 | How far up the plumes reach |
| warp | number | 2.4 | Domain warping, more turbulence |
| grain | number | 0.25 | Grain |
| seed | number | 3 | Seed |
| className | string | — | Classes for the root element. Use it for sizing and position. |
Code
'use client'
import { useEffect, useRef, type CSSProperties } from 'react'
import { useDotCanvas } from '@/hooks/use-dot-canvas'
import { LiquidSmokeEngine, type LiquidSmokeOptions } from './liquid-smoke.engine'
export interface LiquidSmokeProps extends Partial<LiquidSmokeOptions> {
className?: string
style?: CSSProperties
}
export const liquidSmokeDefaults: LiquidSmokeOptions = {
color: '#f5f5f7',
background: '#09090c',
intensity: 0.7,
scale: 2.2,
speed: 0.35,
height: 0.7,
warp: 2.4,
grain: 0.25,
seed: 3,
}
export function LiquidSmoke({
color = liquidSmokeDefaults.color,
background = liquidSmokeDefaults.background,
intensity = liquidSmokeDefaults.intensity,
scale = liquidSmokeDefaults.scale,
speed = liquidSmokeDefaults.speed,
height = liquidSmokeDefaults.height,
warp = liquidSmokeDefaults.warp,
grain = liquidSmokeDefaults.grain,
seed = liquidSmokeDefaults.seed,
className,
style,
}: LiquidSmokeProps) {
const engineRef = useRef<LiquidSmokeEngine | null>(null)
if (!engineRef.current) engineRef.current = new LiquidSmokeEngine(liquidSmokeDefaults)
const engine = engineRef.current
const { containerRef, canvasRef, wake } = useDotCanvas(engine, {
pointer: false,
kind: 'webgl',
maxDpr: 1,
})
useEffect(() => {
engine.setOptions({ color, background, intensity, scale, speed, height, warp, grain, seed })
wake()
}, [engine, wake, color, background, intensity, scale, speed, height, warp, grain, seed])
return (
<div
ref={containerRef}
className={['uig-liquid-smoke', 'pointer-events-none overflow-hidden', className]
.filter(Boolean)
.join(' ')}
style={{ background, ...style }}
aria-hidden="true"
>
<canvas ref={canvasRef} className="block h-full w-full" />
</div>
)
}