Introducing Nuvyx UI v1.0.0

Bubbles Background

An interactive fluid bubble background component with animated colorful blobs that respond to user interaction.

Installation Guide

1

Install Dependencies

Tailwind CSS

npm install tailwindcss postcss autoprefixer && npx tailwindcss init -p
2

Setup Configuration

Create file: /lib/utils.ts
/lib/utils.ts
Configuration
1import { clsx, type ClassValue } from "clsx";
2import { twMerge } from "tailwind-merge";
3
4export function cn(...inputs: ClassValue[]) {
5    return twMerge(clsx(inputs));
6}
3

Copy Component Code

Bubbles Background.tsx
TypeScript
1"use client";
2
3import React, { useEffect, useRef } from "react";
4import type { CSSProperties } from "react";
5
6interface BubblesProps {
7  backgroundColorA?: string;
8  backgroundColorB?: string;
9  bubbleColors?: {
10    colorA?: string;
11    colorB?: string;
12    colorC?: string;
13    colorD?: string;
14    colorE?: string;
15    interactive?: string;
16  };
17  /** Any valid CSS `mix-blend-mode` value */
18  blendMode?: CSSProperties["mixBlendMode"];
19  bubbleSize?: string;
20}
21
22const BubbleBackground: React.FC<BubblesProps> = ({
23  backgroundColorA = "rgb(108, 0, 162)",
24  backgroundColorB = "rgb(0, 17, 82)",
25  bubbleColors = {
26    colorA: "18, 113, 255",
27    colorB: "221, 74, 255",
28    colorC: "100, 220, 255",
29    colorD: "200, 50, 50",
30    colorE: "180, 180, 50",
31    interactive: "148, 100, 255",
32  },
33  blendMode = "hard-light",
34  bubbleSize = "80%",
35}) => {
36  const interactiveRef = useRef<HTMLDivElement>(null);
37
38  useEffect(() => {
39    let curX = 0;
40    let curY = 0;
41    let tgX = 0;
42    let tgY = 0;
43    const easeFactor = 10;
44
45    function move() {
46      if (!interactiveRef.current) return;
47
48      curX += (tgX - curX) / easeFactor;
49      curY += (tgY - curY) / easeFactor;
50
51      interactiveRef.current.style.transform = `translate(${Math.round(
52        curX
53      )}px, ${Math.round(curY)}px)`;
54      requestAnimationFrame(move);
55    }
56
57    const handlePointerMove = (e: PointerEvent) => {
58      tgX = e.clientX;
59      tgY = e.clientY;
60    };
61
62    window.addEventListener("pointermove", handlePointerMove);
63    move();
64
65    return () => {
66      window.removeEventListener("pointermove", handlePointerMove);
67    };
68  }, []);
69
70  const bounceVAnimation = `
71    @keyframes bounceV {
72      0% { transform: translateY(-50%); }
73      50% { transform: translateY(50%); }
74      100% { transform: translateY(-50%); }
75    }
76  `;
77
78  const bounceHAnimation = `
79    @keyframes bounceH {
80      0% { transform: translateX(-50%) translateY(-10%); }
81      50% { transform: translateX(50%) translateY(10%); }
82      100% { transform: translateX(-50%) translateY(-10%); }
83    }
84  `;
85
86  const moveInCircleAnimation = `
87    @keyframes moveInCircle {
88      0% { transform: rotate(0deg); }
89      50% { transform: rotate(180deg); }
90      100% { transform: rotate(360deg); }
91    }
92  `;
93
94  const gooFilter = `
95    <filter id="goo">
96      <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />
97      <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -8" result="goo" />
98      <feBlend in="SourceGraphic" in2="goo" />
99    </filter>
100  `;
101
102  return (
103    <>
104      <style jsx global>{`
105        @import url("https://fonts.googleapis.com/css2?family=DynaPuff:wght@400..700&display=swap");
106        ${bounceVAnimation}
107        ${bounceHAnimation}
108        ${moveInCircleAnimation}
109      `}</style>
110
111      <div
112        className="w-screen h-screen relative overflow-hidden"
113        style={{
114          background: `linear-gradient(40deg, ${backgroundColorA}, ${backgroundColorB})`,
115        }}
116      >
117        <svg
118          className="hidden"
119          xmlns="http://www.w3.org/2000/svg"
120          dangerouslySetInnerHTML={{ __html: gooFilter }}
121        />
122
123        <div
124          className="w-full h-full"
125          style={{
126            filter: "url(#goo) blur(40px)",
127          }}
128        >
129          <div
130            className="absolute opacity-100"
131            style={{
132              width: bubbleSize,
133              height: bubbleSize,
134              top: `calc(50% - ${bubbleSize} / 2)`,
135              left: `calc(50% - ${bubbleSize} / 2)`,
136              background: `radial-gradient(circle at center, rgba(${bubbleColors.colorA}, 0.8) 0, rgba(${bubbleColors.colorA}, 0) 50%) no-repeat`,
137              mixBlendMode: blendMode,
138              transformOrigin: "center center",
139              animation: "bounceV 30s ease infinite",
140            }}
141          ></div>
142
143          <div
144            className="absolute opacity-100"
145            style={{
146              width: bubbleSize,
147              height: bubbleSize,
148              top: `calc(50% - ${bubbleSize} / 2)`,
149              left: `calc(50% - ${bubbleSize} / 2)`,
150              background: `radial-gradient(circle at center, rgba(${bubbleColors.colorB}, 0.8) 0, rgba(${bubbleColors.colorB}, 0) 50%) no-repeat`,
151              mixBlendMode: blendMode,
152              transformOrigin: "calc(50% - 400px)",
153              animation: "moveInCircle 20s reverse infinite",
154            }}
155          ></div>
156
157          <div
158            className="absolute opacity-100"
159            style={{
160              width: bubbleSize,
161              height: bubbleSize,
162              top: `calc(50% - ${bubbleSize} / 2 + 200px)`,
163              left: `calc(50% - ${bubbleSize} / 2 - 500px)`,
164              background: `radial-gradient(circle at center, rgba(${bubbleColors.colorC}, 0.8) 0, rgba(${bubbleColors.colorC}, 0) 50%) no-repeat`,
165              mixBlendMode: blendMode,
166              transformOrigin: "calc(50% + 400px)",
167              animation: "moveInCircle 40s linear infinite",
168            }}
169          ></div>
170          <div
171            className="absolute opacity-70"
172            style={{
173              width: bubbleSize,
174              height: bubbleSize,
175              top: `calc(50% - ${bubbleSize} / 2)`,
176              left: `calc(50% - ${bubbleSize} / 2)`,
177              background: `radial-gradient(circle at center, rgba(${bubbleColors.colorD}, 0.8) 0, rgba(${bubbleColors.colorD}, 0) 50%) no-repeat`,
178              mixBlendMode: blendMode,
179              transformOrigin: "calc(50% - 200px)",
180              animation: "bounceH 40s ease infinite",
181            }}
182          ></div>
183
184          <div
185            className="absolute opacity-100"
186            style={{
187              width: `calc(${bubbleSize} * 2)`,
188              height: `calc(${bubbleSize} * 2)`,
189              top: `calc(50% - ${bubbleSize})`,
190              left: `calc(50% - ${bubbleSize})`,
191              background: `radial-gradient(circle at center, rgba(${bubbleColors.colorE}, 0.8) 0, rgba(${bubbleColors.colorE}, 0) 50%) no-repeat`,
192              mixBlendMode: blendMode,
193              transformOrigin: "calc(50% - 800px) calc(50% + 200px)",
194              animation: "moveInCircle 20s ease infinite",
195            }}
196          ></div>
197
198          <div
199            ref={interactiveRef}
200            className="absolute w-full h-full opacity-70"
201            style={{
202              top: "-50%",
203              left: "-50%",
204              background: `radial-gradient(circle at center, rgba(${bubbleColors.interactive}, 0.8) 0, rgba(${bubbleColors.interactive}, 0) 50%) no-repeat`,
205              mixBlendMode: blendMode,
206            }}
207          ></div>
208        </div>
209      </div>
210    </>
211  );
212};
213
214export default BubbleBackground;
215
4

Final Steps

Update Import Paths

Make sure to update the import paths in the component code to match your project structure. For example, change @/components/ui/button to match your UI components location.

Props

NameTypeDefaultDescription
backgroundColorAstring"rgb(108, 0, 162)"First color for the background gradient.
backgroundColorBstring"rgb(0, 17, 82)"Second color for the background gradient.
bubbleColorsobject{ colorA: "18, 113, 255", colorB: "221, 74, 255", colorC: "100, 220, 255", colorD: "200, 50, 50", colorE: "180, 180, 50", interactive: "148, 100, 255" }RGB color values for different bubbles and the interactive bubble.
blendModestring"hard-light"CSS blend mode for the bubble elements.
bubbleSizestring"80%"Size of the bubble elements relative to the container.

Examples