@firecms/neat

中文文档
1.1.1发布于 2026/11/09SEE LICENSE IN LICENSETypeScriptESM + CJS零依赖近 30 天下载 4.7万

Beautiful 3D gradients for your website

原始使用文档
文档目录26 节
  1. 📦 Installation
  2. 🚀 Quick Start
  3. Basic Usage
  4. React Example
  5. ⚙️ Configuration API
  6. Core Animation
  7. Colors
  8. Visual Effects
  9. Advanced Shaders & Post-Processing
  10. 3D Geometries & Shapes
  11. Camera Settings
  12. Background
  13. Performance
  14. Scroll Integration
  15. Flow Field (Distortion)
  16. Procedural Texture Overlay
  17. 🛠️ API Methods
  18. destroy()
  19. 🎨 Dynamic Property Updates
  20. 🪞 One gradient, many canvases
  21. 📖 TypeScript Support
  22. 📄 License
  23. Remove the NEAT Watermark
  24. 🙏 Credits
  25. 🐛 Issues & Contributing
  26. 🔗 Links

🌈 Neat Gradients

Create stunning, animated 3D gradients with hardware-accelerated WebGL performance.

npm version License: MIT + Commons Clause

✨ Try the Interactive Editor ✨

Design your perfect gradient with our visual editor, featuring 20+ presets and real-time preview. Export the config and use it in your project instantly.

Neat Gradient Examples


📦 Installation

npm install @firecms/neat

or

yarn add @firecms/neat

🚀 Quick Start

Basic Usage

import { NeatGradient } from "@firecms/neat";

const gradient = new NeatGradient({
    ref: document.getElementById("canvas"),
    colors: [
        { color: "#FF5772", enabled: true },
        { color: "#4CB4BB", enabled: true },
        { color: "#FFC600", enabled: true },
        { color: "#8B6AE6", enabled: true },
        { color: "#2E0EC7", enabled: true }
    ],
    speed: 4,
    waveAmplitude: 5,
    backgroundColor: "#003FFF",
    backgroundAlpha: 1
});

// Clean up when done (important for React, Vue, etc.)
gradient.destroy();

React Example

import { useEffect, useRef } from "react";
import { NeatGradient, NeatConfig } from "@firecms/neat";

function BackgroundGradient() {
    const canvasRef = useRef<HTMLCanvasElement>(null);
    const gradientRef = useRef<NeatGradient | null>(null);

    useEffect(() => {
        if (!canvasRef.current) return;

        gradientRef.current = new NeatGradient({
            ref: canvasRef.current,
            colors: [
                { color: "#FF5772", enabled: true },
                { color: "#4CB4BB", enabled: true },
                { color: "#FFC600", enabled: true }
            ],
            speed: 3,
            waveAmplitude: 5
        });

        return () => gradientRef.current?.destroy();
    }, []);

    return (
        <canvas
            ref={canvasRef}
            style={{
                position: "fixed",
                top: 0,
                left: 0,
                width: "100%",
                height: "100%",
                zIndex: -1
            }}
        />
    );
}

⚙️ Configuration API

Core Animation

PropertyTypeDefaultRangeDescription
speednumber40-10Animation speed (0 = static)
waveAmplitudenumber30-10Wave height intensity
waveFrequencyXnumber50-10Horizontal wave frequency
waveFrequencyYnumber50-10Vertical wave frequency

Colors

PropertyTypeDefaultDescription
colorsNeatColor[]RequiredArray of color objects (up to 6)
colorBlendingnumber5How colors mix together (0-10)
colorBrightnessnumber1Overall brightness multiplier
colorSaturationnumber0Color saturation adjustment (-10 to 10)
horizontalPressurenumber3Horizontal color distribution (0-10)
verticalPressurenumber3Vertical color distribution (0-10)

Color Object:

{
    color: string;      // Hex color (e.g., "#FF5772")
    enabled: boolean;   // Toggle color on/off
    influence?: number; // Color strength (0-1, optional)
}

Visual Effects

PropertyTypeDefaultDescription
shadowsnumber4Shadow intensity (0-10)
highlightsnumber4Highlight intensity (0-10)
grainIntensitynumber0.55Film grain amount (0-1)
grainScalenumber2Grain size
grainSparsitynumber0.0Grain distribution sparsity (0-1)
grainSpeednumber0.1Grain animation speed
wireframebooleanfalseShow wireframe mesh

Advanced Shaders & Post-Processing

Domain Warping

PropertyTypeDefaultDescription
domainWarpEnabledbooleanfalseEnable domain warping distortion
domainWarpIntensitynumber0.5Strength of domain warping
domainWarpScalenumber1.0Spatial frequency scale of warping

Vignette

PropertyTypeDefaultDescription
vignetteIntensitynumber0.0Darkness intensity at corners (0-1)
vignetteRadiusnumber0.8Radial falloff start distance

Fresnel (Rim Glow)

PropertyTypeDefaultDescription
fresnelEnabledbooleanfalseEnable glowing outer edge shader effect
fresnelPowernumber2.0Falloff exponent of the rim glow
fresnelIntensitynumber0.5Brightness of the glow effect
fresnelColorstring"#FFFFFF"Color of the rim glow (hex)

Iridescence

PropertyTypeDefaultDescription
iridescenceEnabledbooleanfalseEnable soap-bubble style color shifting
iridescenceIntensitynumber0.5Strength of the color shift effect
iridescenceSpeednumber1.0Color cycle speed

Bloom (Fake Glow)

PropertyTypeDefaultDescription
bloomIntensitynumber0.0Intensity of the glow bleeding from highlights
bloomThresholdnumber0.7Brightness threshold for bloom candidate pixels

Chromatic Aberration

PropertyTypeDefaultDescription
chromaticAberrationnumber0.0Lens color channel splitting distance

3D Geometries & Shapes

PropertyTypeDefaultDescription
shapeType'plane' | 'sphere' | 'torus' | 'cylinder' | 'ribbon''plane'3D shape geometry to render the gradient on
shapeRotationXnumber0Manual X rotation (radians)
shapeRotationYnumber0Manual Y rotation (radians)
shapeRotationZnumber0Manual Z rotation (radians)
shapeAutoRotateSpeedXnumber0Auto-rotation speed on X-axis
shapeAutoRotateSpeedYnumber0Auto-rotation speed on Y-axis
sphereRadiusnumber15Radius of the sphere shape
torusRadiusnumber15Torus primary ring radius
torusTubenumber5Torus inner tube thickness
cylinderRadiusnumber10Radius of the cylinder shape
cylinderHeightnumber40Height of the cylinder shape
planeBendnumber0Bending distortion applied to the plane geometry
planeTwistnumber0Twisting distortion applied to the plane geometry
silhouetteFadenumber0.25Edge transparency fade for sphere/torus
cylinderFadenumber0.08Transparency fade towards the ends of the cylinder
ribbonFadenumber0.05Transparency fade towards the ends of the ribbon
flatShadingbooleantrueUse flat shading for geometry normals

Camera Settings

PropertyTypeDefaultDescription
cameraLockbooleanfalseLock camera controls and prevent drag rotation
cameraXnumber0Camera offset along X-axis
cameraYnumber0Camera offset along Y-axis
cameraZnumber0Camera offset along Z-axis
cameraRotationXnumber0Camera pitch rotation (radians)
cameraRotationYnumber0Camera yaw rotation (radians)
cameraRotationZnumber0Camera roll rotation (radians)
cameraZoomnumber1.0Camera zoom factor

Background

PropertyTypeDefaultDescription
backgroundColorstring"#FFFFFF"Background color (hex)
backgroundAlphanumber1Background opacity (0-1)

Performance

PropertyTypeDefaultDescription
resolutionnumber1Mesh density (0.1-2, lower = better performance)
renderScalenumber1Drawing buffer size relative to the canvas' CSS size (0.1-3)

resolution is mesh density, not pixel resolution. It scales the displacement grid — 240x240 segments for a plane at 1, 120x120 for the 3D shapes. Most of the per-frame cost is in the vertex shader, so this is the first thing to turn down. The grid is also capped to roughly one segment per 6 canvas pixels, so a small canvas never pays for detail it cannot show.

renderScale is the pixel one. At 0.75 the gradient renders 44% fewer pixels and the browser scales the result up — usually invisible behind content, and the cheapest win on low-end devices. It needs the canvas to be sized by CSS; if the layout size comes from the width/height attributes, scaling is ignored (with a warning) so the element cannot shrink itself on every resize.

speed: 0 costs nothing. With the clock stopped every frame would be identical, so the render loop parks itself and only wakes when you change a property. A static gradient is a one-off render, not a 60fps redraw of the same image.

Scroll Integration

PropertyTypeDefaultDescription
yOffsetnumber0Vertical scroll offset
yOffsetWaveMultipliernumber4How much scroll affects waves (0-20)
yOffsetColorMultipliernumber4How much scroll affects colors (0-20)
yOffsetFlowMultipliernumber4How much scroll affects flow field (0-20)

Flow Field (Distortion)

PropertyTypeDefaultDescription
flowEnabledbooleantrueEnable flow field distortion
flowDistortionAnumber0Primary distortion amplitude
flowDistortionBnumber0Secondary distortion frequency
flowScalenumber1Overall flow field scale
flowEasenumber0Flow field smoothing (0-1)

Procedural Texture Overlay

PropertyTypeDefaultDescription
enableProceduralTexturebooleanfalseEnable texture overlay
textureMode'bitmap' | 'baked''bitmap'How the texture is drawn — see below
textureBakeResolutionnumber0Baked texture size; 0 derives it from the canvas
bakeEdgeSoftnessnumber1Antialiasing width when baking, in output texels
textureVoidLikelihoodnumber0.45Gap frequency in texture (0-1)
textureVoidWidthMinnumber200Minimum gap width
textureVoidWidthMaxnumber486Maximum gap width
textureBandDensitynumber2.15Texture band density
textureColorBlendingnumber0.01Color mixing in texture (0-1)
textureSeednumber333Random seed for texture
textureEasenumber0.5Flow/Image blend (0=flow, 1=image)
transparentTextureVoidbooleanfalseRender voids as transparent instead of using proceduralBackgroundColor
proceduralBackgroundColorstring"#000000"Texture void color
textureShapeTrianglesnumber20Number of triangle shapes
textureShapeCirclesnumber15Number of circle shapes
textureShapeBarsnumber15Number of bar shapes
textureShapeSquigglesnumber10Number of squiggle shapes

Bitmap vs baked

textureMode decides how the shapes above are drawn into the texture. Both modes read the same generated artwork, so a given textureSeed produces the same composition either way, and both end up as an ordinary mipmapped texture — runtime cost is identical. What differs is how sharp that texture is.

bitmap (default) draws the shapes through Canvas2D at a hardcoded 1024px. Edges land on that grid, so once the camera magnifies the texture they soften and diagonals show the stair-stepping of the grid they were drawn on.

baked rasterizes the same shapes analytically on the GPU: exact per-pixel edge coverage, at a resolution derived from the canvas rather than fixed. It is also the faster of the two to generate — around 20 ms against 35 ms for the Canvas2D path on a full-screen hero, because it skips the CPU drawing and the upload.

bitmapbaked
resolution1024, alwaysfrom canvas, 1024–2048 (4096 opt-in)
edge qualityCanvas2D antialiasingexact analytic coverage
generation~35 ms~20 ms
runtimeone texture fetchone texture fetch
memory~5.5 MB~22 MB at 2048

Three limits to know about:

  • Squiggles are not supported when baking and are dropped with a warning. Cubic Béziers have no closed-form distance function, so they would cost roughly ten times what every other shape does.
  • baked needs WebGL2 (texelFetch and float textures) and falls back to bitmap otherwise. Read activeTextureMode for the mode actually in use.
  • Memory is per instance. WebGL textures cannot be shared across contexts, and every NeatGradient has its own canvas and context, so a page with several of them pays for each. That is why the default caps at 2048 rather than 4096; raise textureBakeResolution deliberately.
const gradient = new NeatGradient({
    ref: canvas,
    enableProceduralTexture: true,
    textureMode: "baked",
    textureShapeSquiggles: 0,   // not available when baking
    textureBakeResolution: 0,   // 0 = derive from the canvas
    bakeEdgeSoftness: 1.0,      // antialiasing width, in output texels
    // ...
});

gradient.activeTextureMode; // 'baked', or 'bitmap' if it had to fall back
gradient.textureMode = "bitmap"; // switchable at runtime

🛠️ API Methods

destroy()

Cleans up the WebGL context, event listeners, and removes any injected DOM elements. Call this when the component unmounts to prevent memory leaks (essential for React, Vue, etc.).

gradient.destroy();

🎨 Dynamic Property Updates

All properties can be updated in real-time:

// Animation
gradient.speed = 6;
gradient.waveAmplitude = 8;

// Colors
gradient.colors = [
    { color: "#FF0000", enabled: true },
    { color: "#00FF00", enabled: true }
];

// 3D Shape Geometries & Auto-Rotation
gradient.shapeType = "sphere";
gradient.shapeAutoRotateSpeedY = 1.5;

// Advanced Post-Processing Effects
gradient.iridescenceEnabled = true;
gradient.fresnelEnabled = true;
gradient.fresnelColor = "#FF0055";

// Effects
gradient.grainIntensity = 0.5;

// Texture
gradient.enableProceduralTexture = true;
gradient.textureEase = 0.7;

🪞 One gradient, many canvases

Browsers only grant a handful of live WebGL contexts, and every extra NeatGradient runs its own shader. So don't create one per card — create one gradient and mirror it into as many plain 2D canvases as you like. Each mirror can show a different crop, they all stay perfectly in sync, and the cost per mirror is a GPU copy instead of a second render.

This is exactly how the editor previews a gradient as a website hero, a phone screen and a row of avatars at the same time.

The source gradient needs preserveDrawingBuffer: true — without it the drawing buffer is cleared after compositing and there is nothing left to copy:

import { NeatGradient } from "@firecms/neat";

const source = document.getElementById("source") as HTMLCanvasElement;

const gradient = new NeatGradient({
    ref: source,
    preserveDrawingBuffer: true,   // required to read the canvas from outside its own frame
    colors: [
        { color: "#FF5772", enabled: true },
        { color: "#4CB4BB", enabled: true },
        { color: "#FFC600", enabled: true }
    ]
});

Then mirror regions of it wherever you want:

type MirrorOptions = {
    cx?: number;    // horizontal centre of the crop, 0–1
    cy?: number;    // vertical centre of the crop, 0–1
    zoom?: number;  // 1 shows as much as fits, 2 shows half
    fps?: number;   // refresh rate — drop it for small decorative mirrors
};

function mirror(target: HTMLCanvasElement, options: MirrorOptions = {}) {
    const { cx = 0.5, cy = 0.5, zoom = 1, fps = 60 } = options;
    const ctx = target.getContext("2d");
    const interval = 1000 / fps;
    let raf = 0;
    let last = 0;

    const draw = (now: number) => {
        raf = requestAnimationFrame(draw);
        if (!ctx || now - last < interval) return;
        last = now;

        const dpr = Math.min(window.devicePixelRatio || 1, 2);
        const w = Math.round(target.clientWidth * dpr);
        const h = Math.round(target.clientHeight * dpr);
        if (!w || !h || !source.width || !source.height) return;
        if (target.width !== w || target.height !== h) {
            target.width = w;
            target.height = h;
        }

        // Largest crop of the source matching the target's aspect ratio, then zoomed
        const aspect = w / h;
        let cropW = source.width;
        let cropH = source.width / aspect;
        if (cropH > source.height) {
            cropH = source.height;
            cropW = source.height * aspect;
        }
        cropW /= zoom;
        cropH /= zoom;

        const sx = Math.min(Math.max(cx * source.width - cropW / 2, 0), source.width - cropW);
        const sy = Math.min(Math.max(cy * source.height - cropH / 2, 0), source.height - cropH);

        ctx.clearRect(0, 0, w, h);
        ctx.drawImage(source, sx, sy, cropW, cropH, 0, 0, w, h);
    };

    raf = requestAnimationFrame(draw);
    return () => cancelAnimationFrame(raf);
}

// A hero, a tight crop for a card, and a slow-refreshing avatar
mirror(document.getElementById("hero") as HTMLCanvasElement);
mirror(document.getElementById("card") as HTMLCanvasElement, { cx: 0.3, cy: 0.4, zoom: 1.7 });
mirror(document.getElementById("avatar") as HTMLCanvasElement, { cx: 0.7, cy: 0.6, zoom: 10, fps: 10 });

Things worth knowing

  • The source canvas must stay laid out. display: none collapses it to zero size and the gradient stops rendering — hide it with position: fixed; inset: 0; z-index: -1 behind your content, or cover it with an opaque layer, instead.
  • Use one requestAnimationFrame loop for all mirrors rather than one each; the example above is per-mirror for clarity, but a shared ticker iterating a list scales better.
  • drawImage from a WebGL canvas is a cross-context copy — cheap on desktop, noticeable on phones. There, cap the device pixel ratio at 1 and run mirrors at 30 fps, and give small decorative ones (avatars, icons) 10 fps. Nobody can tell on a 40px circle.
  • Mirrors are ordinary canvases, so they take border-radius, mask-image, filter and anything else CSS offers. A tiny mirror blurred to nothing makes a good ambient glow behind a layout.
  • Only the source needs preserveDrawingBuffer. It is also what makes downloadAsPNG() and video capture work.

📖 TypeScript Support

Full TypeScript definitions included:

import { NeatGradient, NeatConfig, NeatColor, NeatController } from "@firecms/neat";

const config: NeatConfig = {
    // ... fully typed config
};

const gradient: NeatController = new NeatGradient(config);

📄 License

Neat is released under the MIT License + The Commons Clause.

You can:

  • ✅ Use freely in personal projects
  • ✅ Use freely in commercial projects (e.g. SaaS landing pages, company websites)
  • ✅ Modify and redistribute (with attribution)
  • ✅ Use in open-source projects

You CANNOT:

  • ❌ Sell the software
  • ❌ Include it in a paid template or theme builder that you sell
  • ❌ Offer the software as a paid service

Remove the NEAT Watermark

Purchase a license key for €12 one-time (per domain) to remove the NEAT watermark and console branding.

Buy a license →

Then pass the key in your config:

const gradient = new NeatGradient({
    ref: canvas,
    colors: [...],
    licenseKey: "NEAT-eyJ0eXBlI..."  // Your license key
});

Each key is locked to the domain you specify at checkout (subdomains included). Development on localhost always works without a key.

The same key removes the watermark from PNG and video exports in the editor: click PRO, then Activate your key, and paste it in.


🙏 Credits

Created by Rebase with ❤️


🐛 Issues & Contributing

Found a bug or have a feature request?



Made with ✨ by the Rebase team

版本与发布记录

@firecms/neat 共发布 34 个版本,首次发布于 2023/11/23;过去 12 个月发布了 23 个版本。

版本发布日期距今
1.1.1latest2026/11/095 天前
1.1.02026/09/221 个月前
1.0.3-canary.20260916111045canary2026/09/161 个月前
1.0.3-canary.202609160945522026/09/161 个月前
1.0.3-canary.202607311732362026/08/013 个月前
1.0.22026/07/223 个月前

关于 @firecms/neat 的常见问题

@firecms/neat 是什么?

Beautiful 3D gradients for your website

如何安装 @firecms/neat?

在项目目录运行 npm install @firecms/neat,或使用 pnpm add / yarn add / bun add。

@firecms/neat 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@firecms/neat 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@firecms/neat 可以商用吗?

@firecms/neat 使用 SEE LICENSE IN LICENSE 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@firecms/neat 的下载量有多少?

根据 npm 官方下载统计,@firecms/neat 在 2026/10/15 – 2026/11/13 这 30 天内被下载 47,398 次,日均约 1,580 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@firecms/neat 还在维护吗?

最新版本 1.1.1 发布于 2026/11/09(5 天前);至今共发布 34 个版本,其中过去 12 个月发布了 23 个;首次发布于 2023/11/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。