Backgrounds — Component Library Reference
Source: components adapted from React Bits and Svelte Bits (both by DavidHDev). React Bits is licensed MIT + Commons Clause — free to use, modify, and distribute in your own projects (personal or commercial), but the library itself cannot be resold as a standalone product. Svelte Bits is built incrementally from React Bits by the same author and is presumed to carry similar terms; verify at the source if that matters for your use case. This reference bundle is included here for free, under Qofeno's own MIT-licensed repo, as build material for websites, not as a resold library.
name: sparkles code: You are given a task to integrate an existing React component in the codebase
The codebase should support:
- shadcn project structure
- Tailwind CSS
- Typescript
If it doesn't, provide instructions on how to setup project via shadcn CLI, install Tailwind or Typescript.
Determine the default path for components and styles. If default path for components is not /components/ui, provide instructions on why it's important to create this folder Copy-paste this component to /components/ui folder:
sparkles.tsx
"use client"
import { useEffect, useId, useState } from "react"
import Particles, { initParticlesEngine } from "@tsparticles/react"
import { loadSlim } from "@tsparticles/slim"
export function Sparkles({
className,
size = 1,
minSize = null,
density = 800,
speed = 1,
minSpeed = null,
opacity = 1,
opacitySpeed = 3,
minOpacity = null,
color = "#FFFFFF",
background = "transparent",
options = {},
}) {
const [isReady, setIsReady] = useState(false)
useEffect(() => {
initParticlesEngine(async (engine) => {
await loadSlim(engine)
}).then(() => {
setIsReady(true)
})
}, [])
const id = useId()
const defaultOptions = {
background: {
color: {
value: background,
},
},
fullScreen: {
enable: false,
zIndex: 1,
},
fpsLimit: 120,
particles: {
color: {
value: color,
},
move: {
enable: true,
direction: "none",
speed: {
min: minSpeed || speed / 10,
max: speed,
},
straight: false,
},
number: {
value: density,
},
opacity: {
value: {
min: minOpacity || opacity / 10,
max: opacity,
},
animation: {
enable: true,
sync: false,
speed: opacitySpeed,
},
},
size: {
value: {
min: minSize || size / 2.5,
max: size,
},
},
},
detectRetina: true,
}
return isReady && <Particles id={id} options={{ ...defaultOptions, ...options }} className={className} />
}
demo.tsx
import { Sparkles } from "@/components/ui/sparkles"
import { useTheme } from "next-themes"
export function Demo() {
const { theme } = useTheme()
return (
<div className="h-screen w-full overflow-hidden">
<div className="mx-auto mt-32 w-full max-w-2xl">
<div className="text-center text-3xl text-foreground">
<span className="text-indigo-900 dark:text-indigo-200">
Trusted by experts.
</span>
<br />
<span>Used by the leaders.</span>
</div>
<div className="mt-14 grid grid-cols-5 text-zinc-900 dark:text-white">
<Retool />
<Vercel />
<Remote />
<Arc />
<Raycast />
</div>
</div>
<div className="relative -mt-32 h-96 w-full overflow-hidden [mask-image:radial-gradient(50%_50%,white,transparent)]">
<div className="absolute inset-0 before:absolute before:inset-0 before:bg-[radial-gradient(circle_at_bottom_center,#8350e8,transparent_70%)] before:opacity-40" />
<div className="absolute -left-1/2 top-1/2 aspect-[1/0.7] z-10 w-[200%] rounded-[100%] border-t border-zinc-900/20 dark:border-white/20 bg-white dark:bg-zinc-900" />
<Sparkles
density={1200}
className="absolute inset-x-0 bottom-0 h-full w-full [mask-image:radial-gradient(50%_50%,white,transparent_85%)]"
color={theme === "dark" ? "#ffffff" : "#000000"}
/>
</div>
</div>
)
}
// logos
const Retool = () => (
<svg viewBox="0 0 180 56" fill="currentColor" className="w-full">
<path d="M34 18.2a2.2 2.2 0 012.2-2.2h8.6a2.2 2.2 0 012.2 2.2v1.7a1.1 1.1 0 01-1.1 1.1H35.1a1.1 1.1 0 01-1.1-1.1v-1.7zM34 25.1a1.1 1.1 0 011.1-1.1h20.7a2.2 2.2 0 012.2 2.2v5.7a1.1 1.1 0 01-1.1 1.1H36.2a2.2 2.2 0 01-2.2-2.2v-5.7zM45 37.1a1.1 1.1 0 011.1-1.1h10.8a1.1 1.1 0 011.1 1.1v.7a2.2 2.2 0 01-2.2 2.2h-8.6a2.2 2.2 0 01-2.2-2.2v-.7zM71.596 30.741h2.311l4.293 7.017h5.256l-4.76-7.512c2.641-.909 4.182-2.945 4.182-5.89 0-4.127-2.89-6.356-7.54-6.356H67v19.758h4.596v-7.017zm0-3.742V21.88h3.494c2.174 0 3.275.936 3.275 2.56 0 1.595-1.1 2.558-3.275 2.558h-3.494zM91.363 38.06c2.89 0 5.531-1.458 6.605-4.237L94.28 32.64c-.413 1.266-1.486 1.926-2.862 1.926-1.678 0-2.862-1.128-3.164-3.11h9.824v-1.155c0-4.1-2.395-7.348-6.797-7.348-4.183 0-7.265 3.247-7.265 7.54 0 4.513 2.972 7.568 7.347 7.568zm-.138-11.694c1.624 0 2.477 1.1 2.505 2.394H88.39c.44-1.596 1.486-2.394 2.834-2.394zM100.573 33.878c0 2.972 1.569 4.018 4.706 4.018 1.046 0 1.871-.083 2.642-.193v-3.605c-.496.055-.743.083-1.266.083-1.101 0-1.734-.22-1.734-1.431v-5.862h2.834v-3.632h-2.834v-4.018h-4.348v4.018h-1.844v3.632h1.844v6.99zM123.672 30.52c0-4.512-3-7.567-7.265-7.567-4.293 0-7.265 3.055-7.265 7.568s2.972 7.54 7.265 7.54c4.265 0 7.265-3.027 7.265-7.54zm-10.154 0c0-2.53 1.128-3.962 2.889-3.962s2.89 1.431 2.89 3.963-1.129 3.962-2.89 3.962c-1.761 0-2.889-1.43-2.889-3.962zM139.527 30.52c0-4.512-2.999-7.567-7.265-7.567-4.293 0-7.265 3.055-7.265 7.568s2.972 7.54 7.265 7.54c4.266 0 7.265-3.027 7.265-7.54zm-10.154 0c0-2.53 1.128-3.962 2.889-3.962 1.762 0 2.89 1.431 2.89 3.963s-1.128 3.962-2.89 3.962c-1.761 0-2.889-1.43-2.889-3.962zM146 18h-4.403v19.758H146V18z" />
</svg>
)
const Vercel = () => (
<svg viewBox="0 0 180 54" fill="currentColor" className="w-full">
<path d="M89.515 20.5c-4.424 0-7.614 2.925-7.614 7.313 0 4.387 3.59 7.312 8.014 7.312 2.673 0 5.03-1.072 6.488-2.88l-3.066-1.796c-.81.898-2.04 1.422-3.422 1.422-1.919 0-3.55-1.016-4.155-2.64h11.228c.088-.456.14-.927.14-1.423 0-4.383-3.19-7.308-7.613-7.308zm-3.791 5.89c.5-1.62 1.871-2.64 3.787-2.64 1.919 0 3.29 1.02 3.786 2.64h-7.573zm46.938-5.89c-4.424 0-7.613 2.925-7.613 7.313 0 4.387 3.59 7.312 8.014 7.312 2.672 0 5.028-1.072 6.487-2.88l-3.065-1.796c-.81.898-2.04 1.422-3.422 1.422-1.92 0-3.551-1.016-4.156-2.64h11.228c.088-.456.14-.927.14-1.423 0-4.383-3.189-7.308-7.613-7.308zm-3.787 5.89c.501-1.62 1.872-2.64 3.787-2.64 1.919 0 3.29 1.02 3.787 2.64h-7.574zm-15.639 1.422c0 2.438 1.571 4.063 4.007 4.063 1.651 0 2.889-.76 3.526-1.999l3.078 1.8c-1.275 2.153-3.663 3.449-6.604 3.449-4.428 0-7.613-2.925-7.613-7.313 0-4.387 3.189-7.312 7.613-7.312 2.941 0 5.325 1.296 6.604 3.45l-3.078 1.799c-.637-1.24-1.875-1.999-3.526-1.999-2.432 0-4.007 1.625-4.007 4.063zm33.05-11.78v18.687h-3.607V16.03h3.607zM47.806 14l14.806 26H33l14.806-26zm37.016 2.031l-11.103 19.5-11.103-19.5h4.163l6.94 12.188 6.94-12.188h4.163zm23.606 4.875v3.937a4.517 4.517 0 00-1.283-.2c-2.328 0-4.007 1.626-4.007 4.063v6.013h-3.606V20.906h3.606v3.738c0-2.064 2.369-3.738 5.29-3.738z" />
</svg>
)
const Remote = () => (
<svg viewBox="0 0 180 56" fill="currentColor" className="w-full">
<path d="M51.1294 35.0449H51.4609V41H50.4859C44.1484 41 40.4825 37.3997 40.4825 31.503V28.4671L42.5495 27.9416C43.1539 27.7859 43.6999 27.4746 44.1289 27.0269C44.5579 26.5793 44.8504 26.015 44.9869 25.4117C45.1234 24.8084 45.0649 24.1662 44.8504 23.5823C44.6359 22.9985 44.2654 22.4925 43.7779 22.1033C43.2905 21.7141 42.7055 21.4805 42.0815 21.4222C41.4575 21.3638 40.8335 21.4611 40.2875 21.7335C39.722 22.006 39.254 22.4341 38.9225 22.9596C38.591 23.485 38.4155 24.0883 38.4155 24.7111V37.6916H32V24.497C32 24.1078 32.0195 23.6991 32.078 23.3099C32.6825 18.6198 36.7775 15 41.7305 15C46.2349 15 50.0179 17.9775 51.1294 22.0254C51.7144 24.1467 51.5194 26.4042 50.6029 28.4087C49.8229 30.1018 48.5554 31.5225 46.9759 32.4955C47.5219 34.6557 48.6334 35.0449 51.1294 35.0449ZM67.0023 23.6018V27.241H66.3978C65.1498 27.241 64.1749 27.5913 63.4729 28.2725C62.7709 28.9536 62.4199 29.8877 62.4199 31.0749V37.6332H58.8904V23.8159H62.4199V25.6063C63.4729 24.2635 64.7989 23.6018 66.3978 23.6018H67.0023ZM82.1538 32.009H71.4483C71.6628 32.8458 72.1698 33.5853 72.8718 34.0913C73.6128 34.6168 74.5098 34.8892 75.4068 34.8503C76.1673 34.8503 76.9278 34.6946 77.6103 34.3638C78.2343 34.0913 78.7803 33.6632 79.1898 33.1377L81.5493 35.2006C80.8083 36.0763 79.8723 36.7769 78.8193 37.244C77.7078 37.7305 76.4988 37.9835 75.2703 37.9641C73.9053 37.9835 72.5403 37.6527 71.3313 37.0105C70.1808 36.4072 69.2448 35.4925 68.5818 34.3832C67.9383 33.2545 67.5873 31.9895 67.5873 30.7051C67.5873 29.4207 67.9188 28.1362 68.5428 27.0075C69.1668 25.9177 70.0833 25.0225 71.1948 24.4192C72.3453 23.7964 73.6323 23.4656 74.9388 23.485C77.0058 23.485 78.7413 24.1662 80.1258 25.5479C81.5103 26.9296 82.2123 28.6617 82.2123 30.744C82.2513 31.1722 82.2123 31.5808 82.1538 32.009ZM77.1813 27.3578C76.5378 26.8518 75.7578 26.5793 74.9388 26.5793C74.1198 26.5793 73.3398 26.8518 72.6963 27.3578C72.0528 27.8832 71.6043 28.6228 71.4093 29.4207H78.4683C78.2928 28.6033 77.8248 27.8832 77.1813 27.3578ZM104.852 24.9057C105.788 25.8398 106.275 27.0853 106.275 28.6617V37.6527H102.746V29.7126C102.746 28.8563 102.492 28.1946 102.005 27.6886C101.517 27.1826 100.854 26.9296 100.035 26.9296C99.1772 26.9296 98.4752 27.1826 97.9292 27.7081C97.3832 28.2335 97.1297 28.9147 97.1297 29.771V37.6527H93.6002V29.7126C93.6002 28.8757 93.3467 28.1946 92.8592 27.6886C92.3522 27.1826 91.6892 26.9296 90.8702 26.9296C90.4802 26.9102 90.1097 26.9686 89.7392 27.1048C89.3882 27.241 89.0567 27.4551 88.7642 27.7081C88.4912 27.9805 88.2767 28.2919 88.1402 28.6617C88.0037 29.012 87.9452 29.4012 87.9452 29.771V37.6527H84.4158V23.8353H87.9647V25.256C88.9982 24.0883 90.3632 23.5045 92.0597 23.5045C92.9567 23.485 93.8342 23.6796 94.6337 24.0883C95.3747 24.4775 95.9792 25.0419 96.4082 25.7425C97.5587 24.244 99.1187 23.485 101.108 23.485C102.668 23.5045 103.916 23.9716 104.852 24.9057ZM123.143 30.7246C123.143 32.7874 122.441 34.5 121.017 35.8817C119.594 37.2635 117.839 37.9641 115.713 37.9446C113.607 37.9446 111.833 37.244 110.409 35.8623C108.986 34.4805 108.264 32.768 108.264 30.7051C108.264 28.6422 108.986 26.9296 110.409 25.5479C111.833 24.1662 113.607 23.4656 115.713 23.4656C117.819 23.4656 119.594 24.1662 121.017 25.5479C122.441 26.9491 123.162 28.6811 123.143 30.7246ZM118.443 33.4686C119.126 32.729 119.516 31.756 119.516 30.744C119.516 29.732 119.126 28.759 118.443 28.0195C118.092 27.6692 117.663 27.3772 117.195 27.1826C116.727 26.988 116.24 26.8907 115.733 26.8907C115.226 26.8907 114.738 26.988 114.27 27.1826C113.802 27.3772 113.373 27.6497 113.022 28.0195C112.32 28.759 111.93 29.732 111.93 30.744C111.93 31.756 112.32 32.729 113.022 33.4686C113.744 34.1692 114.719 34.5584 115.733 34.5584C116.747 34.5389 117.722 34.1497 118.443 33.4686ZM129.597 27.0464V32.7874C129.597 33.9746 130.241 34.5584 131.508 34.5584C132.132 34.5389 132.756 34.3832 133.322 34.0913V37.3802C132.522 37.7889 131.645 38.003 130.748 37.9835C129.207 37.9835 128.037 37.5749 127.257 36.7575C126.477 35.9401 126.087 34.8114 126.087 33.3713V27.0659H123.884V23.8548H126.087V21.5584L129.617 20.3518V23.8548H133.341V27.0659H129.597V27.0464ZM148.921 32.009H138.216C138.431 32.8458 138.938 33.5853 139.64 34.0913C140.381 34.6168 141.278 34.8892 142.175 34.8503C142.935 34.8503 143.695 34.6946 144.378 34.3638C145.002 34.0913 145.548 33.6632 145.977 33.1377L148.336 35.2006C147.595 36.0763 146.659 36.7769 145.606 37.244C144.495 37.7305 143.286 37.9835 142.058 37.9641C140.693 37.9835 139.328 37.6527 138.119 37.0105C136.968 36.4072 136.032 35.4925 135.369 34.3832C134.726 33.2545 134.375 31.9895 134.375 30.7051C134.375 29.4207 134.706 28.1362 135.33 27.0075C135.954 25.9177 136.871 25.0225 137.982 24.4192C139.133 23.7964 140.42 23.4656 141.726 23.485C143.793 23.485 145.528 24.1662 146.913 25.5479C148.297 26.9296 148.999 28.6617 148.999 30.744C148.999 31.1722 148.98 31.6003 148.921 32.009ZM143.949 27.3578C143.305 26.8518 142.526 26.5793 141.707 26.5793C140.888 26.5793 140.108 26.8518 139.464 27.3578C138.821 27.8832 138.372 28.6228 138.177 29.4207H145.236C145.06 28.6033 144.592 27.8832 143.949 27.3578Z" />
</svg>
)
const Arc = () => (
<svg viewBox="0 0 180 56" fill="currentColor" className="w-full">
<path d="M133.969 31.642a.918.918 0 00-.673.287c-.909.938-2.098 1.51-3.483 1.51a4.803 4.803 0 01-2.232-.546c-1.814-.947-2.987-3.015-2.661-5.319.356-2.529 2.567-4.411 5.045-4.338 1.322.04 2.457.604 3.334 1.509a.914.914 0 00.672.286c.554 0 1.029-.49 1.029-1.02 0-.247-.078-.53-.278-.735a6.742 6.742 0 00-4.277-2.055c-3.913-.348-7.435 2.84-7.557 6.886-.122 4.066 3.01 7.374 6.925 7.374 1.94 0 3.642-.777 4.909-2.081.198-.204.278-.49.278-.734-.002-.533-.478-1.023-1.031-1.023zM116.535 29.095c1.283-.735 2.135-2.1 2.094-3.77-.055-2.325-1.995-4.135-4.25-4.135h-6.239c-.546 0-.989.457-.989 1.02v11.883c0 .519.358.995.856 1.052.616.07 1.123-.356 1.123-.974V31.58c0-.2.131-.372.317-.42l3.506-.895 1.447-.38a.415.415 0 01.484.238l1.959 4.44c.16.365.507.58.872.58a.96.96 0 00.632-.244c.33-.288.399-.788.22-1.193l-2.032-4.61zm-7.405-.42v-5.093c0-.24.188-.431.418-.431h4.767c1.384 0 2.335.98 2.335 2.288 0 1.307-.779 2.251-2.37 2.602l-4.643 1.056a.421.421 0 01-.507-.422zM96.89 21.967c-.21-.455-.655-.727-1.192-.727-.537 0-.983.272-1.192.725l-5.462 11.742c-.071.145-.11.325-.11.488 0 .557.422.976.985.976a.944.944 0 00.895-.57l1.017-2.172a8.97 8.97 0 001.403.386c.792.151 1.59.203 2.377.194.79-.007 1.568-.104 2.335-.235.383-.066.76-.163 1.141-.243l.466-.133 1.024 2.188a.956.956 0 00.903.587c.638 0 .982-.502.982-.975 0-.166-.041-.344-.105-.481l-5.467-11.75zm.757 9.04c-.686.117-1.38.205-2.066.21-.687.006-1.37-.036-2.03-.164a7.106 7.106 0 01-.962-.251l.82-1.755h-.003l1.913-4.085a.413.413 0 01.753 0l1.761 3.76.088.188.064.137.797 1.707-.11.031c-.34.074-.68.164-1.025.223zM77.035 23.307c.212-1.058.044-2.13-.468-3.019-.592-1.023-1.538-1.714-2.668-1.946a3.901 3.901 0 00-.808-.08c-1.92 0-3.536 1.387-3.931 3.371a9.394 9.394 0 01-1.183 3.015.11.11 0 01-.1.054.113.113 0 01-.1-.069l-3.765-8.17c-.521-1.129-1.449-1.967-2.546-2.298-1.876-.569-3.922.376-4.762 2.197l-3.897 8.449a.048.048 0 01-.043.028c-.028 0-.033-.016-.037-.028-.618-1.575-2.08-2.593-3.729-2.593-.533 0-1.054.109-1.55.322-.992.426-1.756 1.24-2.158 2.292a4.375 4.375 0 00.032 3.214c.737 1.818 1.97 3.573 3.566 5.074.039.036.05.09.027.138l-1.258 2.732c-.95 2.063-.151 4.556 1.78 5.56a3.9 3.9 0 001.813.448c1.543 0 2.97-.929 3.633-2.366l1.086-2.356a.112.112 0 01.135-.062 14.83 14.83 0 004.025.578c1.458 0 2.942-.223 4.404-.66a.111.111 0 01.136.061l1.074 2.333c.69 1.494 2.124 2.464 3.66 2.474h.023a3.87 3.87 0 001.812-.447c1.93-1.005 2.728-3.505 1.772-5.575l-1.357-2.934a.12.12 0 01.028-.137c2.742-2.617 4.643-6.026 5.354-9.6zM54.201 36.69l-.98 2.126a2.19 2.19 0 01-1.975 1.286c-.338 0-.664-.078-.97-.234-1.06-.543-1.492-1.916-.964-3.065l1.045-2.268a.122.122 0 01.108-.071c.018 0 .041.005.062.021a17.81 17.81 0 003.61 2.044c.04.016.06.05.066.068a.107.107 0 01-.002.093zm9.565-1.345a.108.108 0 01-.07.061c-1.19.325-2.391.49-3.571.49-5.465 0-11.24-3.817-13.15-8.688-.45-1.15.09-2.465 1.206-2.931.261-.11.537-.166.817-.166.896 0 1.69.552 2.025 1.409 1.247 3.183 5.417 5.873 9.102 5.873.555 0 1.125-.055 1.694-.164.053-.01.106.014.127.064l1.82 3.947a.136.136 0 010 .105zm-4.468-6.183l.733-1.591a.273.273 0 01.252-.164.28.28 0 01.253.164l.716 1.553a.29.29 0 01-.007.26.27.27 0 01-.204.147 6.112 6.112 0 01-1.518.04.276.276 0 01-.213-.144.292.292 0 01-.012-.265zm11.09 10.706a2.108 2.108 0 01-.969.234 2.188 2.188 0 01-1.972-1.286l-6.578-14.27a.584.584 0 00-1.07 0l-2.04 4.426a.115.115 0 01-.137.064c-1.361-.483-2.638-1.295-3.596-2.282a.117.117 0 01-.018-.13l4.408-9.562c.148-.32.359-.578.609-.746.592-.4 1.265-.519 1.899-.337a2.199 2.199 0 011.382 1.201l9.047 19.626c.53 1.146.096 2.52-.965 3.062zm.526-8.807a.123.123 0 01-.106.036.11.11 0 01-.082-.067l-1.929-4.186a.12.12 0 01.014-.123c1.09-1.443 1.837-3.086 2.16-4.755.205-1.05 1.103-1.812 2.138-1.812h.002c.177 0 .356.024.533.069 1.144.293 1.84 1.506 1.584 2.76-.613 3.001-2.103 5.793-4.314 8.078z" />
</svg>
)
const Raycast = () => (
<svg viewBox="0 0 180 56" fill="currentColor" class="w-full">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M34.292 33.307v3.443L26 28.5l1.731-1.723 6.56 6.53zm3.46 3.443h-3.46L42.583 45l1.732-1.723-6.563-6.527zm19.68-6.527l1.73-1.723L42.58 12l-1.727 1.727 6.56 6.527h-3.964l-4.58-4.547-1.73 1.723 2.847 2.833h-1.99V33.07h12.871v-1.98l2.848 2.834 1.732-1.723-4.58-4.556V23.7l6.565 6.523zM35.155 19.396L33.42 21.12l1.858 1.848 1.731-1.723-1.853-1.848zm14.726 14.652l-1.73 1.723 1.856 1.848 1.732-1.723-1.858-1.848zM31.442 23.09l-1.732 1.723 4.58 4.556v-3.445l-2.848-2.834zm13.735 13.667h-3.46l4.579 4.556 1.731-1.723-2.85-2.833z"
></path>
<path d="M151.74 36.73c-1.116 0-1.99-.301-2.613-.906-.624-.605-.936-1.446-.936-2.51v-6.6h-2.003v-2.471h2.014l.359-3.3h2.359v3.3H154v2.475h-3.08v6.237a1.3 1.3 0 00.356.92 1.22 1.22 0 00.94.38H154v2.475h-2.26zM139.691 36.963c-1.489 0-2.686-.353-3.593-1.06a4.739 4.739 0 01-1.74-2.816h2.961c.129.429.417.793.804 1.02a2.917 2.917 0 001.568.386c1.579 0 2.373-.44 2.373-1.29 0-.423-.249-.747-.745-.97a8.223 8.223 0 00-1.8-.534 22.253 22.253 0 01-2.125-.52 3.697 3.697 0 01-1.816-1.2c-.493-.587-.741-1.359-.743-2.315a3.264 3.264 0 011.252-2.616c.837-.695 1.998-1.042 3.483-1.04 1.484 0 2.663.33 3.537.99a4.415 4.415 0 011.679 2.666h-2.966c-.316-.781-1.061-1.18-2.242-1.181-1.267 0-1.899.393-1.899 1.18a1.016 1.016 0 00.533.88 3.55 3.55 0 001.327.472c.587.1 1.168.233 1.74.4.593.169 1.174.376 1.74.62a3.092 3.092 0 011.342 1.162 3.53 3.53 0 01.537 2 3.28 3.28 0 01-.333 1.517 3.307 3.307 0 01-.993 1.198c-.882.7-2.175 1.05-3.881 1.051zM130.862 25.257c-.912-.817-2.19-1.225-3.834-1.225-1.31 0-2.409.35-3.297 1.053a4.928 4.928 0 00-1.755 2.722h2.799c.142-.408.414-.76.774-1.001a2.488 2.488 0 011.481-.416c.77 0 1.376.198 1.82.59a2.081 2.081 0 01.663 1.645v.596h-3.206c-1.474 0-2.632.37-3.474 1.111a3.568 3.568 0 00-1.255 2.777 3.645 3.645 0 001.171 2.785c.779.733 1.811 1.1 3.096 1.1a4.748 4.748 0 002.291-.53 3.43 3.43 0 001.388-1.234h.115l.233 1.54h2.368v-8.14c-.005-1.429-.464-2.553-1.378-3.373zm-1.346 6.67a2.525 2.525 0 01-.83 1.98c-.549.486-1.3.73-2.251.73-.692 0-1.222-.158-1.587-.466a1.495 1.495 0 01-.553-1.19c0-1.1.723-1.646 2.14-1.646h3.081v.592zM114.473 36.99c-1.796 0-3.232-.574-4.307-1.72-1.074-1.148-1.612-2.735-1.614-4.762 0-2.022.536-3.608 1.608-4.756 1.071-1.148 2.509-1.722 4.313-1.72 1.466 0 2.68.388 3.644 1.166a5.532 5.532 0 011.92 2.954h-2.716a3.054 3.054 0 00-2.845-1.646 2.887 2.887 0 00-2.326 1.046c-.59.7-.885 1.685-.885 2.956 0 1.272.295 2.258.885 2.957a2.872 2.872 0 002.326 1.05 3.06 3.06 0 002.845-1.648h2.716a5.524 5.524 0 01-1.92 2.957c-.962.777-2.177 1.166-3.644 1.166zM98.455 41.476l1.895-4.717-5.088-12.487h2.96l3.434 8.837h.115l3.439-8.837h2.96l-6.872 17.204h-2.843zM92.704 25.257c-.913-.817-2.191-1.225-3.835-1.225-1.31 0-2.412.35-3.296 1.053a4.92 4.92 0 00-1.756 2.722h2.8c.14-.409.412-.76.773-1.001a2.48 2.48 0 011.482-.416c.769 0 1.376.197 1.82.59a2.074 2.074 0 01.663 1.645v.596h-3.207c-1.474 0-2.632.37-3.473 1.111a3.56 3.56 0 00-1.256 2.777 3.642 3.642 0 001.167 2.785c.779.733 1.81 1.1 3.096 1.1a4.745 4.745 0 002.29-.53 3.431 3.431 0 001.39-1.234h.114l.232 1.54h2.364v-8.14c0-1.429-.456-2.553-1.368-3.373zm-1.351 6.67a2.51 2.51 0 01-.83 1.98c-.548.487-1.298.73-2.25.73-.693 0-1.226-.156-1.588-.466a1.494 1.494 0 01-.553-1.19c.007-1.096.72-1.644 2.14-1.646h3.08v.592zM77.343 30.509c.422-.112.83-.272 1.216-.475a6.86 6.86 0 001.256-.88 4.036 4.036 0 001.046-1.54c.269-.69.401-1.425.39-2.165 0-1.57-.503-2.83-1.511-3.775-1.009-.946-2.32-1.418-3.936-1.415h-6.87v16.5h2.96v-6.122h2.247l4.267 6.122h3.317l-4.382-6.25zm-5.449-2.482v-5.17h3.669c.84 0 1.503.227 1.99.68.486.453.453.73 1.088.734 1.905 0 .817-.246 1.454-.739 1.91a2.784 2.784 0 01-1.961.675h-3.693z"></path>
</svg>
)
Install NPM dependencies:
@tsparticles/slim, @tsparticles/reactExtend existing globals.css with this code:
@layer base {
:root {
--gradient-color: #8350e8;
--sparkles-color: #8350e8;
}
.dark {
--gradient-color: #8350e8;
--sparkles-color: #ffffff;
}
}Implementation Guidelines
- Analyze the component structure and identify all required dependencies
- Review the component's argumens and state
- Identify any required context providers or hooks and install them
- Questions to Ask
- What data/props will be passed to this component?
- Are there any specific state management requirements?
- Are there any required assets (images, icons, etc.)?
- What is the expected responsive behavior?
- What is the best place to use this component in the app?
Steps to integrate 0. Copy paste all the code above in the correct directories
- Install external dependencies
- Fill image assets with Unsplash stock images you know exist
- Use lucide-react icons for svgs or logos if component requires them
name: laser flow code ## Integrate the <LaserFlow /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: LaserFlow
Variant: JavaScript + CSS
Dependencies: three
Usage Example
import LaserFlow from './LaserFlow';
import { useRef } from 'react';
// NOTE: You can also adjust the variables in the shader for super detailed customization
// Basic Usage
<div style={{ height: '500px', position: 'relative', overflow: 'hidden' }}>
<LaserFlow />
</div>
// Image Example Interactive Reveal Effect
function LaserFlowBoxExample() {
const revealImgRef = useRef(null);
return (
<div
style={{
height: '800px',
position: 'relative',
overflow: 'hidden',
backgroundColor: '#120F17'
}}
onMouseMove={(e) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const el = revealImgRef.current;
if (el) {
el.style.setProperty('--mx', `${x}px`);
el.style.setProperty('--my', `${y + rect.height * 0.5}px`);
}
}}
onMouseLeave={() => {
const el = revealImgRef.current;
if (el) {
el.style.setProperty('--mx', '-9999px');
el.style.setProperty('--my', '-9999px');
}
}}
>
<LaserFlow
horizontalBeamOffset={0.1}
verticalBeamOffset={0.0}
color="#FF79C6"
/>
<div style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translateX(-50%)',
width: '86%',
height: '60%',
backgroundColor: '#120F17',
borderRadius: '20px',
border: '2px solid #FF79C6',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontSize: '2rem',
zIndex: 6
}}>
{/* Your content here */}
</div>
<img
ref={revealImgRef}
src="/path/to/image.jpg"
alt="Reveal effect"
style={{
position: 'absolute',
width: '100%',
top: '-50%',
zIndex: 5,
mixBlendMode: 'lighten',
opacity: 0.3,
pointerEvents: 'none',
'--mx': '-9999px',
'--my': '-9999px',
WebkitMaskImage: 'radial-gradient(circle at var(--mx) var(--my), rgba(255,255,255,1) 0px, rgba(255,255,255,0.95) 60px, rgba(255,255,255,0.6) 120px, rgba(255,255,255,0.25) 180px, rgba(255,255,255,0) 240px)',
maskImage: 'radial-gradient(circle at var(--mx) var(--my), rgba(255,255,255,1) 0px, rgba(255,255,255,0.95) 60px, rgba(255,255,255,0.6) 120px, rgba(255,255,255,0.25) 180px, rgba(255,255,255,0) 240px)',
WebkitMaskRepeat: 'no-repeat',
maskRepeat: 'no-repeat'
}}
/>
</div>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| horizontalBeamOffset | number | 0.1 | Horizontal offset of the beam (0–1 of canvas width). |
| verticalBeamOffset | number | 0.0 | Vertical offset of the beam (0–1 of canvas height). |
| horizontalSizing | number | 0.5 | Horizontal sizing factor of the beam footprint. |
| verticalSizing | number | 2.0 | Vertical sizing factor of the beam footprint. |
| wispDensity | number | 1 | Density of micro-streak wisps. |
| wispSpeed | number | 15.0 | Speed of wisp motion. |
| wispIntensity | number | 5.0 | Brightness of wisps. |
| flowSpeed | number | 0.35 | Speed of the beam’s flow modulation. |
| flowStrength | number | 0.25 | Strength of the beam’s flow modulation. |
| fogIntensity | number | 0.45 | Overall volumetric fog intensity. |
| fogScale | number | 0.3 | Spatial scale for the fog noise. |
| fogFallSpeed | number | 0.6 | Drift speed for the fog field. |
| mouseTiltStrength | number | 0.01 | How much mouse x tilts the fog volume. |
| mouseSmoothTime | number | 0.0 | Pointer smoothing time (seconds). |
| decay | number | 1.1 | Beam decay shaping for sampling envelope. |
| falloffStart | number | 1.2 | Falloff start radius used in inverse-square blending. |
| dpr | number | auto | Device pixel ratio override (defaults to window.devicePixelRatio). |
| color | string | #FF79C6 | Beam color (hex). |
Full Component Source
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import './LaserFlow.css';
const VERT = `
precision highp float;
attribute vec3 position;
void main(){
gl_Position = vec4(position, 1.0);
}
`;
const FRAG = `
#ifdef GL_ES
#extension GL_OES_standard_derivatives : enable
#endif
precision highp float;
precision mediump int;
uniform float iTime;
uniform vec3 iResolution;
uniform vec4 iMouse;
uniform float uWispDensity;
uniform float uTiltScale;
uniform float uFlowTime;
uniform float uFogTime;
uniform float uBeamXFrac;
uniform float uBeamYFrac;
uniform float uFlowSpeed;
uniform float uVLenFactor;
uniform float uHLenFactor;
uniform float uFogIntensity;
uniform float uFogScale;
uniform float uWSpeed;
uniform float uWIntensity;
uniform float uFlowStrength;
uniform float uDecay;
uniform float uFalloffStart;
uniform float uFogFallSpeed;
uniform vec3 uColor;
uniform float uFade;
// Core beam/flare shaping and dynamics
#define PI 3.14159265359
#define TWO_PI 6.28318530718
#define EPS 1e-6
#define EDGE_SOFT (DT_LOCAL*4.0)
#define DT_LOCAL 0.0038
#define TAP_RADIUS 6
#define R_H 150.0
#define R_V 150.0
#define FLARE_HEIGHT 16.0
#define FLARE_AMOUNT 8.0
#define FLARE_EXP 2.0
#define TOP_FADE_START 0.1
#define TOP_FADE_EXP 1.0
#define FLOW_PERIOD 0.5
#define FLOW_SHARPNESS 1.5
// Wisps (animated micro-streaks) that travel along the beam
#define W_BASE_X 1.5
#define W_LAYER_GAP 0.25
#define W_LANES 10
#define W_SIDE_DECAY 0.5
#define W_HALF 0.01
#define W_AA 0.15
#define W_CELL 20.0
#define W_SEG_MIN 0.01
#define W_SEG_MAX 0.55
#define W_CURVE_AMOUNT 15.0
#define W_CURVE_RANGE (FLARE_HEIGHT - 3.0)
#define W_BOTTOM_EXP 10.0
// Volumetric fog controls
#define FOG_ON 1
#define FOG_CONTRAST 1.2
#define FOG_SPEED_U 0.1
#define FOG_SPEED_V -0.1
#define FOG_OCTAVES 5
#define FOG_BOTTOM_BIAS 0.8
#define FOG_TILT_TO_MOUSE 0.05
#define FOG_TILT_DEADZONE 0.01
#define FOG_TILT_MAX_X 0.35
#define FOG_TILT_SHAPE 1.5
#define FOG_BEAM_MIN 0.0
#define FOG_BEAM_MAX 0.75
#define FOG_MASK_GAMMA 0.5
#define FOG_EXPAND_SHAPE 12.2
#define FOG_EDGE_MIX 0.5
// Horizontal vignette for the fog volume
#define HFOG_EDGE_START 0.20
#define HFOG_EDGE_END 0.98
#define HFOG_EDGE_GAMMA 1.4
#define HFOG_Y_RADIUS 25.0
#define HFOG_Y_SOFT 60.0
// Beam extents and edge masking
#define EDGE_X0 0.22
#define EDGE_X1 0.995
#define EDGE_X_GAMMA 1.25
#define EDGE_LUMA_T0 0.0
#define EDGE_LUMA_T1 2.0
#define DITHER_STRENGTH 1.0
float g(float x){return x<=0.00031308?12.92*x:1.055*pow(x,1.0/2.4)-0.055;}
float bs(vec2 p,vec2 q,float powr){
float d=distance(p,q),f=powr*uFalloffStart,r=(f*f)/(d*d+EPS);
return powr*min(1.0,r);
}
float bsa(vec2 p,vec2 q,float powr,vec2 s){
vec2 d=p-q; float dd=(d.x*d.x)/(s.x*s.x)+(d.y*d.y)/(s.y*s.y),f=powr*uFalloffStart,r=(f*f)/(dd+EPS);
return powr*min(1.0,r);
}
float tri01(float x){float f=fract(x);return 1.0-abs(f*2.0-1.0);}
float tauWf(float t,float tmin,float tmax){float a=smoothstep(tmin,tmin+EDGE_SOFT,t),b=1.0-smoothstep(tmax-EDGE_SOFT,tmax,t);return max(0.0,a*b);}
float h21(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+34.123);return fract(p.x*p.y);}
float vnoise(vec2 p){
vec2 i=floor(p),f=fract(p);
float a=h21(i),b=h21(i+vec2(1,0)),c=h21(i+vec2(0,1)),d=h21(i+vec2(1,1));
vec2 u=f*f*(3.0-2.0*f);
return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);
}
float fbm2(vec2 p){
float v=0.0,amp=0.6; mat2 m=mat2(0.86,0.5,-0.5,0.86);
for(int i=0;i<FOG_OCTAVES;++i){v+=amp*vnoise(p); p=m*p*2.03+17.1; amp*=0.52;}
return v;
}
float rGate(float x,float l){float a=smoothstep(0.0,W_AA,x),b=1.0-smoothstep(l,l+W_AA,x);return max(0.0,a*b);}
float flareY(float y){float t=clamp(1.0-(clamp(y,0.0,FLARE_HEIGHT)/max(FLARE_HEIGHT,EPS)),0.0,1.0);return pow(t,FLARE_EXP);}
float vWisps(vec2 uv,float topF){
float y=uv.y,yf=(y+uFlowTime*uWSpeed)/W_CELL;
float dRaw=clamp(uWispDensity,0.0,2.0),d=dRaw<=0.0?1.0:dRaw;
float lanesF=floor(float(W_LANES)*min(d,1.0)+0.5); // WebGL1-safe
int lanes=int(max(1.0,lanesF));
float sp=min(d,1.0),ep=max(d-1.0,0.0);
float fm=flareY(max(y,0.0)),rm=clamp(1.0-(y/max(W_CURVE_RANGE,EPS)),0.0,1.0),cm=fm*rm;
const float G=0.05; float xS=1.0+(FLARE_AMOUNT*W_CURVE_AMOUNT*G)*cm;
float sPix=clamp(y/R_V,0.0,1.0),bGain=pow(1.0-sPix,W_BOTTOM_EXP),sum=0.0;
for(int s=0;s<2;++s){
float sgn=s==0?-1.0:1.0;
for(int i=0;i<W_LANES;++i){
if(i>=lanes) break;
float off=W_BASE_X+float(i)*W_LAYER_GAP,xc=sgn*(off*xS);
float dx=abs(uv.x-xc),lat=1.0-smoothstep(W_HALF,W_HALF+W_AA,dx),amp=exp(-off*W_SIDE_DECAY);
float seed=h21(vec2(off,sgn*17.0)),yf2=yf+seed*7.0,ci=floor(yf2),fy=fract(yf2);
float seg=mix(W_SEG_MIN,W_SEG_MAX,h21(vec2(ci,off*2.3)));
float spR=h21(vec2(ci,off+sgn*31.0)),seg1=rGate(fy,seg)*step(spR,sp);
if(ep>0.0){float spR2=h21(vec2(ci*3.1+7.0,off*5.3+sgn*13.0)); float f2=fract(fy+0.5); seg1+=rGate(f2,seg*0.9)*step(spR2,ep);}
sum+=amp*lat*seg1;
}
}
float span=smoothstep(-3.0,0.0,y)*(1.0-smoothstep(R_V-6.0,R_V,y));
return uWIntensity*sum*topF*bGain*span;
}
void mainImage(out vec4 fc,in vec2 frag){
vec2 C=iResolution.xy*.5; float invW=1.0/max(C.x,1.0);
vec2 sc=(512.0/iResolution.xy)*.4;
vec2 uv=(frag-C)*sc,off=vec2(uBeamXFrac*iResolution.x*sc.x,uBeamYFrac*iResolution.y*sc.y);
vec2 uvc = uv - off;
float a=0.0,b=0.0;
float basePhase=1.5*PI+uDecay*.5; float tauMin=basePhase-uDecay; float tauMax=basePhase;
float cx=clamp(uvc.x/(R_H*uHLenFactor),-1.0,1.0),tH=clamp(TWO_PI-acos(cx),tauMin,tauMax);
for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){
float tu=tH+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;
float spd=max(abs(sin(tu)),0.02),u=clamp((basePhase-tu)/max(uDecay,EPS),0.0,1.0),env=pow(1.0-abs(u*2.0-1.0),0.8);
vec2 p=vec2((R_H*uHLenFactor)*cos(tu),0.0);
a+=wt*bs(uvc,p,env*spd);
}
float yPix=uvc.y,cy=clamp(-yPix/(R_V*uVLenFactor),-1.0,1.0),tV=clamp(TWO_PI-acos(cy),tauMin,tauMax);
for(int k=-TAP_RADIUS;k<=TAP_RADIUS;++k){
float tu=tV+float(k)*DT_LOCAL,wt=tauWf(tu,tauMin,tauMax); if(wt<=0.0) continue;
float yb=(-R_V)*cos(tu),s=clamp(yb/R_V,0.0,1.0),spd=max(abs(sin(tu)),0.02);
float env=pow(1.0-s,0.6)*spd;
float cap=1.0-smoothstep(TOP_FADE_START,1.0,s); cap=pow(cap,TOP_FADE_EXP); env*=cap;
float ph=s/max(FLOW_PERIOD,EPS)+uFlowTime*uFlowSpeed;
float fl=pow(tri01(ph),FLOW_SHARPNESS);
env*=mix(1.0-uFlowStrength,1.0,fl);
float yp=(-R_V*uVLenFactor)*cos(tu),m=pow(smoothstep(FLARE_HEIGHT,0.0,yp),FLARE_EXP),wx=1.0+FLARE_AMOUNT*m;
vec2 sig=vec2(wx,1.0),p=vec2(0.0,yp);
float mask=step(0.0,yp);
b+=wt*bsa(uvc,p,mask*env,sig);
}
float sPix=clamp(yPix/R_V,0.0,1.0),topA=pow(1.0-smoothstep(TOP_FADE_START,1.0,sPix),TOP_FADE_EXP);
float L=a+b*topA;
float w=vWisps(vec2(uvc.x,yPix),topA);
float fog=0.0;
#if FOG_ON
vec2 fuv=uvc*uFogScale;
float mAct=step(1.0,length(iMouse.xy)),nx=((iMouse.x-C.x)*invW)*mAct;
float ax = abs(nx);
float stMag = mix(ax, pow(ax, FOG_TILT_SHAPE), 0.35);
float st = sign(nx) * stMag * uTiltScale;
st = clamp(st, -FOG_TILT_MAX_X, FOG_TILT_MAX_X);
vec2 dir=normalize(vec2(st,1.0));
fuv+=uFogTime*uFogFallSpeed*dir;
vec2 prp=vec2(-dir.y,dir.x);
fuv+=prp*(0.08*sin(dot(uvc,prp)*0.08+uFogTime*0.9));
float n=fbm2(fuv+vec2(fbm2(fuv+vec2(7.3,2.1)),fbm2(fuv+vec2(-3.7,5.9)))*0.6);
n=pow(clamp(n,0.0,1.0),FOG_CONTRAST);
float pixW = 1.0 / max(iResolution.y, 1.0);
#ifdef GL_OES_standard_derivatives
float wL = max(fwidth(L), pixW);
#else
float wL = pixW;
#endif
float m0=pow(smoothstep(FOG_BEAM_MIN - wL, FOG_BEAM_MAX + wL, L),FOG_MASK_GAMMA);
float bm=1.0-pow(1.0-m0,FOG_EXPAND_SHAPE); bm=mix(bm*m0,bm,FOG_EDGE_MIX);
float yP=1.0-smoothstep(HFOG_Y_RADIUS,HFOG_Y_RADIUS+HFOG_Y_SOFT,abs(yPix));
float nxF=abs((frag.x-C.x)*invW),hE=1.0-smoothstep(HFOG_EDGE_START,HFOG_EDGE_END,nxF); hE=pow(clamp(hE,0.0,1.0),HFOG_EDGE_GAMMA);
float hW=mix(1.0,hE,clamp(yP,0.0,1.0));
float bBias=mix(1.0,1.0-sPix,FOG_BOTTOM_BIAS);
float browserFogIntensity = uFogIntensity;
browserFogIntensity *= 1.8;
float radialFade = 1.0 - smoothstep(0.0, 0.7, length(uvc) / 120.0);
float safariFog = n * browserFogIntensity * bBias * bm * hW * radialFade;
fog = safariFog;
#endif
float LF=L+fog;
float dith=(h21(frag)-0.5)*(DITHER_STRENGTH/255.0);
float tone=g(LF+w);
vec3 col=tone*uColor+dith;
float alpha=clamp(g(L+w*0.6)+dith*0.6,0.0,1.0);
float nxE=abs((frag.x-C.x)*invW),xF=pow(clamp(1.0-smoothstep(EDGE_X0,EDGE_X1,nxE),0.0,1.0),EDGE_X_GAMMA);
float scene=LF+max(0.0,w)*0.5,hi=smoothstep(EDGE_LUMA_T0,EDGE_LUMA_T1,scene);
float eM=mix(xF,1.0,hi);
col*=eM; alpha*=eM;
col*=uFade; alpha*=uFade;
fc=vec4(col,alpha);
}
void main(){
vec4 fc;
mainImage(fc, gl_FragCoord.xy);
gl_FragColor = fc;
}
`;
export const LaserFlow = ({
className,
style,
wispDensity = 1,
dpr,
mouseSmoothTime = 0.0,
mouseTiltStrength = 0.01,
horizontalBeamOffset = 0.1,
verticalBeamOffset = 0.0,
flowSpeed = 0.35,
verticalSizing = 2.0,
horizontalSizing = 0.5,
fogIntensity = 0.45,
fogScale = 0.3,
wispSpeed = 15.0,
wispIntensity = 5.0,
flowStrength = 0.25,
decay = 1.1,
falloffStart = 1.2,
fogFallSpeed = 0.6,
color = '#FF79C6'
}) => {
const mountRef = useRef(null);
const rendererRef = useRef(null);
const uniformsRef = useRef(null);
const hasFadedRef = useRef(false);
const rectRef = useRef(null);
const baseDprRef = useRef(1);
const currentDprRef = useRef(1);
const lastSizeRef = useRef({ width: 0, height: 0, dpr: 0 });
const fpsSamplesRef = useRef([]);
const lastFpsCheckRef = useRef(performance.now());
const emaDtRef = useRef(16.7);
const pausedRef = useRef(false);
const inViewRef = useRef(true);
const hexToRGB = hex => {
let c = hex.trim();
if (c[0] === '#') c = c.slice(1);
if (c.length === 3)
c = c
.split('')
.map(x => x + x)
.join('');
const n = parseInt(c, 16) || 0xffffff;
return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };
};
useEffect(() => {
const mount = mountRef.current;
const renderer = new THREE.WebGLRenderer({
antialias: false,
alpha: false,
depth: false,
stencil: false,
powerPreference: 'high-performance',
premultipliedAlpha: false,
preserveDrawingBuffer: false,
failIfMajorPerformanceCaveat: false,
logarithmicDepthBuffer: false
});
rendererRef.current = renderer;
baseDprRef.current = Math.min(dpr ?? (window.devicePixelRatio || 1), 2);
currentDprRef.current = baseDprRef.current;
renderer.setPixelRatio(currentDprRef.current);
renderer.shadowMap.enabled = false;
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.setClearColor(0x000000, 1);
const canvas = renderer.domElement;
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.display = 'block';
mount.appendChild(canvas);
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]), 3));
const uniforms = {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector3(1, 1, 1) },
iMouse: { value: new THREE.Vector4(0, 0, 0, 0) },
uWispDensity: { value: wispDensity },
uTiltScale: { value: mouseTiltStrength },
uFlowTime: { value: 0 },
uFogTime: { value: 0 },
uBeamXFrac: { value: horizontalBeamOffset },
uBeamYFrac: { value: verticalBeamOffset },
uFlowSpeed: { value: flowSpeed },
uVLenFactor: { value: verticalSizing },
uHLenFactor: { value: horizontalSizing },
uFogIntensity: { value: fogIntensity },
uFogScale: { value: fogScale },
uWSpeed: { value: wispSpeed },
uWIntensity: { value: wispIntensity },
uFlowStrength: { value: flowStrength },
uDecay: { value: decay },
uFalloffStart: { value: falloffStart },
uFogFallSpeed: { value: fogFallSpeed },
uColor: { value: new THREE.Vector3(1, 1, 1) },
uFade: { value: hasFadedRef.current ? 1 : 0 }
};
uniformsRef.current = uniforms;
const material = new THREE.RawShaderMaterial({
vertexShader: VERT,
fragmentShader: FRAG,
uniforms,
transparent: false,
depthTest: false,
depthWrite: false,
blending: THREE.NormalBlending
});
const mesh = new THREE.Mesh(geometry, material);
mesh.frustumCulled = false;
scene.add(mesh);
const clock = new THREE.Clock();
let prevTime = 0;
let fade = hasFadedRef.current ? 1 : 0;
const mouseTarget = new THREE.Vector2(0, 0);
const mouseSmooth = new THREE.Vector2(0, 0);
const setSizeNow = () => {
const w = mount.clientWidth || 1;
const h = mount.clientHeight || 1;
const pr = currentDprRef.current;
const last = lastSizeRef.current;
const sizeChanged = Math.abs(w - last.width) > 0.5 || Math.abs(h - last.height) > 0.5;
const dprChanged = Math.abs(pr - last.dpr) > 0.01;
if (!sizeChanged && !dprChanged) {
return;
}
lastSizeRef.current = { width: w, height: h, dpr: pr };
renderer.setPixelRatio(pr);
renderer.setSize(w, h, false);
uniforms.iResolution.value.set(w * pr, h * pr, pr);
rectRef.current = canvas.getBoundingClientRect();
if (!pausedRef.current) {
renderer.render(scene, camera);
}
};
let resizeRaf = 0;
const scheduleResize = () => {
if (resizeRaf) cancelAnimationFrame(resizeRaf);
resizeRaf = requestAnimationFrame(setSizeNow);
};
setSizeNow();
const ro = new ResizeObserver(scheduleResize);
ro.observe(mount);
const io = new IntersectionObserver(
entries => {
inViewRef.current = entries[0]?.isIntersecting ?? true;
},
{ root: null, threshold: 0 }
);
io.observe(mount);
const onVis = () => {
pausedRef.current = document.hidden;
};
document.addEventListener('visibilitychange', onVis, { passive: true });
const updateMouse = (clientX, clientY) => {
const rect = rectRef.current;
if (!rect) return;
const x = clientX - rect.left;
const y = clientY - rect.top;
const ratio = currentDprRef.current;
const hb = rect.height * ratio;
mouseTarget.set(x * ratio, hb - y * ratio);
};
const onMove = ev => updateMouse(ev.clientX, ev.clientY);
const onLeave = () => mouseTarget.set(0, 0);
canvas.addEventListener('pointermove', onMove, { passive: true });
canvas.addEventListener('pointerdown', onMove, { passive: true });
canvas.addEventListener('pointerenter', onMove, { passive: true });
canvas.addEventListener('pointerleave', onLeave, { passive: true });
const onCtxLost = e => {
e.preventDefault();
pausedRef.current = true;
};
const onCtxRestored = () => {
pausedRef.current = false;
scheduleResize();
};
canvas.addEventListener('webglcontextlost', onCtxLost, false);
canvas.addEventListener('webglcontextrestored', onCtxRestored, false);
let raf = 0;
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
const dprFloor = 0.6;
const lowerThresh = 50;
const upperThresh = 58;
let lastDprChangeRef = 0;
const dprChangeCooldown = 2000;
const adjustDprIfNeeded = now => {
const elapsed = now - lastFpsCheckRef.current;
if (elapsed < 750) return;
const samples = fpsSamplesRef.current;
if (samples.length === 0) {
lastFpsCheckRef.current = now;
return;
}
const avgFps = samples.reduce((a, b) => a + b, 0) / samples.length;
let next = currentDprRef.current;
const base = baseDprRef.current;
if (avgFps < lowerThresh) {
next = clamp(currentDprRef.current * 0.85, dprFloor, base);
} else if (avgFps > upperThresh && currentDprRef.current < base) {
next = clamp(currentDprRef.current * 1.1, dprFloor, base);
}
if (Math.abs(next - currentDprRef.current) > 0.01 && now - lastDprChangeRef > dprChangeCooldown) {
currentDprRef.current = next;
lastDprChangeRef = now;
setSizeNow();
}
fpsSamplesRef.current = [];
lastFpsCheckRef.current = now;
};
const animate = () => {
raf = requestAnimationFrame(animate);
if (pausedRef.current || !inViewRef.current) return;
const t = clock.getElapsedTime();
const dt = Math.max(0, t - prevTime);
prevTime = t;
const dtMs = dt * 1000;
emaDtRef.current = emaDtRef.current * 0.9 + dtMs * 0.1;
const instFps = 1000 / Math.max(1, emaDtRef.current);
fpsSamplesRef.current.push(instFps);
uniforms.iTime.value = t;
const cdt = Math.min(0.033, Math.max(0.001, dt));
uniforms.uFlowTime.value += cdt;
uniforms.uFogTime.value += cdt;
if (!hasFadedRef.current) {
const fadeDur = 1.0;
fade = Math.min(1, fade + cdt / fadeDur);
uniforms.uFade.value = fade;
if (fade >= 1) hasFadedRef.current = true;
}
const tau = Math.max(1e-3, mouseSmoothTime);
const alpha = 1 - Math.exp(-cdt / tau);
mouseSmooth.lerp(mouseTarget, alpha);
uniforms.iMouse.value.set(mouseSmooth.x, mouseSmooth.y, 0, 0);
renderer.render(scene, camera);
adjustDprIfNeeded(performance.now());
};
animate();
return () => {
cancelAnimationFrame(raf);
ro.disconnect();
io.disconnect();
document.removeEventListener('visibilitychange', onVis);
canvas.removeEventListener('pointermove', onMove);
canvas.removeEventListener('pointerdown', onMove);
canvas.removeEventListener('pointerenter', onMove);
canvas.removeEventListener('pointerleave', onLeave);
canvas.removeEventListener('webglcontextlost', onCtxLost);
canvas.removeEventListener('webglcontextrestored', onCtxRestored);
geometry.dispose();
material.dispose();
renderer.dispose();
renderer.forceContextLoss();
if (mount.contains(canvas)) mount.removeChild(canvas);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dpr]);
useEffect(() => {
const uniforms = uniformsRef.current;
if (!uniforms) return;
uniforms.uWispDensity.value = wispDensity;
uniforms.uTiltScale.value = mouseTiltStrength;
uniforms.uBeamXFrac.value = horizontalBeamOffset;
uniforms.uBeamYFrac.value = verticalBeamOffset;
uniforms.uFlowSpeed.value = flowSpeed;
uniforms.uVLenFactor.value = verticalSizing;
uniforms.uHLenFactor.value = horizontalSizing;
uniforms.uFogIntensity.value = fogIntensity;
uniforms.uFogScale.value = fogScale;
uniforms.uWSpeed.value = wispSpeed;
uniforms.uWIntensity.value = wispIntensity;
uniforms.uFlowStrength.value = flowStrength;
uniforms.uDecay.value = decay;
uniforms.uFalloffStart.value = falloffStart;
uniforms.uFogFallSpeed.value = fogFallSpeed;
const { r, g, b } = hexToRGB(color || '#FFFFFF');
uniforms.uColor.value.set(r, g, b);
}, [
wispDensity,
mouseTiltStrength,
horizontalBeamOffset,
verticalBeamOffset,
flowSpeed,
verticalSizing,
horizontalSizing,
fogIntensity,
fogScale,
wispSpeed,
wispIntensity,
flowStrength,
decay,
falloffStart,
fogFallSpeed,
color
]);
return <div ref={mountRef} className={`laser-flow-container ${className || ''}`} style={style} />;
};
export default LaserFlow;
Component CSS
.laser-flow-container {
width: 100%;
height: 100%;
position: relative;
pointer-events: none;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: strands code: ## Integrate the <Strands /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Strands
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Strands from './Strands';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Strands
colors={["#F97316","#7C3AED","#06B6D4"]}
count={3}
speed={0.5}
amplitude={1}
waviness={1}
thickness={0.7}
glow={2.6}
taper={3}
spread={1}
intensity={0.6}
saturation={1.5}
opacity={1}
scale={1.5}
glass={false}
refraction={1}
dispersion={1}
glassSize={1}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| colors | string[] | ["#FF4242", "#7C3AED", "#06B6D4", "#EAB308"] | Palette of hex colors cycled across the strands. Pass an empty array to use the built-in rainbow spectrum. |
| count | number | 3 | Number of strands woven through the animation. |
| speed | number | 0.5 | How quickly the strands ripple and flow. |
| amplitude | number | 1 | Vertical reach of each strand as it waves up and down. |
| waviness | number | 1 | Density of the curves along each strand. |
| thickness | number | 0.7 | Width of each glowing strand. |
| glow | number | 2.6 | Strength of the luminous bloom around the strands. |
| taper | number | 3 | How sharply the strands fade out toward the edges. |
| spread | number | 1 | Separation between strands so they fan out instead of overlapping. |
| hueShift | number | 0 | Rotates the colors around the strands for variation. |
| intensity | number | 0.6 | Overall brightness and energy of the effect. |
| saturation | number | 1 | Vibrance of the colors. Above 1 makes them more intense, below 1 fades to grayscale. |
| opacity | number | 1 | Overall transparency of the rendered strands. |
| scale | number | 1 | Zooms the whole effect in or out to make the strands bigger or smaller. |
| glass | boolean | false | Renders the strands inside a refractive glass ball. |
| refraction | number | 1 | How strongly the glass ball bends the light passing through it. |
| dispersion | number | 1 | Amount of rainbow color separation along the edges of the glass ball. |
| glassSize | number | 1 | Size of the glass ball relative to the canvas. |
Full Component Source
import { Renderer, Program, Mesh, Color, Triangle, RenderTarget } from 'ogl';
import { useEffect, useRef } from 'react';
import './Strands.css';
const MAX_STRANDS = 12;
const MAX_COLORS = 8;
const VERT = `#version 300 es
in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const FRAG = `#version 300 es
precision highp float;
uniform float uTime;
uniform vec2 uResolution;
uniform vec3 uColors[${MAX_COLORS}];
uniform int uColorCount;
uniform int uStrandCount;
uniform float uSpeed;
uniform float uAmplitude;
uniform float uWaviness;
uniform float uThickness;
uniform float uGlow;
uniform float uTaper;
uniform float uSpread;
uniform float uHueShift;
uniform float uIntensity;
uniform float uOpacity;
uniform float uScale;
uniform float uSaturation;
out vec4 fragColor;
const float PI = 3.14159265;
vec3 spectrum(float t) {
return 0.5 + 0.5 * cos(2.0 * PI * (t + vec3(0.00, 0.33, 0.67)));
}
vec3 samplePalette(float t) {
t = fract(t);
float scaled = t * float(uColorCount);
int idx = int(floor(scaled));
float blend = fract(scaled);
int nextIdx = idx + 1;
if (nextIdx >= uColorCount) nextIdx = 0;
return mix(uColors[idx], uColors[nextIdx], blend);
}
vec3 strandColor(float t) {
if (uColorCount > 0) return samplePalette(t);
return spectrum(t);
}
void main() {
vec2 uv = (gl_FragCoord.xy - 0.5 * uResolution) / uResolution.y;
uv /= max(uScale, 0.0001);
float e = 0.06 + uIntensity * 0.94;
float env = pow(max(cos(uv.x * PI * 1.3), 0.0), uTaper);
vec3 col = vec3(0.0);
for (int i = 0; i < ${MAX_STRANDS}; i++) {
if (i >= uStrandCount) break;
float fi = float(i);
float ph = fi * 1.7 * uSpread;
float freq = (2.0 + fi * 0.35) * uWaviness;
float spd = 1.4 + fi * 1.2;
float tt = uTime * uSpeed;
float w = sin(uv.x * freq + tt * spd + ph) * 0.60
+ sin(uv.x * freq * 1.1 - tt * spd * 0.7 + ph * 1.7) * 0.40;
float amp = (0.1 + 0.02 * e) * env * uAmplitude;
float y = w * amp;
float d = abs(uv.y - y);
float thick = (0.001 + 0.05 * e) * (0.35 + env) * uThickness;
float g = thick / (d + thick * 0.45);
g = g * g;
float h = fi / float(uStrandCount) + uv.x * 0.30 + uTime * 0.04 + uHueShift;
col += strandColor(h) * g * env;
}
col *= 0.45 + 0.7 * e;
col = 1.0 - exp(-col * uGlow);
float gray = dot(col, vec3(0.2126, 0.7152, 0.0722));
col = max(mix(vec3(gray), col, uSaturation), 0.0);
float lum = max(max(col.r, col.g), col.b);
float alpha = clamp(lum, 0.0, 1.0) * uOpacity;
fragColor = vec4(col * uOpacity, alpha);
}
`;
const GLASS_FRAG = `#version 300 es
precision highp float;
uniform sampler2D uScene;
uniform vec2 uResolution;
uniform float uRadius;
uniform float uRefraction;
uniform float uDispersion;
out vec4 fragColor;
vec2 toUv(vec2 p) {
return p * (uResolution.y / uResolution) + 0.5;
}
void main() {
vec2 p = (gl_FragCoord.xy - 0.5 * uResolution) / uResolution.y;
float d = length(p);
float r = uRadius;
float edge = fwidth(d) * 1.5;
float mask = 1.0 - smoothstep(r - edge, r + edge, d);
if (mask <= 0.0) {
fragColor = vec4(0.0);
return;
}
// sphere height: 0 at the rim, 1 at the center
float z = sqrt(max(r * r - d * d, 0.0)) / r;
float nd = d / r; // 0 at the center, 1 at the rim
// refraction is confined to a narrow band near the rim; the rest stays undistorted
vec2 dir = d > 0.0 ? p / d : vec2(0.0);
float lens = smoothstep(0.85, 1.0, nd) * pow(nd, 6.0);
vec2 offset = -dir * lens * uRefraction * 0.15;
vec2 disp = -dir * lens * uDispersion * 0.012;
vec3 light;
light.r = texture(uScene, toUv(p + offset - disp)).r;
light.g = texture(uScene, toUv(p + offset)).g;
light.b = texture(uScene, toUv(p + offset + disp)).b;
// neutral fresnel rim (no color tint so the glass stays clear)
float fres = pow(1.0 - z, 3.0);
vec3 rim = vec3(1.0) * fres * 0.18;
// specular highlight from the upper-left
vec2 lightDir = normalize(vec2(-0.55, 0.6));
float spec = pow(max(dot(p / max(r, 1e-4), lightDir), 0.0), 6.0);
spec *= smoothstep(r, r * 0.55, d);
vec3 emissive = light + rim + vec3(spec) * 0.4;
float emissiveA = clamp(max(max(emissive.r, emissive.g), emissive.b), 0.0, 1.0);
// almost clear glass body: only a faint neutral darkening, mostly near the rim
float bodyA = 0.05 + fres * 0.05;
// composite emissive light over the clear body (premultiplied)
float outA = emissiveA + bodyA * (1.0 - emissiveA);
vec3 outRGB = emissive;
outRGB *= mask;
outA *= mask;
fragColor = vec4(outRGB, outA);
}
`;
const buildPalette = colors => {
const filled = colors && colors.length ? colors : ['#ffffff'];
const padded = [];
for (let i = 0; i < MAX_COLORS; i++) {
const hex = filled[i] ?? filled[filled.length - 1];
const c = new Color(hex);
padded.push([c.r, c.g, c.b]);
}
return padded;
};
export default function Strands({
colors = ['#FF4242', '#7C3AED', '#06B6D4', '#EAB308'],
count = 3,
speed = 0.5,
amplitude = 1,
waviness = 1,
thickness = 0.7,
glow = 2.6,
taper = 3,
spread = 1,
hueShift = 0,
intensity = 0.6,
saturation = 1.5,
opacity = 1,
scale = 1.5,
glass = false,
refraction = 1,
dispersion = 1,
glassSize = 1,
className = '',
style
}) {
const propsRef = useRef({});
propsRef.current = {
colors,
count,
speed,
amplitude,
waviness,
thickness,
glow,
taper,
spread,
hueShift,
intensity,
saturation,
opacity,
scale,
glass,
refraction,
dispersion,
glassSize
};
const ctnDom = useRef(null);
useEffect(() => {
const ctn = ctnDom.current;
if (!ctn) return;
const renderer = new Renderer({
alpha: true,
premultipliedAlpha: true,
antialias: true
});
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.canvas.style.backgroundColor = 'transparent';
const geometry = new Triangle(gl);
if (geometry.attributes.uv) {
delete geometry.attributes.uv;
}
const program = new Program(gl, {
vertex: VERT,
fragment: FRAG,
uniforms: {
uTime: { value: 0 },
uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },
uColors: { value: buildPalette(propsRef.current.colors) },
uColorCount: { value: Math.min(propsRef.current.colors.length, MAX_COLORS) },
uStrandCount: { value: Math.min(propsRef.current.count, MAX_STRANDS) },
uSpeed: { value: speed },
uAmplitude: { value: amplitude },
uWaviness: { value: waviness },
uThickness: { value: thickness },
uGlow: { value: glow },
uTaper: { value: taper },
uSpread: { value: spread },
uHueShift: { value: hueShift },
uIntensity: { value: intensity },
uOpacity: { value: opacity },
uScale: { value: scale },
uSaturation: { value: saturation }
}
});
const mesh = new Mesh(gl, { geometry, program });
const renderTarget = new RenderTarget(gl, {
width: ctn.offsetWidth,
height: ctn.offsetHeight
});
const glassProgram = new Program(gl, {
vertex: VERT,
fragment: GLASS_FRAG,
uniforms: {
uScene: { value: renderTarget.texture },
uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },
uRadius: { value: 0.46 * glassSize },
uRefraction: { value: refraction },
uDispersion: { value: dispersion }
}
});
const glassMesh = new Mesh(gl, { geometry, program: glassProgram });
ctn.appendChild(gl.canvas);
function resize() {
if (!ctn) return;
const width = ctn.offsetWidth;
const height = ctn.offsetHeight;
renderer.setSize(width, height);
program.uniforms.uResolution.value = [width, height];
renderTarget.setSize(width, height);
glassProgram.uniforms.uResolution.value = [width, height];
}
window.addEventListener('resize', resize);
resize();
let animateId = 0;
const update = t => {
animateId = requestAnimationFrame(update);
const current = propsRef.current;
program.uniforms.uTime.value = t * 0.001;
program.uniforms.uColors.value = buildPalette(current.colors);
program.uniforms.uColorCount.value = Math.min(current.colors.length, MAX_COLORS);
program.uniforms.uStrandCount.value = Math.min(Math.max(Math.round(current.count), 1), MAX_STRANDS);
program.uniforms.uSpeed.value = current.speed;
program.uniforms.uAmplitude.value = current.amplitude;
program.uniforms.uWaviness.value = current.waviness;
program.uniforms.uThickness.value = current.thickness;
program.uniforms.uGlow.value = current.glow;
program.uniforms.uTaper.value = current.taper;
program.uniforms.uSpread.value = current.spread;
program.uniforms.uHueShift.value = current.hueShift;
program.uniforms.uIntensity.value = current.intensity;
program.uniforms.uOpacity.value = current.opacity;
program.uniforms.uScale.value = current.scale;
program.uniforms.uSaturation.value = current.saturation;
if (current.glass) {
renderer.render({ scene: mesh, target: renderTarget });
glassProgram.uniforms.uScene.value = renderTarget.texture;
glassProgram.uniforms.uRefraction.value = current.refraction;
glassProgram.uniforms.uDispersion.value = current.dispersion;
glassProgram.uniforms.uRadius.value = 0.46 * current.glassSize;
renderer.render({ scene: glassMesh });
} else {
renderer.render({ scene: mesh });
}
};
animateId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animateId);
window.removeEventListener('resize', resize);
if (ctn && gl.canvas.parentNode === ctn) {
ctn.removeChild(gl.canvas);
}
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div ref={ctnDom} className={`strands-container ${className}`} style={style} />;
}
Component CSS
.strands-container {
position: relative;
width: 100%;
height: 100%;
background: transparent;
}
.strands-container canvas {
display: block;
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: cubes code: ## Integrate the <Cubes /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Cubes
Variant: JavaScript + CSS
Dependencies: gsap
Usage Example
// CREDIT
// Component inspired from Can Tastemel's original work for the lambda.ai landing page
// https://cantastemel.com
import Cubes from './Cubes'
<div style={{ height: '600px', position: 'relative' }}>
<Cubes
gridSize={8}
maxAngle={60}
radius={4}
borderStyle="2px dashed #5227FF"
faceColor="#1a1a2e"
rippleColor="#ff6b6b"
rippleSpeed={1.5}
autoAnimate={true}
rippleOnClick={true}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| gridSize | number | 10 | The size of the grid (number of cubes per row/column) |
| cubeSize | number | undefined | Fixed size of each cube in pixels. If not provided, cubes will be responsive |
| maxAngle | number | 45 | Maximum rotation angle for the tilt effect in degrees |
| radius | number | 3 | Radius of the tilt effect (how many cubes around the cursor are affected) |
| easing | string | 'power3.out' | GSAP easing function for the tilt animation |
| duration | object | { enter: 0.3, leave: 0.6 } | Animation duration for enter and leave effects |
| cellGap | number | object | undefined |
| borderStyle | string | '1px solid #fff' | CSS border style for cube faces |
| faceColor | string | '#120F17' | Background color for cube faces |
| shadow | boolean | string | false |
| autoAnimate | boolean | true | Whether to automatically animate when user is idle |
| rippleOnClick | boolean | true | Whether to show ripple effect on click |
| rippleColor | string | '#fff' | Color of the ripple effect |
| rippleSpeed | number | 2 | Speed multiplier for the ripple animation |
Full Component Source
import { useCallback, useEffect, useRef } from 'react';
import gsap from 'gsap';
import './Cubes.css';
const Cubes = ({
gridSize = 10,
cubeSize,
maxAngle = 45,
radius = 3,
easing = 'power3.out',
duration = { enter: 0.3, leave: 0.6 },
cellGap,
borderStyle = '1px solid #fff',
faceColor = '#120F17',
shadow = false,
autoAnimate = true,
rippleOnClick = true,
rippleColor = '#fff',
rippleSpeed = 2
}) => {
const sceneRef = useRef(null);
const rafRef = useRef(null);
const idleTimerRef = useRef(null);
const userActiveRef = useRef(false);
const simPosRef = useRef({ x: 0, y: 0 });
const simTargetRef = useRef({ x: 0, y: 0 });
const simRAFRef = useRef(null);
const colGap = typeof cellGap === 'number' ? `${cellGap}px` : cellGap?.col !== undefined ? `${cellGap.col}px` : '5%';
const rowGap = typeof cellGap === 'number' ? `${cellGap}px` : cellGap?.row !== undefined ? `${cellGap.row}px` : '5%';
const enterDur = duration.enter;
const leaveDur = duration.leave;
const tiltAt = useCallback(
(rowCenter, colCenter) => {
if (!sceneRef.current) return;
sceneRef.current.querySelectorAll('.cube').forEach(cube => {
const r = +cube.dataset.row;
const c = +cube.dataset.col;
const dist = Math.hypot(r - rowCenter, c - colCenter);
if (dist <= radius) {
const pct = 1 - dist / radius;
const angle = pct * maxAngle;
gsap.to(cube, {
duration: enterDur,
ease: easing,
overwrite: true,
rotateX: -angle,
rotateY: angle
});
} else {
gsap.to(cube, {
duration: leaveDur,
ease: 'power3.out',
overwrite: true,
rotateX: 0,
rotateY: 0
});
}
});
},
[radius, maxAngle, enterDur, leaveDur, easing]
);
const onPointerMove = useCallback(
e => {
userActiveRef.current = true;
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
const rect = sceneRef.current.getBoundingClientRect();
const cellW = rect.width / gridSize;
const cellH = rect.height / gridSize;
const colCenter = (e.clientX - rect.left) / cellW;
const rowCenter = (e.clientY - rect.top) / cellH;
if (rafRef.current) cancelAnimationFrame(rafRef.current);
rafRef.current = requestAnimationFrame(() => tiltAt(rowCenter, colCenter));
idleTimerRef.current = setTimeout(() => {
userActiveRef.current = false;
}, 3000);
},
[gridSize, tiltAt]
);
const resetAll = useCallback(() => {
if (!sceneRef.current) return;
sceneRef.current.querySelectorAll('.cube').forEach(cube =>
gsap.to(cube, {
duration: leaveDur,
rotateX: 0,
rotateY: 0,
ease: 'power3.out'
})
);
}, [leaveDur]);
const onTouchMove = useCallback(
e => {
e.preventDefault();
userActiveRef.current = true;
if (idleTimerRef.current) clearTimeout(idleTimerRef.current);
const rect = sceneRef.current.getBoundingClientRect();
const cellW = rect.width / gridSize;
const cellH = rect.height / gridSize;
const touch = e.touches[0];
const colCenter = (touch.clientX - rect.left) / cellW;
const rowCenter = (touch.clientY - rect.top) / cellH;
if (rafRef.current) cancelAnimationFrame(rafRef.current);
rafRef.current = requestAnimationFrame(() => tiltAt(rowCenter, colCenter));
idleTimerRef.current = setTimeout(() => {
userActiveRef.current = false;
}, 3000);
},
[gridSize, tiltAt]
);
const onTouchStart = useCallback(() => {
userActiveRef.current = true;
}, []);
const onTouchEnd = useCallback(() => {
if (!sceneRef.current) return;
resetAll();
}, [resetAll]);
const onClick = useCallback(
e => {
if (!rippleOnClick || !sceneRef.current) return;
const rect = sceneRef.current.getBoundingClientRect();
const cellW = rect.width / gridSize;
const cellH = rect.height / gridSize;
const clientX = e.clientX || (e.touches && e.touches[0].clientX);
const clientY = e.clientY || (e.touches && e.touches[0].clientY);
const colHit = Math.floor((clientX - rect.left) / cellW);
const rowHit = Math.floor((clientY - rect.top) / cellH);
const baseRingDelay = 0.15;
const baseAnimDur = 0.3;
const baseHold = 0.6;
const spreadDelay = baseRingDelay / rippleSpeed;
const animDuration = baseAnimDur / rippleSpeed;
const holdTime = baseHold / rippleSpeed;
const rings = {};
sceneRef.current.querySelectorAll('.cube').forEach(cube => {
const r = +cube.dataset.row;
const c = +cube.dataset.col;
const dist = Math.hypot(r - rowHit, c - colHit);
const ring = Math.round(dist);
if (!rings[ring]) rings[ring] = [];
rings[ring].push(cube);
});
Object.keys(rings)
.map(Number)
.sort((a, b) => a - b)
.forEach(ring => {
const delay = ring * spreadDelay;
const faces = rings[ring].flatMap(cube => Array.from(cube.querySelectorAll('.cube-face')));
gsap.to(faces, {
backgroundColor: rippleColor,
duration: animDuration,
delay,
ease: 'power3.out'
});
gsap.to(faces, {
backgroundColor: faceColor,
duration: animDuration,
delay: delay + animDuration + holdTime,
ease: 'power3.out'
});
});
},
[rippleOnClick, gridSize, faceColor, rippleColor, rippleSpeed]
);
useEffect(() => {
if (!autoAnimate || !sceneRef.current) return;
simPosRef.current = {
x: Math.random() * gridSize,
y: Math.random() * gridSize
};
simTargetRef.current = {
x: Math.random() * gridSize,
y: Math.random() * gridSize
};
const speed = 0.02;
const loop = () => {
if (!userActiveRef.current) {
const pos = simPosRef.current;
const tgt = simTargetRef.current;
pos.x += (tgt.x - pos.x) * speed;
pos.y += (tgt.y - pos.y) * speed;
tiltAt(pos.y, pos.x);
if (Math.hypot(pos.x - tgt.x, pos.y - tgt.y) < 0.1) {
simTargetRef.current = {
x: Math.random() * gridSize,
y: Math.random() * gridSize
};
}
}
simRAFRef.current = requestAnimationFrame(loop);
};
simRAFRef.current = requestAnimationFrame(loop);
return () => {
if (simRAFRef.current != null) {
cancelAnimationFrame(simRAFRef.current);
}
};
}, [autoAnimate, gridSize, tiltAt]);
useEffect(() => {
const el = sceneRef.current;
if (!el) return;
el.addEventListener('pointermove', onPointerMove);
el.addEventListener('pointerleave', resetAll);
el.addEventListener('click', onClick);
el.addEventListener('touchmove', onTouchMove, { passive: false });
el.addEventListener('touchstart', onTouchStart, { passive: true });
el.addEventListener('touchend', onTouchEnd, { passive: true });
return () => {
el.removeEventListener('pointermove', onPointerMove);
el.removeEventListener('pointerleave', resetAll);
el.removeEventListener('click', onClick);
el.removeEventListener('touchmove', onTouchMove);
el.removeEventListener('touchstart', onTouchStart);
el.removeEventListener('touchend', onTouchEnd);
rafRef.current != null && cancelAnimationFrame(rafRef.current);
idleTimerRef.current && clearTimeout(idleTimerRef.current);
};
}, [onPointerMove, resetAll, onClick, onTouchMove, onTouchStart, onTouchEnd]);
const cells = Array.from({ length: gridSize });
const sceneStyle = {
gridTemplateColumns: cubeSize ? `repeat(${gridSize}, ${cubeSize}px)` : `repeat(${gridSize}, 1fr)`,
gridTemplateRows: cubeSize ? `repeat(${gridSize}, ${cubeSize}px)` : `repeat(${gridSize}, 1fr)`,
columnGap: colGap,
rowGap: rowGap
};
const wrapperStyle = {
'--cube-face-border': borderStyle,
'--cube-face-bg': faceColor,
'--cube-face-shadow': shadow === true ? '0 0 6px rgba(0,0,0,.5)' : shadow || 'none',
...(cubeSize
? {
width: `${gridSize * cubeSize}px`,
height: `${gridSize * cubeSize}px`
}
: {})
};
return (
<div className="default-animation" style={wrapperStyle}>
<div ref={sceneRef} className="default-animation--scene" style={sceneStyle}>
{cells.map((_, r) =>
cells.map((__, c) => (
<div key={`${r}-${c}`} className="cube" data-row={r} data-col={c}>
<div className="cube-face cube-face--top" />
<div className="cube-face cube-face--bottom" />
<div className="cube-face cube-face--left" />
<div className="cube-face cube-face--right" />
<div className="cube-face cube-face--front" />
<div className="cube-face cube-face--back" />
</div>
))
)}
</div>
</div>
);
};
export default Cubes;
Component CSS
:root {
--col-gap: 5%;
--row-gap: 5%;
--cube-perspective: 99999999px;
--cube-face-border: 1px solid #fff;
--cube-face-bg: #120F17;
}
.default-animation {
position: relative;
width: 50%;
aspect-ratio: 1 / 1;
height: auto;
}
.default-animation--scene {
display: grid;
width: 100%;
height: 100%;
column-gap: var(--col-gap);
row-gap: var(--row-gap);
perspective: var(--cube-perspective);
grid-auto-rows: 1fr;
}
.cube {
position: relative;
width: 100%;
height: 100%;
aspect-ratio: 1 / 1;
transform-style: preserve-3d;
}
.cube::before {
content: '';
position: absolute;
top: -36px;
right: -36px;
bottom: -36px;
left: -36px;
}
.default-animation .cube-face {
position: absolute;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: var(--cube-face-bg);
border: var(--cube-face-border);
opacity: 1;
}
.default-animation .cube-face--top {
transform: translateY(-50%) rotateX(90deg);
}
.default-animation .cube-face--bottom {
transform: translateY(50%) rotateX(-90deg);
}
.default-animation .cube-face--left {
transform: translateX(-50%) rotateY(-90deg);
}
.default-animation .cube-face--right {
transform: translateX(50%) rotateY(90deg);
}
.default-animation .cube-face--back,
.default-animation .cube-face--front {
transform: rotateY(-90deg) translateX(50%) rotateY(90deg);
}
@media (max-width: 768px) {
.default-animation {
width: 90%;
}
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: noise code: ## Integrate the <Noise /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Noise
Variant: JavaScript + CSS
Usage Example
import Noise from './Noise;'
<div style={{width: '600px', height: '400px', position: 'relative', overflow: 'hidden'}}>
<Noise
patternSize={250}
patternScaleX={1}
patternScaleY={1}
patternRefreshInterval={2}
patternAlpha={15}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| patternSize | number | 250 | Defines the size of the grain pattern. |
| patternScaleX | number | 1 | Scaling factor for the X-axis of the grain pattern. |
| patternScaleY | number | 1 | Scaling factor for the Y-axis of the grain pattern. |
| patternRefreshInterval | number | 2 | Number of frames before the grain pattern refreshes. |
| patternAlpha | number | 15 | Opacity of the grain pattern (0-255). |
Full Component Source
import { useRef, useEffect } from 'react';
import './Noise.css';
const Noise = ({
patternSize = 250,
patternScaleX = 1,
patternScaleY = 1,
patternRefreshInterval = 2,
patternAlpha = 15
}) => {
const grainRef = useRef(null);
useEffect(() => {
const canvas = grainRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d', { alpha: true });
if (!ctx) return;
let frame = 0;
let animationId;
const canvasSize = 1024;
const resize = () => {
if (!canvas) return;
canvas.width = canvasSize;
canvas.height = canvasSize;
canvas.style.width = '100vw';
canvas.style.height = '100vh';
};
const drawGrain = () => {
const imageData = ctx.createImageData(canvasSize, canvasSize);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const value = Math.random() * 255;
data[i] = value;
data[i + 1] = value;
data[i + 2] = value;
data[i + 3] = patternAlpha;
}
ctx.putImageData(imageData, 0, 0);
};
const loop = () => {
if (frame % patternRefreshInterval === 0) {
drawGrain();
}
frame++;
animationId = window.requestAnimationFrame(loop);
};
window.addEventListener('resize', resize);
resize();
loop();
return () => {
window.removeEventListener('resize', resize);
window.cancelAnimationFrame(animationId);
};
}, [patternSize, patternScaleX, patternScaleY, patternRefreshInterval, patternAlpha]);
return <canvas className="noise-overlay" ref={grainRef} style={{ imageRendering: 'pixelated' }} />;
};
export default Noise;
Component CSS
.noise-overlay {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: ribbons code: ## Integrate the <Ribbons /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Ribbons
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Ribbons from './Ribbons';
<div style={{ height: '500px', position: 'relative', overflow: 'hidden'}}>
<Ribbons
baseThickness={30}
colors={['#ffffff']}
speedMultiplier={0.5}
maxAge={500}
enableFade={false}
enableShaderEffect={true}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| colors | string[] | ['#5227FF'] | An array of color strings to be used for the ribbons. |
| baseSpring | number | 0.03 | Base spring factor for the physics controlling ribbon motion. |
| baseFriction | number | 0.9 | Base friction factor that dampens the ribbon motion. |
| baseThickness | number | 30 | The base thickness of the ribbons. |
| offsetFactor | number | 0.02 | A factor to horizontally offset the starting positions of the ribbons. |
| maxAge | number | 500 | Delay in milliseconds controlling how long the ribbon trails extend. |
| pointCount | number | 50 | The number of points that make up each ribbon. |
| speedMultiplier | number | 0.5 | Multiplier that adjusts how fast trailing points interpolate towards the head. |
| enableFade | boolean | true | If true, a fade effect is applied along the length of the ribbon. |
| enableShaderEffect | boolean | true | If true, an additional sine-wave shader effect is applied to the ribbons. |
| effectAmplitude | number | 2 | The amplitude of the shader displacement effect. |
| backgroundColor | number[] | [0, 0, 0, 0] | An RGBA array specifying the clear color for the renderer. |
Full Component Source
import { useEffect, useRef } from 'react';
import { Renderer, Transform, Vec3, Color, Polyline } from 'ogl';
import './Ribbons.css';
const Ribbons = ({
colors = ['#FC8EAC'],
baseSpring = 0.03,
baseFriction = 0.9,
baseThickness = 30,
offsetFactor = 0.05,
maxAge = 500,
pointCount = 50,
speedMultiplier = 0.6,
enableFade = false,
enableShaderEffect = false,
effectAmplitude = 2,
backgroundColor = [0, 0, 0, 0]
}) => {
const containerRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const renderer = new Renderer({ dpr: window.devicePixelRatio || 2, alpha: true });
const gl = renderer.gl;
if (Array.isArray(backgroundColor) && backgroundColor.length === 4) {
gl.clearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], backgroundColor[3]);
} else {
gl.clearColor(0, 0, 0, 0);
}
gl.canvas.style.position = 'absolute';
gl.canvas.style.top = '0';
gl.canvas.style.left = '0';
gl.canvas.style.width = '100%';
gl.canvas.style.height = '100%';
container.appendChild(gl.canvas);
const scene = new Transform();
const lines = [];
const vertex = `
precision highp float;
attribute vec3 position;
attribute vec3 next;
attribute vec3 prev;
attribute vec2 uv;
attribute float side;
uniform vec2 uResolution;
uniform float uDPR;
uniform float uThickness;
uniform float uTime;
uniform float uEnableShaderEffect;
uniform float uEffectAmplitude;
varying vec2 vUV;
vec4 getPosition() {
vec4 current = vec4(position, 1.0);
vec2 aspect = vec2(uResolution.x / uResolution.y, 1.0);
vec2 nextScreen = next.xy * aspect;
vec2 prevScreen = prev.xy * aspect;
vec2 tangent = normalize(nextScreen - prevScreen);
vec2 normal = vec2(-tangent.y, tangent.x);
normal /= aspect;
normal *= mix(1.0, 0.1, pow(abs(uv.y - 0.5) * 2.0, 2.0));
float dist = length(nextScreen - prevScreen);
normal *= smoothstep(0.0, 0.02, dist);
float pixelWidthRatio = 1.0 / (uResolution.y / uDPR);
float pixelWidth = current.w * pixelWidthRatio;
normal *= pixelWidth * uThickness;
current.xy -= normal * side;
if(uEnableShaderEffect > 0.5) {
current.xy += normal * sin(uTime + current.x * 10.0) * uEffectAmplitude;
}
return current;
}
void main() {
vUV = uv;
gl_Position = getPosition();
}
`;
const fragment = `
precision highp float;
uniform vec3 uColor;
uniform float uOpacity;
uniform float uEnableFade;
varying vec2 vUV;
void main() {
float fadeFactor = 1.0;
if(uEnableFade > 0.5) {
fadeFactor = 1.0 - smoothstep(0.0, 1.0, vUV.y);
}
gl_FragColor = vec4(uColor, uOpacity * fadeFactor);
}
`;
function resize() {
const width = container.clientWidth;
const height = container.clientHeight;
renderer.setSize(width, height);
lines.forEach(line => line.polyline.resize());
}
window.addEventListener('resize', resize);
const center = (colors.length - 1) / 2;
colors.forEach((color, index) => {
const spring = baseSpring + (Math.random() - 0.5) * 0.05;
const friction = baseFriction + (Math.random() - 0.5) * 0.05;
const thickness = baseThickness + (Math.random() - 0.5) * 3;
const mouseOffset = new Vec3(
(index - center) * offsetFactor + (Math.random() - 0.5) * 0.01,
(Math.random() - 0.5) * 0.1,
0
);
const line = {
spring,
friction,
mouseVelocity: new Vec3(),
mouseOffset
};
const count = pointCount;
const points = [];
for (let i = 0; i < count; i++) {
points.push(new Vec3());
}
line.points = points;
line.polyline = new Polyline(gl, {
points,
vertex,
fragment,
uniforms: {
uColor: { value: new Color(color) },
uThickness: { value: thickness },
uOpacity: { value: 1.0 },
uTime: { value: 0.0 },
uEnableShaderEffect: { value: enableShaderEffect ? 1.0 : 0.0 },
uEffectAmplitude: { value: effectAmplitude },
uEnableFade: { value: enableFade ? 1.0 : 0.0 }
}
});
line.polyline.mesh.setParent(scene);
lines.push(line);
});
resize();
const mouse = new Vec3();
function updateMouse(e) {
let x, y;
const rect = container.getBoundingClientRect();
if (e.changedTouches && e.changedTouches.length) {
x = e.changedTouches[0].clientX - rect.left;
y = e.changedTouches[0].clientY - rect.top;
} else {
x = e.clientX - rect.left;
y = e.clientY - rect.top;
}
const width = container.clientWidth;
const height = container.clientHeight;
mouse.set((x / width) * 2 - 1, (y / height) * -2 + 1, 0);
}
container.addEventListener('mousemove', updateMouse);
container.addEventListener('touchstart', updateMouse);
container.addEventListener('touchmove', updateMouse);
const tmp = new Vec3();
let frameId;
let lastTime = performance.now();
function update() {
frameId = requestAnimationFrame(update);
const currentTime = performance.now();
const dt = currentTime - lastTime;
lastTime = currentTime;
lines.forEach(line => {
tmp.copy(mouse).add(line.mouseOffset).sub(line.points[0]).multiply(line.spring);
line.mouseVelocity.add(tmp).multiply(line.friction);
line.points[0].add(line.mouseVelocity);
for (let i = 1; i < line.points.length; i++) {
if (isFinite(maxAge) && maxAge > 0) {
const segmentDelay = maxAge / (line.points.length - 1);
const alpha = Math.min(1, (dt * speedMultiplier) / segmentDelay);
line.points[i].lerp(line.points[i - 1], alpha);
} else {
line.points[i].lerp(line.points[i - 1], 0.9);
}
}
if (line.polyline.mesh.program.uniforms.uTime) {
line.polyline.mesh.program.uniforms.uTime.value = currentTime * 0.001;
}
line.polyline.updateGeometry();
});
renderer.render({ scene });
}
update();
return () => {
window.removeEventListener('resize', resize);
container.removeEventListener('mousemove', updateMouse);
container.removeEventListener('touchstart', updateMouse);
container.removeEventListener('touchmove', updateMouse);
cancelAnimationFrame(frameId);
if (gl.canvas && gl.canvas.parentNode === container) {
container.removeChild(gl.canvas);
}
};
}, [
colors,
baseSpring,
baseFriction,
baseThickness,
offsetFactor,
maxAge,
pointCount,
speedMultiplier,
enableFade,
enableShaderEffect,
effectAmplitude,
backgroundColor
]);
return <div ref={containerRef} className="ribbons-container" />;
};
export default Ribbons;
Component CSS
.ribbons-container {
width: 100%;
height: 100%;
position: relative;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: meta balls code: ## Integrate the <MetaBalls /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: MetaBalls
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import MetaBalls from './MetaBalls';
<MetaBalls
color="#ffffff"
cursorBallColor="#ffffff"
cursorBallSize={2}
ballCount={15}
animationSize={30}
enableMouseInteraction={true}
enableTransparency={true}
hoverSmoothness={0.05}
clumpFactor={1}
speed={0.3}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| color | string | #ffffff | The base color of the metaballs. |
| speed | number | 0.3 | Speed multiplier for the animation. |
| enableMouseInteraction | boolean | true | Enables or disables the ball following the mouse. |
| enableTransparency | boolean | false | Enables or disables transparency for the container of the animation. |
| hoverSmoothness | number | 0.05 | Smoothness factor for the cursor ball when following the mouse. |
| animationSize | number | 30 | The size of the world for the animation. |
| ballCount | number | 15 | Number of metaballs rendered. |
| clumpFactor | number | 1 | Determines how close together the balls are rendered. |
| cursorBallSize | number | 3 | Size of the cursor-controlled ball. |
| cursorBallColor | string | #ff0000 | Color of the cursor ball. |
Full Component Source
import { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle, Transform, Vec3, Camera } from 'ogl';
import './MetaBalls.css';
function parseHexColor(hex) {
const c = hex.replace('#', '');
const r = parseInt(c.substring(0, 2), 16) / 255;
const g = parseInt(c.substring(2, 4), 16) / 255;
const b = parseInt(c.substring(4, 6), 16) / 255;
return [r, g, b];
}
function fract(x) {
return x - Math.floor(x);
}
function hash31(p) {
let r = [p * 0.1031, p * 0.103, p * 0.0973].map(fract);
const r_yzx = [r[1], r[2], r[0]];
const dotVal = r[0] * (r_yzx[0] + 33.33) + r[1] * (r_yzx[1] + 33.33) + r[2] * (r_yzx[2] + 33.33);
for (let i = 0; i < 3; i++) {
r[i] = fract(r[i] + dotVal);
}
return r;
}
function hash33(v) {
let p = [v[0] * 0.1031, v[1] * 0.103, v[2] * 0.0973].map(fract);
const p_yxz = [p[1], p[0], p[2]];
const dotVal = p[0] * (p_yxz[0] + 33.33) + p[1] * (p_yxz[1] + 33.33) + p[2] * (p_yxz[2] + 33.33);
for (let i = 0; i < 3; i++) {
p[i] = fract(p[i] + dotVal);
}
const p_xxy = [p[0], p[0], p[1]];
const p_yxx = [p[1], p[0], p[0]];
const p_zyx = [p[2], p[1], p[0]];
const result = [];
for (let i = 0; i < 3; i++) {
result[i] = fract((p_xxy[i] + p_yxx[i]) * p_zyx[i]);
}
return result;
}
const vertex = `#version 300 es
precision highp float;
layout(location = 0) in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragment = `#version 300 es
precision highp float;
uniform vec3 iResolution;
uniform float iTime;
uniform vec3 iMouse;
uniform vec3 iColor;
uniform vec3 iCursorColor;
uniform float iAnimationSize;
uniform int iBallCount;
uniform float iCursorBallSize;
uniform vec3 iMetaBalls[50];
uniform float iClumpFactor;
uniform bool enableTransparency;
out vec4 outColor;
const float PI = 3.14159265359;
float getMetaBallValue(vec2 c, float r, vec2 p) {
vec2 d = p - c;
float dist2 = dot(d, d);
return (r * r) / dist2;
}
void main() {
vec2 fc = gl_FragCoord.xy;
float scale = iAnimationSize / iResolution.y;
vec2 coord = (fc - iResolution.xy * 0.5) * scale;
vec2 mouseW = (iMouse.xy - iResolution.xy * 0.5) * scale;
float m1 = 0.0;
for (int i = 0; i < 50; i++) {
if (i >= iBallCount) break;
m1 += getMetaBallValue(iMetaBalls[i].xy, iMetaBalls[i].z, coord);
}
float m2 = getMetaBallValue(mouseW, iCursorBallSize, coord);
float total = m1 + m2;
float f = smoothstep(-1.0, 1.0, (total - 1.3) / min(1.0, fwidth(total)));
vec3 cFinal = vec3(0.0);
if (total > 0.0) {
float alpha1 = m1 / total;
float alpha2 = m2 / total;
cFinal = iColor * alpha1 + iCursorColor * alpha2;
}
outColor = vec4(cFinal * f, enableTransparency ? f : 1.0);
}
`;
const MetaBalls = ({
className = '',
color = '#ffffff',
speed = 0.3,
enableMouseInteraction = true,
hoverSmoothness = 0.05,
animationSize = 30,
ballCount = 15,
clumpFactor = 1,
cursorBallSize = 3,
cursorBallColor = '#ffffff',
enableTransparency = true
}) => {
const containerRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const dpr = 1;
const renderer = new Renderer({ dpr, alpha: true, premultipliedAlpha: false });
const gl = renderer.gl;
gl.clearColor(0, 0, 0, enableTransparency ? 0 : 1);
container.appendChild(gl.canvas);
const camera = new Camera(gl, {
left: -1,
right: 1,
top: 1,
bottom: -1,
near: 0.1,
far: 10
});
camera.position.z = 1;
const geometry = new Triangle(gl);
const [r1, g1, b1] = parseHexColor(color);
const [r2, g2, b2] = parseHexColor(cursorBallColor);
const metaBallsUniform = [];
for (let i = 0; i < 50; i++) {
metaBallsUniform.push(new Vec3(0, 0, 0));
}
const program = new Program(gl, {
vertex,
fragment,
uniforms: {
iTime: { value: 0 },
iResolution: { value: new Vec3(0, 0, 0) },
iMouse: { value: new Vec3(0, 0, 0) },
iColor: { value: new Vec3(r1, g1, b1) },
iCursorColor: { value: new Vec3(r2, g2, b2) },
iAnimationSize: { value: animationSize },
iBallCount: { value: ballCount },
iCursorBallSize: { value: cursorBallSize },
iMetaBalls: { value: metaBallsUniform },
iClumpFactor: { value: clumpFactor },
enableTransparency: { value: enableTransparency }
}
});
const mesh = new Mesh(gl, { geometry, program });
const scene = new Transform();
mesh.setParent(scene);
const maxBalls = 50;
const effectiveBallCount = Math.min(ballCount, maxBalls);
const ballParams = [];
for (let i = 0; i < effectiveBallCount; i++) {
const idx = i + 1;
const h1 = hash31(idx);
const st = h1[0] * (2 * Math.PI);
const dtFactor = 0.1 * Math.PI + h1[1] * (0.4 * Math.PI - 0.1 * Math.PI);
const baseScale = 5.0 + h1[1] * (10.0 - 5.0);
const h2 = hash33(h1);
const toggle = Math.floor(h2[0] * 2.0);
const radiusVal = 0.5 + h2[2] * (2.0 - 0.5);
ballParams.push({ st, dtFactor, baseScale, toggle, radius: radiusVal });
}
const mouseBallPos = { x: 0, y: 0 };
let pointerInside = false;
let pointerX = 0;
let pointerY = 0;
function resize() {
if (!container) return;
const width = container.clientWidth;
const height = container.clientHeight;
renderer.setSize(width * dpr, height * dpr);
gl.canvas.style.width = width + 'px';
gl.canvas.style.height = height + 'px';
program.uniforms.iResolution.value.set(gl.canvas.width, gl.canvas.height, 0);
}
window.addEventListener('resize', resize);
resize();
function onPointerMove(e) {
if (!enableMouseInteraction) return;
const rect = container.getBoundingClientRect();
const px = e.clientX - rect.left;
const py = e.clientY - rect.top;
pointerX = (px / rect.width) * gl.canvas.width;
pointerY = (1 - py / rect.height) * gl.canvas.height;
}
function onPointerEnter() {
if (!enableMouseInteraction) return;
pointerInside = true;
}
function onPointerLeave() {
if (!enableMouseInteraction) return;
pointerInside = false;
}
container.addEventListener('pointermove', onPointerMove);
container.addEventListener('pointerenter', onPointerEnter);
container.addEventListener('pointerleave', onPointerLeave);
const startTime = performance.now();
let animationFrameId;
function update(t) {
animationFrameId = requestAnimationFrame(update);
const elapsed = (t - startTime) * 0.001;
program.uniforms.iTime.value = elapsed;
for (let i = 0; i < effectiveBallCount; i++) {
const p = ballParams[i];
const dt = elapsed * speed * p.dtFactor;
const th = p.st + dt;
const x = Math.cos(th);
const y = Math.sin(th + dt * p.toggle);
const posX = x * p.baseScale * clumpFactor;
const posY = y * p.baseScale * clumpFactor;
metaBallsUniform[i].set(posX, posY, p.radius);
}
let targetX, targetY;
if (pointerInside) {
targetX = pointerX;
targetY = pointerY;
} else {
const cx = gl.canvas.width * 0.5;
const cy = gl.canvas.height * 0.5;
const rx = gl.canvas.width * 0.15;
const ry = gl.canvas.height * 0.15;
targetX = cx + Math.cos(elapsed * speed) * rx;
targetY = cy + Math.sin(elapsed * speed) * ry;
}
mouseBallPos.x += (targetX - mouseBallPos.x) * hoverSmoothness;
mouseBallPos.y += (targetY - mouseBallPos.y) * hoverSmoothness;
program.uniforms.iMouse.value.set(mouseBallPos.x, mouseBallPos.y, 0);
renderer.render({ scene, camera });
}
animationFrameId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animationFrameId);
window.removeEventListener('resize', resize);
container.removeEventListener('pointermove', onPointerMove);
container.removeEventListener('pointerenter', onPointerEnter);
container.removeEventListener('pointerleave', onPointerLeave);
container.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, [
color,
cursorBallColor,
speed,
enableMouseInteraction,
hoverSmoothness,
animationSize,
ballCount,
clumpFactor,
cursorBallSize,
enableTransparency
]);
return <div ref={containerRef} className={`metaballs-container ${className}`} />;
};
export default MetaBalls;
Component CSS
.metaballs-container {
position: relative;
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: fluid glass code: ## Integrate the <FluidGlass /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: FluidGlass
Variant: JavaScript + CSS
Dependencies: three @react-three/fiber @react-three/drei maath
Usage Example
// IMPORTANT INFO BELOW
// This component requires a 3D model to function correctly.
// You can find three example models in the 'public/assets/3d' directory of the repository:
// - 'lens.glb'
// - 'bar.glb'
// - 'cube.glb'
// Make sure to place these models in the correct directory or update the paths accordingly.
import FluidGlass from './FluidGlass'
<div style={{ height: '600px', position: 'relative' }}>
<FluidGlass
mode="lens" // or "bar", "cube"
lensProps={{
scale: 0.25,
ior: 1.15,
thickness: 5,
chromaticAberration: 0.1,
anisotropy: 0.01
}}
barProps={} // add specific props if using bar mode
cubeProps={} // add specific props if using cube mode
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| mode | string | 'lens' | Display mode of the fluid glass effect. Options: 'lens', 'bar', 'cube' |
| lensProps | object | {} | Props specific to lens mode including material properties like ior, thickness, transmission |
| barProps | object | {} | Props specific to bar mode including navItems array and material properties |
| cubeProps | object | {} | Props specific to cube mode including material properties and interaction settings |
Full Component Source
/* eslint-disable react/no-unknown-property */
import * as THREE from 'three';
import { useRef, useState, useEffect, memo } from 'react';
import { Canvas, createPortal, useFrame, useThree } from '@react-three/fiber';
import {
useFBO,
useGLTF,
useScroll,
Image,
Scroll,
Preload,
ScrollControls,
MeshTransmissionMaterial,
Text
} from '@react-three/drei';
import { easing } from 'maath';
export default function FluidGlass({ mode = 'lens', lensProps = {}, barProps = {}, cubeProps = {} }) {
const Wrapper = mode === 'bar' ? Bar : mode === 'cube' ? Cube : Lens;
const rawOverrides = mode === 'bar' ? barProps : mode === 'cube' ? cubeProps : lensProps;
const {
navItems = [
{ label: 'Home', link: '' },
{ label: 'About', link: '' },
{ label: 'Contact', link: '' }
],
...modeProps
} = rawOverrides;
return (
<Canvas camera={{ position: [0, 0, 20], fov: 15 }} gl={{ alpha: true }}>
<ScrollControls damping={0.2} pages={3} distance={0.4}>
{mode === 'bar' && <NavItems items={navItems} />}
<Wrapper modeProps={modeProps}>
<Scroll>
<Typography />
<Images />
</Scroll>
<Scroll html />
<Preload />
</Wrapper>
</ScrollControls>
</Canvas>
);
}
const ModeWrapper = memo(function ModeWrapper({
children,
glb,
geometryKey,
lockToBottom = false,
followPointer = true,
modeProps = {},
...props
}) {
const ref = useRef();
const { nodes } = useGLTF(glb);
const buffer = useFBO();
const { viewport: vp } = useThree();
const [scene] = useState(() => new THREE.Scene());
const geoWidthRef = useRef(1);
useEffect(() => {
const geo = nodes[geometryKey]?.geometry;
geo.computeBoundingBox();
geoWidthRef.current = geo.boundingBox.max.x - geo.boundingBox.min.x || 1;
}, [nodes, geometryKey]);
useFrame((state, delta) => {
const { gl, viewport, pointer, camera } = state;
const v = viewport.getCurrentViewport(camera, [0, 0, 15]);
const destX = followPointer ? (pointer.x * v.width) / 2 : 0;
const destY = lockToBottom ? -v.height / 2 + 0.2 : followPointer ? (pointer.y * v.height) / 2 : 0;
easing.damp3(ref.current.position, [destX, destY, 15], 0.15, delta);
if (modeProps.scale == null) {
const maxWorld = v.width * 0.9;
const desired = maxWorld / geoWidthRef.current;
ref.current.scale.setScalar(Math.min(0.15, desired));
}
gl.setRenderTarget(buffer);
gl.render(scene, camera);
gl.setRenderTarget(null);
// Background Color
gl.setClearColor(0x5227ff, 1);
});
const { scale, ior, thickness, anisotropy, chromaticAberration, ...extraMat } = modeProps;
return (
<>
{createPortal(children, scene)}
<mesh scale={[vp.width, vp.height, 1]}>
<planeGeometry />
<meshBasicMaterial map={buffer.texture} transparent />
</mesh>
<mesh ref={ref} scale={scale ?? 0.15} rotation-x={Math.PI / 2} geometry={nodes[geometryKey]?.geometry} {...props}>
<MeshTransmissionMaterial
buffer={buffer.texture}
ior={ior ?? 1.15}
thickness={thickness ?? 5}
anisotropy={anisotropy ?? 0.01}
chromaticAberration={chromaticAberration ?? 0.1}
{...extraMat}
/>
</mesh>
</>
);
});
function Lens({ modeProps, ...p }) {
return <ModeWrapper glb="/assets/3d/lens.glb" geometryKey="Cylinder" followPointer modeProps={modeProps} {...p} />;
}
function Cube({ modeProps, ...p }) {
return <ModeWrapper glb="/assets/3d/cube.glb" geometryKey="Cube" followPointer modeProps={modeProps} {...p} />;
}
function Bar({ modeProps = {}, ...p }) {
const defaultMat = {
transmission: 1,
roughness: 0,
thickness: 10,
ior: 1.15,
color: '#ffffff',
attenuationColor: '#ffffff',
attenuationDistance: 0.25
};
return (
<ModeWrapper
glb="/assets/3d/bar.glb"
geometryKey="Cube"
lockToBottom
followPointer={false}
modeProps={{ ...defaultMat, ...modeProps }}
{...p}
/>
);
}
function NavItems({ items }) {
const group = useRef();
const { viewport, camera } = useThree();
const DEVICE = {
mobile: { max: 639, spacing: 0.2, fontSize: 0.035 },
tablet: { max: 1023, spacing: 0.24, fontSize: 0.035 },
desktop: { max: Infinity, spacing: 0.3, fontSize: 0.035 }
};
const getDevice = () => {
const w = window.innerWidth;
return w <= DEVICE.mobile.max ? 'mobile' : w <= DEVICE.tablet.max ? 'tablet' : 'desktop';
};
const [device, setDevice] = useState(getDevice());
useEffect(() => {
const onResize = () => setDevice(getDevice());
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const { spacing, fontSize } = DEVICE[device];
useFrame(() => {
if (!group.current) return;
const v = viewport.getCurrentViewport(camera, [0, 0, 15]);
group.current.position.set(0, -v.height / 2 + 0.2, 15.1);
group.current.children.forEach((child, i) => {
child.position.x = (i - (items.length - 1) / 2) * spacing;
});
});
const handleNavigate = link => {
if (!link) return;
link.startsWith('#') ? (window.location.hash = link) : (window.location.href = link);
};
return (
<group ref={group} renderOrder={10}>
{items.map(({ label, link }) => (
<Text
key={label}
fontSize={fontSize}
color="white"
anchorX="center"
anchorY="middle"
depthWrite={false}
outlineWidth={0}
outlineBlur="20%"
outlineColor="#000"
outlineOpacity={0.5}
depthTest={false}
renderOrder={10}
onClick={e => {
e.stopPropagation();
handleNavigate(link);
}}
onPointerOver={() => (document.body.style.cursor = 'pointer')}
onPointerOut={() => (document.body.style.cursor = 'auto')}
>
{label}
</Text>
))}
</group>
);
}
function Images() {
const group = useRef();
const data = useScroll();
const { height } = useThree(s => s.viewport);
useFrame(() => {
group.current.children[0].material.zoom = 1 + data.range(0, 1 / 3) / 3;
group.current.children[1].material.zoom = 1 + data.range(0, 1 / 3) / 3;
group.current.children[2].material.zoom = 1 + data.range(1.15 / 3, 1 / 3) / 2;
group.current.children[3].material.zoom = 1 + data.range(1.15 / 3, 1 / 3) / 2;
group.current.children[4].material.zoom = 1 + data.range(1.15 / 3, 1 / 3) / 2;
});
return (
<group ref={group}>
<Image position={[-2, 0, 0]} scale={[3, height / 1.1, 1]} url="/assets/demo/cs1.webp" />
<Image position={[2, 0, 3]} scale={3} url="/assets/demo/cs2.webp" />
<Image position={[-2.05, -height, 6]} scale={[1, 3, 1]} url="/assets/demo/cs3.webp" />
<Image position={[-0.6, -height, 9]} scale={[1, 2, 1]} url="/assets/demo/cs1.webp" />
<Image position={[0.75, -height, 10.5]} scale={1.5} url="/assets/demo/cs2.webp" />
</group>
);
}
function Typography() {
const DEVICE = {
mobile: { fontSize: 0.2 },
tablet: { fontSize: 0.4 },
desktop: { fontSize: 0.6 }
};
const getDevice = () => {
const w = window.innerWidth;
return w <= 639 ? 'mobile' : w <= 1023 ? 'tablet' : 'desktop';
};
const [device, setDevice] = useState(getDevice());
useEffect(() => {
const onResize = () => setDevice(getDevice());
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
const { fontSize } = DEVICE[device];
return (
<Text
position={[0, 0, 12]}
fontSize={fontSize}
letterSpacing={-0.05}
outlineWidth={0}
outlineBlur="20%"
outlineColor="#000"
outlineOpacity={0.5}
color="white"
anchorX="center"
anchorY="middle"
>
React Bits
</Text>
);
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: chroma grid code: ## Integrate the <ChromaGrid /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: ChromaGrid
Variant: JavaScript + CSS
Dependencies: gsap
Usage Example
import ChromaGrid from './ChromaGrid'
const items = [
{
image: "https://i.pravatar.cc/300?img=1",
title: "Sarah Johnson",
subtitle: "Frontend Developer",
handle: "@sarahjohnson",
borderColor: "#3B82F6",
gradient: "linear-gradient(145deg, #3B82F6, #000)",
url: "https://github.com/sarahjohnson"
},
{
image: "https://i.pravatar.cc/300?img=2",
title: "Mike Chen",
subtitle: "Backend Engineer",
handle: "@mikechen",
borderColor: "#10B981",
gradient: "linear-gradient(180deg, #10B981, #000)",
url: "https://linkedin.com/in/mikechen"
}
];
<div style={{ height: '600px', position: 'relative' }}>
<ChromaGrid
items={items}
radius={300}
damping={0.45}
fadeOut={0.6}
ease="power3.out"
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items | Array | Demo [] | Array of ChromaItem objects to display in the grid |
| className | string | '' | Additional CSS classes to apply to the grid container |
| radius | number | 300 | Size of the spotlight effect in pixels |
| damping | number | 0.45 | Cursor follow animation duration in seconds |
| fadeOut | number | 0.6 | Fade-out animation duration in seconds when mouse leaves |
| ease | string | 'power3.out' | GSAP easing function for animations |
Full Component Source
import { useRef, useEffect } from 'react';
import { gsap } from 'gsap';
import './ChromaGrid.css';
export const ChromaGrid = ({
items,
className = '',
radius = 300,
columns = 3,
rows = 2,
damping = 0.45,
fadeOut = 0.6,
ease = 'power3.out'
}) => {
const rootRef = useRef(null);
const fadeRef = useRef(null);
const setX = useRef(null);
const setY = useRef(null);
const pos = useRef({ x: 0, y: 0 });
const demo = [
{
image: 'https://i.pravatar.cc/300?img=8',
title: 'Alex Rivera',
subtitle: 'Full Stack Developer',
handle: '@alexrivera',
borderColor: '#4F46E5',
gradient: 'linear-gradient(145deg, #4F46E5, #000)',
url: 'https://github.com/'
},
{
image: 'https://i.pravatar.cc/300?img=11',
title: 'Jordan Chen',
subtitle: 'DevOps Engineer',
handle: '@jordanchen',
borderColor: '#10B981',
gradient: 'linear-gradient(210deg, #10B981, #000)',
url: 'https://linkedin.com/in/'
},
{
image: 'https://i.pravatar.cc/300?img=3',
title: 'Morgan Blake',
subtitle: 'UI/UX Designer',
handle: '@morganblake',
borderColor: '#F59E0B',
gradient: 'linear-gradient(165deg, #F59E0B, #000)',
url: 'https://dribbble.com/'
},
{
image: 'https://i.pravatar.cc/300?img=16',
title: 'Casey Park',
subtitle: 'Data Scientist',
handle: '@caseypark',
borderColor: '#EF4444',
gradient: 'linear-gradient(195deg, #EF4444, #000)',
url: 'https://kaggle.com/'
},
{
image: 'https://i.pravatar.cc/300?img=25',
title: 'Sam Kim',
subtitle: 'Mobile Developer',
handle: '@thesamkim',
borderColor: '#8B5CF6',
gradient: 'linear-gradient(225deg, #8B5CF6, #000)',
url: 'https://github.com/'
},
{
image: 'https://i.pravatar.cc/300?img=60',
title: 'Tyler Rodriguez',
subtitle: 'Cloud Architect',
handle: '@tylerrod',
borderColor: '#06B6D4',
gradient: 'linear-gradient(135deg, #06B6D4, #000)',
url: 'https://aws.amazon.com/'
}
];
const data = items?.length ? items : demo;
useEffect(() => {
const el = rootRef.current;
if (!el) return;
setX.current = gsap.quickSetter(el, '--x', 'px');
setY.current = gsap.quickSetter(el, '--y', 'px');
const { width, height } = el.getBoundingClientRect();
pos.current = { x: width / 2, y: height / 2 };
setX.current(pos.current.x);
setY.current(pos.current.y);
}, []);
const moveTo = (x, y) => {
gsap.to(pos.current, {
x,
y,
duration: damping,
ease,
onUpdate: () => {
setX.current?.(pos.current.x);
setY.current?.(pos.current.y);
},
overwrite: true
});
};
const handleMove = e => {
const r = rootRef.current.getBoundingClientRect();
moveTo(e.clientX - r.left, e.clientY - r.top);
gsap.to(fadeRef.current, { opacity: 0, duration: 0.25, overwrite: true });
};
const handleLeave = () => {
gsap.to(fadeRef.current, {
opacity: 1,
duration: fadeOut,
overwrite: true
});
};
const handleCardClick = url => {
if (url) {
window.open(url, '_blank', 'noopener,noreferrer');
}
};
const handleCardMove = e => {
const card = e.currentTarget;
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
card.style.setProperty('--mouse-x', `${x}px`);
card.style.setProperty('--mouse-y', `${y}px`);
};
return (
<div
ref={rootRef}
className={`chroma-grid ${className}`}
style={{
'--r': `${radius}px`,
'--cols': columns,
'--rows': rows
}}
onPointerMove={handleMove}
onPointerLeave={handleLeave}
>
{data.map((c, i) => (
<article
key={i}
className="chroma-card"
onMouseMove={handleCardMove}
onClick={() => handleCardClick(c.url)}
style={{
'--card-border': c.borderColor || 'transparent',
'--card-gradient': c.gradient,
cursor: c.url ? 'pointer' : 'default'
}}
>
<div className="chroma-img-wrapper">
<img src={c.image} alt={c.title} loading="lazy" />
</div>
<footer className="chroma-info">
<h3 className="name">{c.title}</h3>
{c.handle && <span className="handle">{c.handle}</span>}
<p className="role">{c.subtitle}</p>
{c.location && <span className="location">{c.location}</span>}
</footer>
</article>
))}
<div className="chroma-overlay" />
<div ref={fadeRef} className="chroma-fade" />
</div>
);
};
export default ChromaGrid;
Component CSS
.chroma-grid {
position: relative;
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(var(--cols, 3), 320px);
grid-auto-rows: auto;
justify-content: center;
gap: 0.75rem;
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
box-sizing: border-box;
--x: 50%;
--y: 50%;
--r: 220px;
}
@media (max-width: 1124px) {
.chroma-grid {
grid-template-columns: repeat(auto-fit, minmax(320px, 320px));
gap: 0.5rem;
padding: 0.5rem;
}
}
@media (max-width: 480px) {
.chroma-grid {
grid-template-columns: 320px;
gap: 0.75rem;
padding: 1rem;
}
}
.chroma-card {
position: relative;
display: flex;
flex-direction: column;
width: 320px;
height: auto;
border-radius: 20px;
overflow: hidden;
border: 1px solid #333;
transition: border-color 0.3s ease;
background: var(--card-gradient);
--mouse-x: 50%;
--mouse-y: 50%;
--spotlight-color: rgba(255, 255, 255, 0.3);
}
.chroma-card:hover {
border-color: var(--card-border);
}
.chroma-card::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at var(--mouse-x) var(--mouse-y), var(--spotlight-color), transparent 70%);
pointer-events: none;
opacity: 0;
transition: opacity 0.5s ease;
z-index: 2;
}
.chroma-card:hover::before {
opacity: 1;
}
.chroma-img-wrapper {
position: relative;
z-index: 1;
flex: 1;
padding: 10px;
box-sizing: border-box;
background: transparent;
transition: background 0.3s ease;
}
.chroma-img-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px;
display: block;
}
.chroma-info {
position: relative;
z-index: 1;
padding: 0.75rem 1rem;
color: #fff;
font-family: system-ui, sans-serif;
display: grid;
grid-template-columns: 1fr auto;
row-gap: 0.25rem;
column-gap: 0.75rem;
}
.chroma-info .role,
.chroma-info .handle {
color: #aaa;
}
.chroma-overlay {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 3;
backdrop-filter: grayscale(1) brightness(0.78);
-webkit-backdrop-filter: grayscale(1) brightness(0.78);
background: rgba(0, 0, 0, 0.001);
mask-image: radial-gradient(
circle var(--r) at var(--x) var(--y),
transparent 0%,
transparent 15%,
rgba(0, 0, 0, 0.1) 30%,
rgba(0, 0, 0, 0.22) 45%,
rgba(0, 0, 0, 0.35) 60%,
rgba(0, 0, 0, 0.5) 75%,
rgba(0, 0, 0, 0.68) 88%,
white 100%
);
-webkit-mask-image: radial-gradient(
circle var(--r) at var(--x) var(--y),
transparent 0%,
transparent 15%,
rgba(0, 0, 0, 0.1) 30%,
rgba(0, 0, 0, 0.22) 45%,
rgba(0, 0, 0, 0.35) 60%,
rgba(0, 0, 0, 0.5) 75%,
rgba(0, 0, 0, 0.68) 88%,
white 100%
);
}
.chroma-fade {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 4;
backdrop-filter: grayscale(1) brightness(0.78);
-webkit-backdrop-filter: grayscale(1) brightness(0.78);
background: rgba(0, 0, 0, 0.001);
mask-image: radial-gradient(
circle var(--r) at var(--x) var(--y),
white 0%,
white 15%,
rgba(255, 255, 255, 0.9) 30%,
rgba(255, 255, 255, 0.78) 45%,
rgba(255, 255, 255, 0.65) 60%,
rgba(255, 255, 255, 0.5) 75%,
rgba(255, 255, 255, 0.32) 88%,
transparent 100%
);
-webkit-mask-image: radial-gradient(
circle var(--r) at var(--x) var(--y),
white 0%,
white 15%,
rgba(255, 255, 255, 0.9) 30%,
rgba(255, 255, 255, 0.78) 45%,
rgba(255, 255, 255, 0.65) 60%,
rgba(255, 255, 255, 0.5) 75%,
rgba(255, 255, 255, 0.32) 88%,
transparent 100%
);
opacity: 1;
transition: opacity 0.25s ease;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: ferrofluid code: ## Integrate the <Ferrofluid /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Ferrofluid
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Ferrofluid from './Ferrofluid';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Ferrofluid
colors={["#ffffff","#ffffff","#ffffff"]}
speed={0.5}
scale={1}
turbulence={1}
fluidity={0.1}
rimWidth={0.2}
sharpness={3}
shimmer={1}
glow={2}
flowDirection="down"
opacity={1}
mouseInteraction={true}
mouseStrength={1}
mouseRadius={0.3}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| colors | string[] | ['#4F46E5', '#06B6D4', '#E0F2FE'] | Array of hex colors (up to 8) used to tint the fluid rim. Colors are spread across the surface by height; a single color makes the whole effect uniform. |
| backgroundColor | string | '#03010A' | Hex color of the background behind the fluid. |
| backgroundColor | string | '#03010A' | Hex color of the background behind the fluid. |
| speed | number | 0.5 | Multiplier for how fast the fluid churns and flows. |
| scale | number | 1 | Overall feature size. Higher values zoom in for larger, fewer blobs. |
| turbulence | number | 1 | Amount of domain distortion. Higher values create more chaotic, swirling motion. |
| fluidity | number | 0.1 | Smoothness of the merge between the two fluid layers. Higher = softer, more liquid blending. |
| rimWidth | number | 0.2 | Thickness of the glowing contour lines tracing the fluid surface. |
| sharpness | number | 3 | Contrast of the rim highlights. Higher values give crisper, thinner edges. |
| shimmer | number | 1 | Amount of fine grainy break-up applied to the rim. 0 = smooth lines. |
| glow | number | 2 | Overall brightness multiplier of the rim highlights. |
| flowDirection | 'up' | 'down' | 'left' |
| opacity | number | 1 | Overall alpha of the rendered canvas. |
| mouseInteraction | boolean | true | Enables a magnetic spike that rises and glows under the cursor. |
| mouseStrength | number | 1 | Intensity of the magnetic cursor spike. |
| mouseRadius | number | 0.3 | Falloff radius of the magnetic cursor spike. |
| mouseDampening | number | 0.15 | Easing time constant (seconds) for the cursor to follow the pointer. 0 = immediate. |
| mixBlendMode | string | undefined | CSS mix-blend-mode applied to the canvas (e.g. 'screen', 'lighten'). |
| paused | boolean | false | If true, stops rendering updates (freezing the current frame). |
| dpr | number | window.devicePixelRatio | Overrides device pixel ratio; lower for performance, higher for sharpness. |
| className | string | — | Additional class names for the root container. |
Full Component Source
import React, { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle } from 'ogl';
import './Ferrofluid.css';
const MAX_COLORS = 8;
const hexToRGB = hex => {
const c = hex.replace('#', '').padEnd(6, '0');
const r = parseInt(c.slice(0, 2), 16) / 255;
const g = parseInt(c.slice(2, 4), 16) / 255;
const b = parseInt(c.slice(4, 6), 16) / 255;
return [r, g, b];
};
const prepColors = input => {
const base = (input && input.length ? input : ['#4F46E5', '#06B6D4', '#E0F2FE']).slice(0, MAX_COLORS);
const count = base.length;
const arr = [];
for (let i = 0; i < MAX_COLORS; i++) arr.push(hexToRGB(base[Math.min(i, base.length - 1)]));
const avg = [0, 0, 0];
for (let i = 0; i < count; i++) {
avg[0] += arr[i][0];
avg[1] += arr[i][1];
avg[2] += arr[i][2];
}
avg[0] /= count;
avg[1] /= count;
avg[2] /= count;
return { arr, count, avg };
};
const flowVec = d => {
switch (d) {
case 'up':
return [0, 1];
case 'down':
return [0, -1];
case 'left':
return [-1, 0];
case 'right':
return [1, 0];
default:
return [0, -1];
}
};
const vertex = `
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragment = `
precision highp float;
uniform vec3 iResolution;
uniform vec2 iMouse;
uniform float iTime;
uniform vec3 uColor0;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform vec3 uColor3;
uniform vec3 uColor4;
uniform vec3 uColor5;
uniform vec3 uColor6;
uniform vec3 uColor7;
uniform int uColorCount;
uniform vec3 uMouseColor;
uniform vec2 uFlow;
uniform float uSpeed;
uniform float uScale;
uniform float uTurbulence;
uniform float uFluidity;
uniform float uRimWidth;
uniform float uSharpness;
uniform float uShimmer;
uniform float uGlow;
uniform float uOpacity;
uniform float uMouseEnabled;
uniform float uMouseStrength;
uniform float uMouseRadius;
varying vec2 vUv;
#define PI 3.14159265
vec3 palette(float h) {
int count = uColorCount;
if (count < 1) count = 1;
int idx = int(floor(clamp(h, 0.0, 0.999999) * float(count)));
if (idx <= 0) return uColor0;
if (idx == 1) return uColor1;
if (idx == 2) return uColor2;
if (idx == 3) return uColor3;
if (idx == 4) return uColor4;
if (idx == 5) return uColor5;
if (idx == 6) return uColor6;
return uColor7;
}
float hash(vec3 p3) {
p3 = fract(p3 * 0.1031);
p3 += dot(p3, p3.zyx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
float smin(float a, float b, float k) {
float r = exp2(-a / k) + exp2(-b / k);
return -k * log2(r);
}
float sinlerp(float a, float b, float w) {
return mix(a, b, (sin(w * PI - PI / 2.0) + 1.0) / 2.0);
}
float vn(vec2 p, float s, float seed) {
vec2 cellp = floor(p / s);
vec2 relp = mod(p, s);
float g1 = hash(vec3(cellp, seed));
float g2 = hash(vec3(cellp.x + 1.0, cellp.y, seed));
float g3 = hash(vec3(cellp.x + 1.0, cellp.y + 1.0, seed));
float g4 = hash(vec3(cellp.x, cellp.y + 1.0, seed));
float bx = sinlerp(g1, g2, relp.x / s);
float tx = sinlerp(g4, g3, relp.x / s);
return sinlerp(bx, tx, relp.y / s);
}
float dbn(vec2 p, float s, float seed) {
float o = s / 2.0;
float n0 = vn(p, s, seed);
float n1 = vn(p + vec2(o, o), s, seed + 0.1);
float n2 = vn(p + vec2(-o, o), s, seed + 0.2);
float n3 = vn(p + vec2(o, -o), s, seed + 0.3);
float n4 = vn(p + vec2(-o, -o), s, seed + 0.4);
return (2.0 * n0 + 1.5 * n1 + 1.25 * n2 + 1.125 * n3 + n4) / 7.0;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
float ref = 700.0 / max(uScale, 0.05);
vec2 p = fragCoord / iResolution.y * ref;
float spd = 200.0 * uSpeed;
float t = iTime;
vec2 dir = uFlow;
vec2 perp = vec2(-dir.y, dir.x);
float distort1 = vn(p + perp * (t * spd), 60.0, 10.0) * 50.0 * uTurbulence;
float distort2 = vn(p - perp * (t * spd), 120.0, 15.0) * 100.0 * uTurbulence;
float peaks = dbn(p + distort1 + dir * (t * spd * 0.5), 40.0, 1.0);
float peaks2 = dbn(p + distort2 - dir * (t * spd * 0.5), 40.0, 0.0);
float mapeaks = smin(peaks, peaks2, max(uFluidity, 0.001));
float mGlow = 0.0;
if (uMouseEnabled > 0.5) {
vec2 mp = iMouse / iResolution.y * ref;
float md = length(p - mp) / ref;
float rr = max(uMouseRadius, 0.02);
mGlow = exp(-md * md / (rr * rr)) * uMouseStrength;
}
float band = (uRimWidth - abs((mapeaks - 0.4) * 2.0)) * 5.0;
float ltn = clamp(band - vn(p + dir * (t * spd * 0.5), 60.0, 12.0) * uShimmer, 0.0, 1.0);
ltn = pow(ltn, uSharpness) * uGlow;
ltn *= clamp(1.0 - mGlow, 0.0, 1.0);
float h = clamp(0.5 + (peaks - peaks2) * 0.8, 0.0, 1.0);
vec3 col = palette(h);
vec3 outc = col * ltn;
float a = clamp(max(outc.r, max(outc.g, outc.b)), 0.0, 1.0);
fragColor = vec4(outc, a * uOpacity);
}
void main() {
vec4 color;
mainImage(color, vUv * iResolution.xy);
gl_FragColor = color;
}
`;
const Ferrofluid = ({
className,
dpr,
paused = false,
colors = ['#ffffff', '#ffffff', '#ffffff'],
speed = 0.5,
scale = 1.6,
turbulence = 1,
fluidity = 0.1,
rimWidth = 0.2,
sharpness = 2.5,
shimmer = 1.5,
glow = 2,
flowDirection = 'down',
opacity = 1,
mouseInteraction = true,
mouseStrength = 1,
mouseRadius = 0.35,
mouseDampening = 0.15,
mixBlendMode
}) => {
const containerRef = useRef(null);
const rafRef = useRef(null);
const programRef = useRef(null);
const meshRef = useRef(null);
const geometryRef = useRef(null);
const rendererRef = useRef(null);
const mouseTargetRef = useRef([0, 0]);
const lastTimeRef = useRef(0);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const renderer = new Renderer({
dpr: dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1),
alpha: true,
antialias: true
});
rendererRef.current = renderer;
const gl = renderer.gl;
const canvas = gl.canvas;
gl.clearColor(0, 0, 0, 0);
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.display = 'block';
container.appendChild(canvas);
const { arr, count, avg } = prepColors(colors);
const uniforms = {
iResolution: { value: [gl.drawingBufferWidth, gl.drawingBufferHeight, 1] },
iMouse: { value: [0, 0] },
iTime: { value: 0 },
uColor0: { value: arr[0] },
uColor1: { value: arr[1] },
uColor2: { value: arr[2] },
uColor3: { value: arr[3] },
uColor4: { value: arr[4] },
uColor5: { value: arr[5] },
uColor6: { value: arr[6] },
uColor7: { value: arr[7] },
uColorCount: { value: count },
uMouseColor: { value: avg },
uFlow: { value: flowVec(flowDirection) },
uSpeed: { value: speed },
uScale: { value: scale },
uTurbulence: { value: turbulence },
uFluidity: { value: fluidity },
uRimWidth: { value: rimWidth },
uSharpness: { value: sharpness },
uShimmer: { value: shimmer },
uGlow: { value: glow },
uOpacity: { value: opacity },
uMouseEnabled: { value: mouseInteraction ? 1 : 0 },
uMouseStrength: { value: mouseStrength },
uMouseRadius: { value: mouseRadius }
};
const program = new Program(gl, { vertex, fragment, uniforms });
programRef.current = program;
const geometry = new Triangle(gl);
geometryRef.current = geometry;
const mesh = new Mesh(gl, { geometry, program });
meshRef.current = mesh;
const resize = () => {
const rect = container.getBoundingClientRect();
renderer.setSize(rect.width, rect.height);
uniforms.iResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight, 1];
};
resize();
const ro = new ResizeObserver(resize);
ro.observe(container);
const onPointerMove = e => {
const rect = canvas.getBoundingClientRect();
const sc = renderer.dpr || 1;
const x = (e.clientX - rect.left) * sc;
const y = (rect.height - (e.clientY - rect.top)) * sc;
mouseTargetRef.current = [x, y];
if (mouseDampening <= 0) {
uniforms.iMouse.value = [x, y];
}
};
if (mouseInteraction) {
canvas.addEventListener('pointermove', onPointerMove);
}
const loop = t => {
rafRef.current = requestAnimationFrame(loop);
uniforms.iTime.value = t * 0.001;
if (mouseDampening > 0) {
if (!lastTimeRef.current) lastTimeRef.current = t;
const dt = (t - lastTimeRef.current) / 1000;
lastTimeRef.current = t;
const tau = Math.max(1e-4, mouseDampening);
let factor = 1 - Math.exp(-dt / tau);
if (factor > 1) factor = 1;
const target = mouseTargetRef.current;
const cur = uniforms.iMouse.value;
cur[0] += (target[0] - cur[0]) * factor;
cur[1] += (target[1] - cur[1]) * factor;
} else {
lastTimeRef.current = t;
}
if (!paused && programRef.current && meshRef.current) {
try {
renderer.render({ scene: meshRef.current });
} catch (e) {
console.error(e);
}
}
};
rafRef.current = requestAnimationFrame(loop);
return () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
if (mouseInteraction) canvas.removeEventListener('pointermove', onPointerMove);
ro.disconnect();
if (canvas.parentElement === container) {
container.removeChild(canvas);
}
const callIfFn = (obj, key) => {
const fn = obj && obj[key];
if (typeof fn === 'function') {
fn.call(obj);
}
};
callIfFn(programRef.current, 'remove');
callIfFn(geometryRef.current, 'remove');
callIfFn(meshRef.current, 'remove');
callIfFn(rendererRef.current, 'destroy');
programRef.current = null;
geometryRef.current = null;
meshRef.current = null;
rendererRef.current = null;
};
}, [
dpr,
paused,
colors,
speed,
scale,
turbulence,
fluidity,
rimWidth,
sharpness,
shimmer,
glow,
flowDirection,
opacity,
mouseInteraction,
mouseStrength,
mouseRadius,
mouseDampening
]);
return (
<div
ref={containerRef}
className={`ferrofluid-container ${className ?? ''}`}
style={{
...(mixBlendMode && { mixBlendMode })
}}
/>
);
};
export default Ferrofluid;
Component CSS
.ferrofluid-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: light fall code: ## Integrate the <Lightfall /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Lightfall
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Lightfall from './Lightfall';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Lightfall
colors={['#A6C8FF', '#5227FF', '#FF9FFC']}
backgroundColor="#0A29FF"
speed={1}
streakCount={8}
streakWidth={1}
streakLength={1}
glow={1}
density={1}
twinkle={1}
zoom={2}
backgroundGlow={1}
opacity={1}
mouseInteraction={true}
mouseStrength={1}
mouseRadius={0.6}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| colors | string[] | ['#A6C8FF', '#5227FF', '#FF9FFC'] | Array of hex colors (up to 8) used to tint the falling light streaks. Each streak is randomly but evenly assigned one of the colors; a single color makes the whole effect uniform. |
| backgroundColor | string | '#0A29FF' | Hex color of the soft ambient glow behind the streaks. |
| speed | number | 1 | Multiplier for how fast the light streaks fall. |
| streakCount | number | 8 | Number of streak layers rendered per cell (1–16). Higher = busier. |
| streakWidth | number | 1 | Thickness of each light streak. |
| streakLength | number | 1 | Length of the glowing tail trailing each streak. |
| glow | number | 1 | Overall brightness multiplier applied before tone mapping. |
| density | number | 1 | Vertical frequency of streaks. Higher values pack more streaks into view. |
| twinkle | number | 1 | Amount of per‑streak brightness flicker. 0 = constant brightness. |
| zoom | number | 2 | Field of view into the tunnel. Higher values zoom further in. |
| backgroundGlow | number | 1 | Intensity of the ambient background glow. |
| opacity | number | 1 | Overall alpha of the rendered canvas. |
| mouseInteraction | boolean | true | Enables a soft light that follows the cursor and flares nearby streaks (no warping). |
| mouseStrength | number | 1 | Intensity of the cursor light. |
| mouseRadius | number | 0.6 | Falloff radius of the cursor light. |
| mouseDampening | number | 0.15 | Easing time constant (seconds) for the cursor light to follow the pointer. 0 = immediate. |
| mixBlendMode | string | undefined | CSS mix-blend-mode applied to the canvas (e.g. 'screen', 'lighten'). |
| paused | boolean | false | If true, stops rendering updates (freezing the current frame). |
| dpr | number | window.devicePixelRatio | Overrides device pixel ratio; lower for performance, higher for sharpness. |
| className | string | — | Additional class names for the root container. |
Full Component Source
import { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle } from 'ogl';
import './Lightfall.css';
const MAX_COLORS = 8;
const hexToRGB = hex => {
const c = hex.replace('#', '').padEnd(6, '0');
const r = parseInt(c.slice(0, 2), 16) / 255;
const g = parseInt(c.slice(2, 4), 16) / 255;
const b = parseInt(c.slice(4, 6), 16) / 255;
return [r, g, b];
};
const prepColors = input => {
const base = (input && input.length ? input : ['#A6C8FF', '#5227FF', '#FF9FFC']).slice(0, MAX_COLORS);
const count = base.length;
const arr = [];
for (let i = 0; i < MAX_COLORS; i++) arr.push(hexToRGB(base[Math.min(i, base.length - 1)]));
const avg = [0, 0, 0];
for (let i = 0; i < count; i++) {
avg[0] += arr[i][0];
avg[1] += arr[i][1];
avg[2] += arr[i][2];
}
avg[0] /= count;
avg[1] /= count;
avg[2] /= count;
return { arr, count, avg };
};
const vertex = `
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragment = `
precision highp float;
uniform vec3 iResolution;
uniform vec2 iMouse;
uniform float iTime;
uniform vec3 uColor0;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform vec3 uColor3;
uniform vec3 uColor4;
uniform vec3 uColor5;
uniform vec3 uColor6;
uniform vec3 uColor7;
uniform int uColorCount;
uniform vec3 uBgColor;
uniform vec3 uMouseColor;
uniform float uSpeed;
uniform int uStreakCount;
uniform float uStreakWidth;
uniform float uStreakLength;
uniform float uGlow;
uniform float uDensity;
uniform float uTwinkle;
uniform float uZoom;
uniform float uBgGlow;
uniform float uOpacity;
uniform float uMouseEnabled;
uniform float uMouseStrength;
uniform float uMouseRadius;
varying vec2 vUv;
vec3 palette(float h) {
int count = uColorCount;
if (count < 1) count = 1;
int idx = int(floor(clamp(h, 0.0, 0.999999) * float(count)));
if (idx <= 0) return uColor0;
if (idx == 1) return uColor1;
if (idx == 2) return uColor2;
if (idx == 3) return uColor3;
if (idx == 4) return uColor4;
if (idx == 5) return uColor5;
if (idx == 6) return uColor6;
return uColor7;
}
vec3 tanhv(vec3 x) {
vec3 e = exp(-2.0 * x);
return (1.0 - e) / (1.0 + e);
}
vec2 sceneC(vec2 frag, vec2 r) {
vec2 P = (frag + frag - r) / r.x;
float z = 0.0;
float d = 1e3;
vec4 O = vec4(0.0);
for (int k = 0; k < 39; k++) {
if (d <= 1e-4) break;
O = z * normalize(vec4(P, uZoom, 0.0)) - vec4(0.0, 4.0, 1.0, 0.0) / 4.5;
d = 1.0 - sqrt(length(O * O));
z += d;
}
return vec2(O.x, atan(O.z, O.y));
}
void mainImage(out vec4 o, vec2 C) {
vec2 r = iResolution.xy;
vec2 uv0 = (C + C - r) / r.x;
float T = 0.1 * iTime * uSpeed + 9.0;
float angRings = max(1.0, floor(6.28318530718 * max(uDensity, 0.05) + 0.5));
vec2 Y = vec2(5e-3, 6.28318530718 / angRings);
vec2 c0 = sceneC(C, r);
vec2 cdx = sceneC(C + vec2(1.0, 0.0), r);
vec2 cdy = sceneC(C + vec2(0.0, 1.0), r);
vec2 dCx = cdx - c0;
vec2 dCy = cdy - c0;
dCx.y -= 6.28318530718 * floor(dCx.y / 6.28318530718 + 0.5);
dCy.y -= 6.28318530718 * floor(dCy.y / 6.28318530718 + 0.5);
vec2 fw = abs(dCx) + abs(dCy);
C = c0;
vec2 P = vec2(2.0, 1.0) * uv0 - (r / r.x) * vec2(0.0, 1.0);
vec4 O = vec4(uBgColor * 90.0 * uBgGlow / (1e3 * dot(P, P) + 6.0), 0.0);
float mGlow = 0.0;
if (uMouseEnabled > 0.5) {
vec2 mN = (iMouse + iMouse - r) / r.x;
float md = length(uv0 - mN);
mGlow = exp(-md * md / max(uMouseRadius * uMouseRadius, 1e-4)) * uMouseStrength;
O.rgb += uMouseColor * mGlow * 0.25;
}
float zr = 5e-4 * uStreakWidth;
vec2 rr = vec2(max(length(fw), 1e-5));
float tail = 19.0 / max(uStreakLength, 0.05);
for (int m = 0; m < 16; m++) {
if (m >= uStreakCount) break;
float jf = float(m) + 1.0;
float ic = fract(sin(dot(vec2(jf, floor(C.x / Y.x + 0.5)), vec2(7.0, 11.0)) * 73.0));
vec2 Pp = C - (T + T * ic) * vec2(0.0, 1.0);
Pp -= floor(Pp / Y + 0.5) * Y;
float h = fract(8663.0 * ic);
vec3 col = palette(h);
float weight = mix(1.5, 1.0 + sin(T + 7.0 * h + 4.0), uTwinkle);
weight *= (1.0 + mGlow * 2.0);
vec2 inner = vec2(length(max(Pp, vec2(-1.0, 0.0))), length(Pp) - zr) - zr;
vec2 sm = vec2(1.0) - smoothstep(-rr, rr, inner);
O.rgb += dot(sm, vec2(exp(tail * Pp.y), 3.0)) * col * weight;
C.x += Y.x / 8.0;
}
vec3 colr = sqrt(tanhv(max(O.rgb * uGlow - vec3(0.04, 0.08, 0.02), 0.0)));
o = vec4(colr, uOpacity);
}
void main() {
vec4 color;
mainImage(color, vUv * iResolution.xy);
gl_FragColor = color;
}
`;
const Lightfall = ({
className,
dpr,
paused = false,
colors = ['#A6C8FF', '#5227FF', '#FF9FFC'],
backgroundColor = '#0A29FF',
speed = 0.5,
streakCount = 2,
streakWidth = 1,
streakLength = 1,
glow = 1,
density = 0.6,
twinkle = 1,
zoom = 3,
backgroundGlow = 0.5,
opacity = 1,
mouseInteraction = true,
mouseStrength = 0.5,
mouseRadius = 1,
mouseDampening = 0.15,
mixBlendMode
}) => {
const containerRef = useRef(null);
const rafRef = useRef(null);
const programRef = useRef(null);
const meshRef = useRef(null);
const geometryRef = useRef(null);
const rendererRef = useRef(null);
const mouseTargetRef = useRef([0, 0]);
const lastTimeRef = useRef(0);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const renderer = new Renderer({
dpr: dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1),
alpha: true,
antialias: true
});
rendererRef.current = renderer;
const gl = renderer.gl;
const canvas = gl.canvas;
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.display = 'block';
container.appendChild(canvas);
const { arr, count, avg } = prepColors(colors);
const uniforms = {
iResolution: { value: [gl.drawingBufferWidth, gl.drawingBufferHeight, 1] },
iMouse: { value: [0, 0] },
iTime: { value: 0 },
uColor0: { value: arr[0] },
uColor1: { value: arr[1] },
uColor2: { value: arr[2] },
uColor3: { value: arr[3] },
uColor4: { value: arr[4] },
uColor5: { value: arr[5] },
uColor6: { value: arr[6] },
uColor7: { value: arr[7] },
uColorCount: { value: count },
uBgColor: { value: hexToRGB(backgroundColor) },
uMouseColor: { value: avg },
uSpeed: { value: speed },
uStreakCount: { value: Math.max(1, Math.min(16, Math.round(streakCount))) },
uStreakWidth: { value: streakWidth },
uStreakLength: { value: streakLength },
uGlow: { value: glow },
uDensity: { value: density },
uTwinkle: { value: twinkle },
uZoom: { value: zoom },
uBgGlow: { value: backgroundGlow },
uOpacity: { value: opacity },
uMouseEnabled: { value: mouseInteraction ? 1 : 0 },
uMouseStrength: { value: mouseStrength },
uMouseRadius: { value: mouseRadius }
};
const program = new Program(gl, { vertex, fragment, uniforms });
programRef.current = program;
const geometry = new Triangle(gl);
geometryRef.current = geometry;
const mesh = new Mesh(gl, { geometry, program });
meshRef.current = mesh;
const resize = () => {
const rect = container.getBoundingClientRect();
renderer.setSize(rect.width, rect.height);
uniforms.iResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight, 1];
};
resize();
const ro = new ResizeObserver(resize);
ro.observe(container);
const onPointerMove = e => {
const rect = canvas.getBoundingClientRect();
const scale = renderer.dpr || 1;
const x = (e.clientX - rect.left) * scale;
const y = (rect.height - (e.clientY - rect.top)) * scale;
mouseTargetRef.current = [x, y];
if (mouseDampening <= 0) {
uniforms.iMouse.value = [x, y];
}
};
if (mouseInteraction) {
canvas.addEventListener('pointermove', onPointerMove);
}
const loop = t => {
rafRef.current = requestAnimationFrame(loop);
uniforms.iTime.value = t * 0.001;
if (mouseDampening > 0) {
if (!lastTimeRef.current) lastTimeRef.current = t;
const dt = (t - lastTimeRef.current) / 1000;
lastTimeRef.current = t;
const tau = Math.max(1e-4, mouseDampening);
let factor = 1 - Math.exp(-dt / tau);
if (factor > 1) factor = 1;
const target = mouseTargetRef.current;
const cur = uniforms.iMouse.value;
cur[0] += (target[0] - cur[0]) * factor;
cur[1] += (target[1] - cur[1]) * factor;
} else {
lastTimeRef.current = t;
}
if (!paused && programRef.current && meshRef.current) {
try {
renderer.render({ scene: meshRef.current });
} catch (e) {
console.error(e);
}
}
};
rafRef.current = requestAnimationFrame(loop);
return () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
if (mouseInteraction) canvas.removeEventListener('pointermove', onPointerMove);
ro.disconnect();
if (canvas.parentElement === container) {
container.removeChild(canvas);
}
const callIfFn = (obj, key) => {
if (obj && typeof obj[key] === 'function') {
obj[key].call(obj);
}
};
callIfFn(programRef.current, 'remove');
callIfFn(geometryRef.current, 'remove');
callIfFn(meshRef.current, 'remove');
callIfFn(rendererRef.current, 'destroy');
programRef.current = null;
geometryRef.current = null;
meshRef.current = null;
rendererRef.current = null;
};
}, [
dpr,
paused,
colors,
backgroundColor,
speed,
streakCount,
streakWidth,
streakLength,
glow,
density,
twinkle,
zoom,
backgroundGlow,
opacity,
mouseInteraction,
mouseStrength,
mouseRadius,
mouseDampening
]);
return (
<div
ref={containerRef}
className={`lightfall-container ${className ?? ''}`}
style={{
...(mixBlendMode && { mixBlendMode })
}}
/>
);
};
export default Lightfall;
Component CSS
.lightfall-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: liquid ether code: ## Integrate the <LiquidEther /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: LiquidEther
Variant: JavaScript + CSS
Dependencies: three
Usage Example
import LiquidEther from './LiquidEther';
<div style={{ width: '100%', height: 600, position: 'relative' }}>
<LiquidEther
colors={[ '#5227FF', '#FF9FFC', '#B497CF' ]}
mouseForce={20}
cursorSize={100}
isViscous={false}
viscous={30}
iterationsViscous={32}
iterationsPoisson={32}
resolution={0.5}
isBounce={false}
autoDemo={true}
autoSpeed={0.5}
autoIntensity={2.2}
takeoverDuration={0.25}
autoResumeDelay={3000}
autoRampDuration={0.6}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| colors | string[] | ["#5227FF", "#FF9FFC", "#B497CF"] | Array of hex color stops used to build the velocity-to-color palette. |
| mouseForce | number | 20 | Strength multiplier applied to mouse / touch movement when injecting velocity. |
| cursorSize | number | 100 | Radius (in pixels at base resolution) of the force brush. |
| resolution | number | 0.5 | Simulation texture scale relative to canvas size (lower = better performance, more blur). |
| dt | number | 0.014 | Fixed simulation timestep used inside the advection / diffusion passes. |
| BFECC | boolean | true | Enable BFECC advection (error-compensated) for crisper flow; disable for slight performance gain. |
| isViscous | boolean | false | Toggle iterative viscosity solve (smoother, thicker motion when enabled). |
| viscous | number | 30 | Viscosity coefficient used when isViscous is true. |
| iterationsViscous | number | 32 | Number of Gauss-Seidel iterations for viscosity (higher = smoother, slower). |
| iterationsPoisson | number | 32 | Number of pressure Poisson iterations to enforce incompressibility. |
| isBounce | boolean | false | If true, shows bounce boundaries (velocity clamped at edges). |
| autoDemo | boolean | true | Enable idle auto-driving of the pointer when no user interaction. |
| autoSpeed | number | 0.5 | Speed (normalized units/sec) for auto pointer motion. |
| autoIntensity | number | 2.2 | Multiplier applied to velocity delta while in auto mode. |
| takeoverDuration | number | 0.25 | Seconds to interpolate from auto pointer to real cursor when user moves mouse. |
| autoResumeDelay | number | 1000 | Milliseconds of inactivity before auto mode resumes. |
| autoRampDuration | number | 0.6 | Seconds to ramp auto movement speed from 0 to full after activation. |
| className | string | '' | Optional class for the root container. |
| style | React.CSSProperties | {} | Inline styles applied to the root container. |
Full Component Source
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import './LiquidEther.css';
export default function LiquidEther({
mouseForce = 20,
cursorSize = 100,
isViscous = false,
viscous = 30,
iterationsViscous = 32,
iterationsPoisson = 32,
dt = 0.014,
BFECC = true,
resolution = 0.5,
isBounce = false,
colors = ['#5227FF', '#FF9FFC', '#B497CF'],
style = {},
className = '',
autoDemo = true,
autoSpeed = 0.5,
autoIntensity = 2.2,
takeoverDuration = 0.25,
autoResumeDelay = 1000,
autoRampDuration = 0.6
}) {
const mountRef = useRef(null);
const webglRef = useRef(null);
const resizeObserverRef = useRef(null);
const rafRef = useRef(null);
const intersectionObserverRef = useRef(null);
const isVisibleRef = useRef(true);
const resizeRafRef = useRef(null);
useEffect(() => {
if (!mountRef.current) return;
function makePaletteTexture(stops) {
let arr;
if (Array.isArray(stops) && stops.length > 0) {
if (stops.length === 1) {
arr = [stops[0], stops[0]];
} else {
arr = stops;
}
} else {
arr = ['#ffffff', '#ffffff'];
}
const w = arr.length;
const data = new Uint8Array(w * 4);
for (let i = 0; i < w; i++) {
const c = new THREE.Color(arr[i]);
data[i * 4 + 0] = Math.round(c.r * 255);
data[i * 4 + 1] = Math.round(c.g * 255);
data[i * 4 + 2] = Math.round(c.b * 255);
data[i * 4 + 3] = 255;
}
const tex = new THREE.DataTexture(data, w, 1, THREE.RGBAFormat);
tex.magFilter = THREE.LinearFilter;
tex.minFilter = THREE.LinearFilter;
tex.wrapS = THREE.ClampToEdgeWrapping;
tex.wrapT = THREE.ClampToEdgeWrapping;
tex.generateMipmaps = false;
tex.needsUpdate = true;
return tex;
}
const paletteTex = makePaletteTexture(colors);
const bgVec4 = new THREE.Vector4(0, 0, 0, 0); // always transparent
class CommonClass {
constructor() {
this.width = 0;
this.height = 0;
this.aspect = 1;
this.pixelRatio = 1;
this.isMobile = false;
this.breakpoint = 768;
this.fboWidth = null;
this.fboHeight = null;
this.time = 0;
this.delta = 0;
this.container = null;
this.renderer = null;
this.clock = null;
}
init(container) {
this.container = container;
this.pixelRatio = Math.min(window.devicePixelRatio || 1, 2);
this.resize();
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.renderer.autoClear = false;
this.renderer.setClearColor(new THREE.Color(0x000000), 0);
this.renderer.setPixelRatio(this.pixelRatio);
this.renderer.setSize(this.width, this.height);
this.renderer.domElement.style.width = '100%';
this.renderer.domElement.style.height = '100%';
this.renderer.domElement.style.display = 'block';
this.clock = new THREE.Clock();
this.clock.start();
}
resize() {
if (!this.container) return;
const rect = this.container.getBoundingClientRect();
this.width = Math.max(1, Math.floor(rect.width));
this.height = Math.max(1, Math.floor(rect.height));
this.aspect = this.width / this.height;
if (this.renderer) this.renderer.setSize(this.width, this.height, false);
}
update() {
this.delta = this.clock.getDelta();
this.time += this.delta;
}
}
const Common = new CommonClass();
class MouseClass {
constructor() {
this.mouseMoved = false;
this.coords = new THREE.Vector2();
this.coords_old = new THREE.Vector2();
this.diff = new THREE.Vector2();
this.timer = null;
this.container = null;
this.docTarget = null;
this.listenerTarget = null;
this.isHoverInside = false;
this.hasUserControl = false;
this.isAutoActive = false;
this.autoIntensity = 2.0;
this.takeoverActive = false;
this.takeoverStartTime = 0;
this.takeoverDuration = 0.25;
this.takeoverFrom = new THREE.Vector2();
this.takeoverTo = new THREE.Vector2();
this.onInteract = null;
this._onMouseMove = this.onDocumentMouseMove.bind(this);
this._onTouchStart = this.onDocumentTouchStart.bind(this);
this._onTouchMove = this.onDocumentTouchMove.bind(this);
this._onTouchEnd = this.onTouchEnd.bind(this);
this._onDocumentLeave = this.onDocumentLeave.bind(this);
}
init(container) {
this.container = container;
this.docTarget = container.ownerDocument || null;
const defaultView =
(this.docTarget && this.docTarget.defaultView) || (typeof window !== 'undefined' ? window : null);
if (!defaultView) return;
this.listenerTarget = defaultView;
this.listenerTarget.addEventListener('mousemove', this._onMouseMove);
this.listenerTarget.addEventListener('touchstart', this._onTouchStart, { passive: true });
this.listenerTarget.addEventListener('touchmove', this._onTouchMove, { passive: true });
this.listenerTarget.addEventListener('touchend', this._onTouchEnd);
if (this.docTarget) {
this.docTarget.addEventListener('mouseleave', this._onDocumentLeave);
}
}
dispose() {
if (this.listenerTarget) {
this.listenerTarget.removeEventListener('mousemove', this._onMouseMove);
this.listenerTarget.removeEventListener('touchstart', this._onTouchStart);
this.listenerTarget.removeEventListener('touchmove', this._onTouchMove);
this.listenerTarget.removeEventListener('touchend', this._onTouchEnd);
}
if (this.docTarget) {
this.docTarget.removeEventListener('mouseleave', this._onDocumentLeave);
}
this.listenerTarget = null;
this.docTarget = null;
this.container = null;
}
isPointInside(clientX, clientY) {
if (!this.container) return false;
const rect = this.container.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return false;
return clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
}
updateHoverState(clientX, clientY) {
this.isHoverInside = this.isPointInside(clientX, clientY);
return this.isHoverInside;
}
setCoords(x, y) {
if (!this.container) return;
if (this.timer) window.clearTimeout(this.timer);
const rect = this.container.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const nx = (x - rect.left) / rect.width;
const ny = (y - rect.top) / rect.height;
this.coords.set(nx * 2 - 1, -(ny * 2 - 1));
this.mouseMoved = true;
this.timer = window.setTimeout(() => {
this.mouseMoved = false;
}, 100);
}
setNormalized(nx, ny) {
this.coords.set(nx, ny);
this.mouseMoved = true;
}
onDocumentMouseMove(event) {
if (!this.updateHoverState(event.clientX, event.clientY)) return;
if (this.onInteract) this.onInteract();
if (this.isAutoActive && !this.hasUserControl && !this.takeoverActive) {
if (!this.container) return;
const rect = this.container.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const nx = (event.clientX - rect.left) / rect.width;
const ny = (event.clientY - rect.top) / rect.height;
this.takeoverFrom.copy(this.coords);
this.takeoverTo.set(nx * 2 - 1, -(ny * 2 - 1));
this.takeoverStartTime = performance.now();
this.takeoverActive = true;
this.hasUserControl = true;
this.isAutoActive = false;
return;
}
this.setCoords(event.clientX, event.clientY);
this.hasUserControl = true;
}
onDocumentTouchStart(event) {
if (event.touches.length !== 1) return;
const t = event.touches[0];
if (!this.updateHoverState(t.clientX, t.clientY)) return;
if (this.onInteract) this.onInteract();
this.setCoords(t.clientX, t.clientY);
this.hasUserControl = true;
}
onDocumentTouchMove(event) {
if (event.touches.length !== 1) return;
const t = event.touches[0];
if (!this.updateHoverState(t.clientX, t.clientY)) return;
if (this.onInteract) this.onInteract();
this.setCoords(t.clientX, t.clientY);
}
onTouchEnd() {
this.isHoverInside = false;
}
onDocumentLeave() {
this.isHoverInside = false;
}
update() {
if (this.takeoverActive) {
const t = (performance.now() - this.takeoverStartTime) / (this.takeoverDuration * 1000);
if (t >= 1) {
this.takeoverActive = false;
this.coords.copy(this.takeoverTo);
this.coords_old.copy(this.coords);
this.diff.set(0, 0);
} else {
const k = t * t * (3 - 2 * t);
this.coords.copy(this.takeoverFrom).lerp(this.takeoverTo, k);
}
}
this.diff.subVectors(this.coords, this.coords_old);
this.coords_old.copy(this.coords);
if (this.coords_old.x === 0 && this.coords_old.y === 0) this.diff.set(0, 0);
if (this.isAutoActive && !this.takeoverActive) this.diff.multiplyScalar(this.autoIntensity);
}
}
const Mouse = new MouseClass();
class AutoDriver {
constructor(mouse, manager, opts) {
this.mouse = mouse;
this.manager = manager;
this.enabled = opts.enabled;
this.speed = opts.speed; // normalized units/sec
this.resumeDelay = opts.resumeDelay || 3000; // ms
this.rampDurationMs = (opts.rampDuration || 0) * 1000;
this.active = false;
this.current = new THREE.Vector2(0, 0);
this.target = new THREE.Vector2();
this.lastTime = performance.now();
this.activationTime = 0;
this.margin = 0.2;
this._tmpDir = new THREE.Vector2(); // reuse temp vector to avoid per-frame alloc
this.pickNewTarget();
}
pickNewTarget() {
const r = Math.random;
this.target.set((r() * 2 - 1) * (1 - this.margin), (r() * 2 - 1) * (1 - this.margin));
}
forceStop() {
this.active = false;
this.mouse.isAutoActive = false;
}
update() {
if (!this.enabled) return;
const now = performance.now();
const idle = now - this.manager.lastUserInteraction;
if (idle < this.resumeDelay) {
if (this.active) this.forceStop();
return;
}
if (this.mouse.isHoverInside) {
if (this.active) this.forceStop();
return;
}
if (!this.active) {
this.active = true;
this.current.copy(this.mouse.coords);
this.lastTime = now;
this.activationTime = now;
}
if (!this.active) return;
this.mouse.isAutoActive = true;
let dtSec = (now - this.lastTime) / 1000;
this.lastTime = now;
if (dtSec > 0.2) dtSec = 0.016;
const dir = this._tmpDir.subVectors(this.target, this.current);
const dist = dir.length();
if (dist < 0.01) {
this.pickNewTarget();
return;
}
dir.normalize();
let ramp = 1;
if (this.rampDurationMs > 0) {
const t = Math.min(1, (now - this.activationTime) / this.rampDurationMs);
ramp = t * t * (3 - 2 * t);
}
const step = this.speed * dtSec * ramp;
const move = Math.min(step, dist);
this.current.addScaledVector(dir, move);
this.mouse.setNormalized(this.current.x, this.current.y);
}
}
const face_vert = `
attribute vec3 position;
uniform vec2 px;
uniform vec2 boundarySpace;
varying vec2 uv;
precision highp float;
void main(){
vec3 pos = position;
vec2 scale = 1.0 - boundarySpace * 2.0;
pos.xy = pos.xy * scale;
uv = vec2(0.5)+(pos.xy)*0.5;
gl_Position = vec4(pos, 1.0);
}
`;
const line_vert = `
attribute vec3 position;
uniform vec2 px;
precision highp float;
varying vec2 uv;
void main(){
vec3 pos = position;
uv = 0.5 + pos.xy * 0.5;
vec2 n = sign(pos.xy);
pos.xy = abs(pos.xy) - px * 1.0;
pos.xy *= n;
gl_Position = vec4(pos, 1.0);
}
`;
const mouse_vert = `
precision highp float;
attribute vec3 position;
attribute vec2 uv;
uniform vec2 center;
uniform vec2 scale;
uniform vec2 px;
varying vec2 vUv;
void main(){
vec2 pos = position.xy * scale * 2.0 * px + center;
vUv = uv;
gl_Position = vec4(pos, 0.0, 1.0);
}
`;
const advection_frag = `
precision highp float;
uniform sampler2D velocity;
uniform float dt;
uniform bool isBFECC;
uniform vec2 fboSize;
uniform vec2 px;
varying vec2 uv;
void main(){
vec2 ratio = max(fboSize.x, fboSize.y) / fboSize;
if(isBFECC == false){
vec2 vel = texture2D(velocity, uv).xy;
vec2 uv2 = uv - vel * dt * ratio;
vec2 newVel = texture2D(velocity, uv2).xy;
gl_FragColor = vec4(newVel, 0.0, 0.0);
} else {
vec2 spot_new = uv;
vec2 vel_old = texture2D(velocity, uv).xy;
vec2 spot_old = spot_new - vel_old * dt * ratio;
vec2 vel_new1 = texture2D(velocity, spot_old).xy;
vec2 spot_new2 = spot_old + vel_new1 * dt * ratio;
vec2 error = spot_new2 - spot_new;
vec2 spot_new3 = spot_new - error / 2.0;
vec2 vel_2 = texture2D(velocity, spot_new3).xy;
vec2 spot_old2 = spot_new3 - vel_2 * dt * ratio;
vec2 newVel2 = texture2D(velocity, spot_old2).xy;
gl_FragColor = vec4(newVel2, 0.0, 0.0);
}
}
`;
const color_frag = `
precision highp float;
uniform sampler2D velocity;
uniform sampler2D palette;
uniform vec4 bgColor;
varying vec2 uv;
void main(){
vec2 vel = texture2D(velocity, uv).xy;
float lenv = clamp(length(vel), 0.0, 1.0);
vec3 c = texture2D(palette, vec2(lenv, 0.5)).rgb;
vec3 outRGB = mix(bgColor.rgb, c, lenv);
float outA = mix(bgColor.a, 1.0, lenv);
gl_FragColor = vec4(outRGB, outA);
}
`;
const divergence_frag = `
precision highp float;
uniform sampler2D velocity;
uniform float dt;
uniform vec2 px;
varying vec2 uv;
void main(){
float x0 = texture2D(velocity, uv-vec2(px.x, 0.0)).x;
float x1 = texture2D(velocity, uv+vec2(px.x, 0.0)).x;
float y0 = texture2D(velocity, uv-vec2(0.0, px.y)).y;
float y1 = texture2D(velocity, uv+vec2(0.0, px.y)).y;
float divergence = (x1 - x0 + y1 - y0) / 2.0;
gl_FragColor = vec4(divergence / dt);
}
`;
const externalForce_frag = `
precision highp float;
uniform vec2 force;
uniform vec2 center;
uniform vec2 scale;
uniform vec2 px;
varying vec2 vUv;
void main(){
vec2 circle = (vUv - 0.5) * 2.0;
float d = 1.0 - min(length(circle), 1.0);
d *= d;
gl_FragColor = vec4(force * d, 0.0, 1.0);
}
`;
const poisson_frag = `
precision highp float;
uniform sampler2D pressure;
uniform sampler2D divergence;
uniform vec2 px;
varying vec2 uv;
void main(){
float p0 = texture2D(pressure, uv + vec2(px.x * 2.0, 0.0)).r;
float p1 = texture2D(pressure, uv - vec2(px.x * 2.0, 0.0)).r;
float p2 = texture2D(pressure, uv + vec2(0.0, px.y * 2.0)).r;
float p3 = texture2D(pressure, uv - vec2(0.0, px.y * 2.0)).r;
float div = texture2D(divergence, uv).r;
float newP = (p0 + p1 + p2 + p3) / 4.0 - div;
gl_FragColor = vec4(newP);
}
`;
const pressure_frag = `
precision highp float;
uniform sampler2D pressure;
uniform sampler2D velocity;
uniform vec2 px;
uniform float dt;
varying vec2 uv;
void main(){
float step = 1.0;
float p0 = texture2D(pressure, uv + vec2(px.x * step, 0.0)).r;
float p1 = texture2D(pressure, uv - vec2(px.x * step, 0.0)).r;
float p2 = texture2D(pressure, uv + vec2(0.0, px.y * step)).r;
float p3 = texture2D(pressure, uv - vec2(0.0, px.y * step)).r;
vec2 v = texture2D(velocity, uv).xy;
vec2 gradP = vec2(p0 - p1, p2 - p3) * 0.5;
v = v - gradP * dt;
gl_FragColor = vec4(v, 0.0, 1.0);
}
`;
const viscous_frag = `
precision highp float;
uniform sampler2D velocity;
uniform sampler2D velocity_new;
uniform float v;
uniform vec2 px;
uniform float dt;
varying vec2 uv;
void main(){
vec2 old = texture2D(velocity, uv).xy;
vec2 new0 = texture2D(velocity_new, uv + vec2(px.x * 2.0, 0.0)).xy;
vec2 new1 = texture2D(velocity_new, uv - vec2(px.x * 2.0, 0.0)).xy;
vec2 new2 = texture2D(velocity_new, uv + vec2(0.0, px.y * 2.0)).xy;
vec2 new3 = texture2D(velocity_new, uv - vec2(0.0, px.y * 2.0)).xy;
vec2 newv = 4.0 * old + v * dt * (new0 + new1 + new2 + new3);
newv /= 4.0 * (1.0 + v * dt);
gl_FragColor = vec4(newv, 0.0, 0.0);
}
`;
class ShaderPass {
constructor(props) {
this.props = props || {};
this.uniforms = this.props.material?.uniforms;
this.scene = null;
this.camera = null;
this.material = null;
this.geometry = null;
this.plane = null;
}
init() {
this.scene = new THREE.Scene();
this.camera = new THREE.Camera();
if (this.uniforms) {
this.material = new THREE.RawShaderMaterial(this.props.material);
this.geometry = new THREE.PlaneGeometry(2.0, 2.0);
this.plane = new THREE.Mesh(this.geometry, this.material);
this.scene.add(this.plane);
}
}
update() {
Common.renderer.setRenderTarget(this.props.output || null);
Common.renderer.render(this.scene, this.camera);
Common.renderer.setRenderTarget(null);
}
}
class Advection extends ShaderPass {
constructor(simProps) {
super({
material: {
vertexShader: face_vert,
fragmentShader: advection_frag,
uniforms: {
boundarySpace: { value: simProps.cellScale },
px: { value: simProps.cellScale },
fboSize: { value: simProps.fboSize },
velocity: { value: simProps.src.texture },
dt: { value: simProps.dt },
isBFECC: { value: true }
}
},
output: simProps.dst
});
this.uniforms = this.props.material.uniforms;
this.init();
}
init() {
super.init();
this.createBoundary();
}
createBoundary() {
const boundaryG = new THREE.BufferGeometry();
const vertices_boundary = new Float32Array([
-1, -1, 0, -1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, -1, 0, -1, -1, 0
]);
boundaryG.setAttribute('position', new THREE.BufferAttribute(vertices_boundary, 3));
const boundaryM = new THREE.RawShaderMaterial({
vertexShader: line_vert,
fragmentShader: advection_frag,
uniforms: this.uniforms
});
this.line = new THREE.LineSegments(boundaryG, boundaryM);
this.scene.add(this.line);
}
update({ dt, isBounce, BFECC }) {
this.uniforms.dt.value = dt;
this.line.visible = isBounce;
this.uniforms.isBFECC.value = BFECC;
super.update();
}
}
class ExternalForce extends ShaderPass {
constructor(simProps) {
super({ output: simProps.dst });
this.init(simProps);
}
init(simProps) {
super.init();
const mouseG = new THREE.PlaneGeometry(1, 1);
const mouseM = new THREE.RawShaderMaterial({
vertexShader: mouse_vert,
fragmentShader: externalForce_frag,
blending: THREE.AdditiveBlending,
depthWrite: false,
uniforms: {
px: { value: simProps.cellScale },
force: { value: new THREE.Vector2(0.0, 0.0) },
center: { value: new THREE.Vector2(0.0, 0.0) },
scale: { value: new THREE.Vector2(simProps.cursor_size, simProps.cursor_size) }
}
});
this.mouse = new THREE.Mesh(mouseG, mouseM);
this.scene.add(this.mouse);
}
update(props) {
const forceX = (Mouse.diff.x / 2) * props.mouse_force;
const forceY = (Mouse.diff.y / 2) * props.mouse_force;
const cursorSizeX = props.cursor_size * props.cellScale.x;
const cursorSizeY = props.cursor_size * props.cellScale.y;
const centerX = Math.min(
Math.max(Mouse.coords.x, -1 + cursorSizeX + props.cellScale.x * 2),
1 - cursorSizeX - props.cellScale.x * 2
);
const centerY = Math.min(
Math.max(Mouse.coords.y, -1 + cursorSizeY + props.cellScale.y * 2),
1 - cursorSizeY - props.cellScale.y * 2
);
const uniforms = this.mouse.material.uniforms;
uniforms.force.value.set(forceX, forceY);
uniforms.center.value.set(centerX, centerY);
uniforms.scale.value.set(props.cursor_size, props.cursor_size);
super.update();
}
}
class Viscous extends ShaderPass {
constructor(simProps) {
super({
material: {
vertexShader: face_vert,
fragmentShader: viscous_frag,
uniforms: {
boundarySpace: { value: simProps.boundarySpace },
velocity: { value: simProps.src.texture },
velocity_new: { value: simProps.dst_.texture },
v: { value: simProps.viscous },
px: { value: simProps.cellScale },
dt: { value: simProps.dt }
}
},
output: simProps.dst,
output0: simProps.dst_,
output1: simProps.dst
});
this.init();
}
update({ viscous, iterations, dt }) {
let fbo_in, fbo_out;
this.uniforms.v.value = viscous;
for (let i = 0; i < iterations; i++) {
if (i % 2 === 0) {
fbo_in = this.props.output0;
fbo_out = this.props.output1;
} else {
fbo_in = this.props.output1;
fbo_out = this.props.output0;
}
this.uniforms.velocity_new.value = fbo_in.texture;
this.props.output = fbo_out;
this.uniforms.dt.value = dt;
super.update();
}
return fbo_out;
}
}
class Divergence extends ShaderPass {
constructor(simProps) {
super({
material: {
vertexShader: face_vert,
fragmentShader: divergence_frag,
uniforms: {
boundarySpace: { value: simProps.boundarySpace },
velocity: { value: simProps.src.texture },
px: { value: simProps.cellScale },
dt: { value: simProps.dt }
}
},
output: simProps.dst
});
this.init();
}
update({ vel }) {
this.uniforms.velocity.value = vel.texture;
super.update();
}
}
class Poisson extends ShaderPass {
constructor(simProps) {
super({
material: {
vertexShader: face_vert,
fragmentShader: poisson_frag,
uniforms: {
boundarySpace: { value: simProps.boundarySpace },
pressure: { value: simProps.dst_.texture },
divergence: { value: simProps.src.texture },
px: { value: simProps.cellScale }
}
},
output: simProps.dst,
output0: simProps.dst_,
output1: simProps.dst
});
this.init();
}
update({ iterations }) {
let p_in, p_out;
for (let i = 0; i < iterations; i++) {
if (i % 2 === 0) {
p_in = this.props.output0;
p_out = this.props.output1;
} else {
p_in = this.props.output1;
p_out = this.props.output0;
}
this.uniforms.pressure.value = p_in.texture;
this.props.output = p_out;
super.update();
}
return p_out;
}
}
class Pressure extends ShaderPass {
constructor(simProps) {
super({
material: {
vertexShader: face_vert,
fragmentShader: pressure_frag,
uniforms: {
boundarySpace: { value: simProps.boundarySpace },
pressure: { value: simProps.src_p.texture },
velocity: { value: simProps.src_v.texture },
px: { value: simProps.cellScale },
dt: { value: simProps.dt }
}
},
output: simProps.dst
});
this.init();
}
update({ vel, pressure }) {
this.uniforms.velocity.value = vel.texture;
this.uniforms.pressure.value = pressure.texture;
super.update();
}
}
class Simulation {
constructor(options) {
this.options = {
iterations_poisson: 32,
iterations_viscous: 32,
mouse_force: 20,
resolution: 0.5,
cursor_size: 100,
viscous: 30,
isBounce: false,
dt: 0.014,
isViscous: false,
BFECC: true,
...options
};
this.fbos = {
vel_0: null,
vel_1: null,
vel_viscous0: null,
vel_viscous1: null,
div: null,
pressure_0: null,
pressure_1: null
};
this.fboSize = new THREE.Vector2();
this.cellScale = new THREE.Vector2();
this.boundarySpace = new THREE.Vector2();
this.init();
}
init() {
this.calcSize();
this.createAllFBO();
this.createShaderPass();
}
getFloatType() {
const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent);
return isIOS ? THREE.HalfFloatType : THREE.FloatType;
}
createAllFBO() {
const type = this.getFloatType();
const opts = {
type,
depthBuffer: false,
stencilBuffer: false,
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
wrapS: THREE.ClampToEdgeWrapping,
wrapT: THREE.ClampToEdgeWrapping
};
for (let key in this.fbos) {
this.fbos[key] = new THREE.WebGLRenderTarget(this.fboSize.x, this.fboSize.y, opts);
}
}
createShaderPass() {
this.advection = new Advection({
cellScale: this.cellScale,
fboSize: this.fboSize,
dt: this.options.dt,
src: this.fbos.vel_0,
dst: this.fbos.vel_1
});
this.externalForce = new ExternalForce({
cellScale: this.cellScale,
cursor_size: this.options.cursor_size,
dst: this.fbos.vel_1
});
this.viscous = new Viscous({
cellScale: this.cellScale,
boundarySpace: this.boundarySpace,
viscous: this.options.viscous,
src: this.fbos.vel_1,
dst: this.fbos.vel_viscous1,
dst_: this.fbos.vel_viscous0,
dt: this.options.dt
});
this.divergence = new Divergence({
cellScale: this.cellScale,
boundarySpace: this.boundarySpace,
src: this.fbos.vel_viscous0,
dst: this.fbos.div,
dt: this.options.dt
});
this.poisson = new Poisson({
cellScale: this.cellScale,
boundarySpace: this.boundarySpace,
src: this.fbos.div,
dst: this.fbos.pressure_1,
dst_: this.fbos.pressure_0
});
this.pressure = new Pressure({
cellScale: this.cellScale,
boundarySpace: this.boundarySpace,
src_p: this.fbos.pressure_0,
src_v: this.fbos.vel_viscous0,
dst: this.fbos.vel_0,
dt: this.options.dt
});
}
calcSize() {
const width = Math.max(1, Math.round(this.options.resolution * Common.width));
const height = Math.max(1, Math.round(this.options.resolution * Common.height));
const px_x = 1.0 / width;
const px_y = 1.0 / height;
this.cellScale.set(px_x, px_y);
this.fboSize.set(width, height);
}
resize() {
this.calcSize();
for (let key in this.fbos) {
this.fbos[key].setSize(this.fboSize.x, this.fboSize.y);
}
}
update() {
if (this.options.isBounce) {
this.boundarySpace.set(0, 0);
} else {
this.boundarySpace.copy(this.cellScale);
}
this.advection.update({
dt: this.options.dt,
isBounce: this.options.isBounce,
BFECC: this.options.BFECC
});
this.externalForce.update({
cursor_size: this.options.cursor_size,
mouse_force: this.options.mouse_force,
cellScale: this.cellScale
});
let vel = this.fbos.vel_1;
if (this.options.isViscous) {
vel = this.viscous.update({
viscous: this.options.viscous,
iterations: this.options.iterations_viscous,
dt: this.options.dt
});
}
this.divergence.update({ vel });
const pressure = this.poisson.update({
iterations: this.options.iterations_poisson
});
this.pressure.update({ vel, pressure });
}
}
class Output {
constructor() {
this.init();
}
init() {
this.simulation = new Simulation();
this.scene = new THREE.Scene();
this.camera = new THREE.Camera();
this.output = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2),
new THREE.RawShaderMaterial({
vertexShader: face_vert,
fragmentShader: color_frag,
transparent: true,
depthWrite: false,
uniforms: {
velocity: { value: this.simulation.fbos.vel_0.texture },
boundarySpace: { value: new THREE.Vector2() },
palette: { value: paletteTex },
bgColor: { value: bgVec4 }
}
})
);
this.scene.add(this.output);
}
addScene(mesh) {
this.scene.add(mesh);
}
resize() {
this.simulation.resize();
}
render() {
Common.renderer.setRenderTarget(null);
Common.renderer.render(this.scene, this.camera);
}
update() {
this.simulation.update();
this.render();
}
}
class WebGLManager {
constructor(props) {
this.props = props;
Common.init(props.$wrapper);
Mouse.init(props.$wrapper);
Mouse.autoIntensity = props.autoIntensity;
Mouse.takeoverDuration = props.takeoverDuration;
this.lastUserInteraction = performance.now();
Mouse.onInteract = () => {
this.lastUserInteraction = performance.now();
if (this.autoDriver) this.autoDriver.forceStop();
};
this.autoDriver = new AutoDriver(Mouse, this, {
enabled: props.autoDemo,
speed: props.autoSpeed,
resumeDelay: props.autoResumeDelay,
rampDuration: props.autoRampDuration
});
this.init();
this._loop = this.loop.bind(this);
this._resize = this.resize.bind(this);
window.addEventListener('resize', this._resize);
this._onVisibility = () => {
const hidden = document.hidden;
if (hidden) {
this.pause();
} else if (isVisibleRef.current) {
this.start();
}
};
document.addEventListener('visibilitychange', this._onVisibility);
this.running = false;
}
init() {
this.props.$wrapper.prepend(Common.renderer.domElement);
this.output = new Output();
}
resize() {
Common.resize();
this.output.resize();
}
render() {
if (this.autoDriver) this.autoDriver.update();
Mouse.update();
Common.update();
this.output.update();
}
loop() {
if (!this.running) return; // safety
this.render();
rafRef.current = requestAnimationFrame(this._loop);
}
start() {
if (this.running) return;
this.running = true;
this._loop();
}
pause() {
this.running = false;
if (rafRef.current) {
cancelAnimationFrame(rafRef.current);
rafRef.current = null;
}
}
dispose() {
try {
window.removeEventListener('resize', this._resize);
document.removeEventListener('visibilitychange', this._onVisibility);
Mouse.dispose();
if (Common.renderer) {
const canvas = Common.renderer.domElement;
if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);
Common.renderer.dispose();
Common.renderer.forceContextLoss();
}
} catch (e) {
void 0;
}
}
}
const container = mountRef.current;
container.style.position = container.style.position || 'relative';
container.style.overflow = container.style.overflow || 'hidden';
const webgl = new WebGLManager({
$wrapper: container,
autoDemo,
autoSpeed,
autoIntensity,
takeoverDuration,
autoResumeDelay,
autoRampDuration
});
webglRef.current = webgl;
const applyOptionsFromProps = () => {
if (!webglRef.current) return;
const sim = webglRef.current.output?.simulation;
if (!sim) return;
const prevRes = sim.options.resolution;
Object.assign(sim.options, {
mouse_force: mouseForce,
cursor_size: cursorSize,
isViscous,
viscous,
iterations_viscous: iterationsViscous,
iterations_poisson: iterationsPoisson,
dt,
BFECC,
resolution,
isBounce
});
if (resolution !== prevRes) {
sim.resize();
}
};
applyOptionsFromProps();
webgl.start();
// IntersectionObserver to pause rendering when not visible
const io = new IntersectionObserver(
entries => {
const entry = entries[0];
const isVisible = entry.isIntersecting && entry.intersectionRatio > 0;
isVisibleRef.current = isVisible;
if (!webglRef.current) return;
if (isVisible && !document.hidden) {
webglRef.current.start();
} else {
webglRef.current.pause();
}
},
{ threshold: [0, 0.01, 0.1] }
);
io.observe(container);
intersectionObserverRef.current = io;
const ro = new ResizeObserver(() => {
if (!webglRef.current) return;
if (resizeRafRef.current) cancelAnimationFrame(resizeRafRef.current);
resizeRafRef.current = requestAnimationFrame(() => {
if (!webglRef.current) return;
webglRef.current.resize();
});
});
ro.observe(container);
resizeObserverRef.current = ro;
return () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
if (resizeObserverRef.current) {
try {
resizeObserverRef.current.disconnect();
} catch (e) {
void 0;
}
}
if (intersectionObserverRef.current) {
try {
intersectionObserverRef.current.disconnect();
} catch (e) {
void 0;
}
}
if (webglRef.current) {
webglRef.current.dispose();
}
webglRef.current = null;
};
}, [
BFECC,
cursorSize,
dt,
isBounce,
isViscous,
iterationsPoisson,
iterationsViscous,
mouseForce,
resolution,
viscous,
colors,
autoDemo,
autoSpeed,
autoIntensity,
takeoverDuration,
autoResumeDelay,
autoRampDuration
]);
useEffect(() => {
const webgl = webglRef.current;
if (!webgl) return;
const sim = webgl.output?.simulation;
if (!sim) return;
const prevRes = sim.options.resolution;
Object.assign(sim.options, {
mouse_force: mouseForce,
cursor_size: cursorSize,
isViscous,
viscous,
iterations_viscous: iterationsViscous,
iterations_poisson: iterationsPoisson,
dt,
BFECC,
resolution,
isBounce
});
if (webgl.autoDriver) {
webgl.autoDriver.enabled = autoDemo;
webgl.autoDriver.speed = autoSpeed;
webgl.autoDriver.resumeDelay = autoResumeDelay;
webgl.autoDriver.rampDurationMs = autoRampDuration * 1000;
if (webgl.autoDriver.mouse) {
webgl.autoDriver.mouse.autoIntensity = autoIntensity;
webgl.autoDriver.mouse.takeoverDuration = takeoverDuration;
}
}
if (resolution !== prevRes) {
sim.resize();
}
}, [
mouseForce,
cursorSize,
isViscous,
viscous,
iterationsViscous,
iterationsPoisson,
dt,
BFECC,
resolution,
isBounce,
autoDemo,
autoSpeed,
autoIntensity,
takeoverDuration,
autoResumeDelay,
autoRampDuration
]);
return <div ref={mountRef} className={`liquid-ether-container ${className || ''}`} style={style} />;
}
Component CSS
.liquid-ether-container {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
touch-action: none;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: prism code: ## Integrate the <Prism /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Prism
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Prism from './Prism';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Prism
animationType="rotate"
timeScale={0.5}
height={3.5}
baseWidth={5.5}
scale={3.6}
hueShift={0}
colorFrequency={1}
noise={0.5}
glow={1}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| height | number | 3.5 | Apex height of the prism (world units). |
| baseWidth | number | 5.5 | Total base width across X/Z (world units). |
| animationType | "rotate" | "hover" | "3drotate" |
| glow | number | 1 | Glow/bleed intensity multiplier. |
| offset | { x?: number; y?: number } | { x: 0, y: 0 } | Pixel offset within the canvas (x→right, y→down). |
| noise | number | 0.5 | Film-grain noise amount added to final color (0 disables). |
| transparent | boolean | true | Whether the canvas has an alpha channel (transparent background). |
| scale | number | 3.6 | Overall screen-space scale of the prism (bigger = larger). |
| hueShift | number | 0 | Hue rotation (radians) applied to final color. |
| colorFrequency | number | 1 | Frequency of internal sine bands controlling color variation. |
| hoverStrength | number | 2 | Sensitivity of hover tilt (pitch/yaw amplitude). |
| inertia | number | 0.05 | Easing factor for hover (0..1, higher = snappier). |
| bloom | number | 1 | Extra bloom factor layered on top of glow. |
| suspendWhenOffscreen | boolean | false | Pause rendering when the element is not in the viewport. |
| timeScale | number | 0.5 | Global time multiplier for animations (0=frozen, 1=normal). |
Full Component Source
import { useEffect, useRef } from 'react';
import { Renderer, Triangle, Program, Mesh } from 'ogl';
import './Prism.css';
const Prism = ({
height = 3.5,
baseWidth = 5.5,
animationType = 'rotate',
glow = 1,
offset = { x: 0, y: 0 },
noise = 0.5,
transparent = true,
scale = 3.6,
hueShift = 0,
colorFrequency = 1,
hoverStrength = 2,
inertia = 0.05,
bloom = 1,
suspendWhenOffscreen = false,
timeScale = 0.5
}) => {
const containerRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const H = Math.max(0.001, height);
const BW = Math.max(0.001, baseWidth);
const BASE_HALF = BW * 0.5;
const GLOW = Math.max(0.0, glow);
const NOISE = Math.max(0.0, noise);
const offX = offset?.x ?? 0;
const offY = offset?.y ?? 0;
const SAT = transparent ? 1.5 : 1;
const SCALE = Math.max(0.001, scale);
const HUE = hueShift || 0;
const CFREQ = Math.max(0.0, colorFrequency || 1);
const BLOOM = Math.max(0.0, bloom || 1);
const RSX = 1;
const RSY = 1;
const RSZ = 1;
const TS = Math.max(0, timeScale || 1);
const HOVSTR = Math.max(0, hoverStrength || 1);
const INERT = Math.max(0, Math.min(1, inertia || 0.12));
const dpr = Math.min(2, window.devicePixelRatio || 1);
const renderer = new Renderer({
dpr,
alpha: transparent,
antialias: false
});
const gl = renderer.gl;
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE);
gl.disable(gl.BLEND);
Object.assign(gl.canvas.style, {
position: 'absolute',
inset: '0',
width: '100%',
height: '100%',
display: 'block'
});
container.appendChild(gl.canvas);
const vertex = /* glsl */ `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragment = /* glsl */ `
precision highp float;
uniform vec2 iResolution;
uniform float iTime;
uniform float uHeight;
uniform float uBaseHalf;
uniform mat3 uRot;
uniform int uUseBaseWobble;
uniform float uGlow;
uniform vec2 uOffsetPx;
uniform float uNoise;
uniform float uSaturation;
uniform float uScale;
uniform float uHueShift;
uniform float uColorFreq;
uniform float uBloom;
uniform float uCenterShift;
uniform float uInvBaseHalf;
uniform float uInvHeight;
uniform float uMinAxis;
uniform float uPxScale;
uniform float uTimeScale;
vec4 tanh4(vec4 x){
vec4 e2x = exp(2.0*x);
return (e2x - 1.0) / (e2x + 1.0);
}
float rand(vec2 co){
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453123);
}
float sdOctaAnisoInv(vec3 p){
vec3 q = vec3(abs(p.x) * uInvBaseHalf, abs(p.y) * uInvHeight, abs(p.z) * uInvBaseHalf);
float m = q.x + q.y + q.z - 1.0;
return m * uMinAxis * 0.5773502691896258;
}
float sdPyramidUpInv(vec3 p){
float oct = sdOctaAnisoInv(p);
float halfSpace = -p.y;
return max(oct, halfSpace);
}
mat3 hueRotation(float a){
float c = cos(a), s = sin(a);
mat3 W = mat3(
0.299, 0.587, 0.114,
0.299, 0.587, 0.114,
0.299, 0.587, 0.114
);
mat3 U = mat3(
0.701, -0.587, -0.114,
-0.299, 0.413, -0.114,
-0.300, -0.588, 0.886
);
mat3 V = mat3(
0.168, -0.331, 0.500,
0.328, 0.035, -0.500,
-0.497, 0.296, 0.201
);
return W + U * c + V * s;
}
void main(){
vec2 f = (gl_FragCoord.xy - 0.5 * iResolution.xy - uOffsetPx) * uPxScale;
float z = 5.0;
float d = 0.0;
vec3 p;
vec4 o = vec4(0.0);
float centerShift = uCenterShift;
float cf = uColorFreq;
mat2 wob = mat2(1.0);
if (uUseBaseWobble == 1) {
float t = iTime * uTimeScale;
float c0 = cos(t + 0.0);
float c1 = cos(t + 33.0);
float c2 = cos(t + 11.0);
wob = mat2(c0, c1, c2, c0);
}
const int STEPS = 100;
for (int i = 0; i < STEPS; i++) {
p = vec3(f, z);
p.xz = p.xz * wob;
p = uRot * p;
vec3 q = p;
q.y += centerShift;
d = 0.1 + 0.2 * abs(sdPyramidUpInv(q));
z -= d;
o += (sin((p.y + z) * cf + vec4(0.0, 1.0, 2.0, 3.0)) + 1.0) / d;
}
o = tanh4(o * o * (uGlow * uBloom) / 1e5);
vec3 col = o.rgb;
float n = rand(gl_FragCoord.xy + vec2(iTime));
col += (n - 0.5) * uNoise;
col = clamp(col, 0.0, 1.0);
float L = dot(col, vec3(0.2126, 0.7152, 0.0722));
col = clamp(mix(vec3(L), col, uSaturation), 0.0, 1.0);
if(abs(uHueShift) > 0.0001){
col = clamp(hueRotation(uHueShift) * col, 0.0, 1.0);
}
gl_FragColor = vec4(col, o.a);
}
`;
const geometry = new Triangle(gl);
const iResBuf = new Float32Array(2);
const offsetPxBuf = new Float32Array(2);
const program = new Program(gl, {
vertex,
fragment,
uniforms: {
iResolution: { value: iResBuf },
iTime: { value: 0 },
uHeight: { value: H },
uBaseHalf: { value: BASE_HALF },
uUseBaseWobble: { value: 1 },
uRot: { value: new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]) },
uGlow: { value: GLOW },
uOffsetPx: { value: offsetPxBuf },
uNoise: { value: NOISE },
uSaturation: { value: SAT },
uScale: { value: SCALE },
uHueShift: { value: HUE },
uColorFreq: { value: CFREQ },
uBloom: { value: BLOOM },
uCenterShift: { value: H * 0.25 },
uInvBaseHalf: { value: 1 / BASE_HALF },
uInvHeight: { value: 1 / H },
uMinAxis: { value: Math.min(BASE_HALF, H) },
uPxScale: {
value: 1 / ((gl.drawingBufferHeight || 1) * 0.1 * SCALE)
},
uTimeScale: { value: TS }
}
});
const mesh = new Mesh(gl, { geometry, program });
const resize = () => {
const w = container.clientWidth || 1;
const h = container.clientHeight || 1;
renderer.setSize(w, h);
iResBuf[0] = gl.drawingBufferWidth;
iResBuf[1] = gl.drawingBufferHeight;
offsetPxBuf[0] = offX * dpr;
offsetPxBuf[1] = offY * dpr;
program.uniforms.uPxScale.value = 1 / ((gl.drawingBufferHeight || 1) * 0.1 * SCALE);
};
const ro = new ResizeObserver(resize);
ro.observe(container);
resize();
const rotBuf = new Float32Array(9);
const setMat3FromEuler = (yawY, pitchX, rollZ, out) => {
const cy = Math.cos(yawY),
sy = Math.sin(yawY);
const cx = Math.cos(pitchX),
sx = Math.sin(pitchX);
const cz = Math.cos(rollZ),
sz = Math.sin(rollZ);
const r00 = cy * cz + sy * sx * sz;
const r01 = -cy * sz + sy * sx * cz;
const r02 = sy * cx;
const r10 = cx * sz;
const r11 = cx * cz;
const r12 = -sx;
const r20 = -sy * cz + cy * sx * sz;
const r21 = sy * sz + cy * sx * cz;
const r22 = cy * cx;
out[0] = r00;
out[1] = r10;
out[2] = r20;
out[3] = r01;
out[4] = r11;
out[5] = r21;
out[6] = r02;
out[7] = r12;
out[8] = r22;
return out;
};
const NOISE_IS_ZERO = NOISE < 1e-6;
let raf = 0;
const t0 = performance.now();
const startRAF = () => {
if (raf) return;
raf = requestAnimationFrame(render);
};
const stopRAF = () => {
if (!raf) return;
cancelAnimationFrame(raf);
raf = 0;
};
const rnd = () => Math.random();
const wX = (0.3 + rnd() * 0.6) * RSX;
const wY = (0.2 + rnd() * 0.7) * RSY;
const wZ = (0.1 + rnd() * 0.5) * RSZ;
const phX = rnd() * Math.PI * 2;
const phZ = rnd() * Math.PI * 2;
let yaw = 0,
pitch = 0,
roll = 0;
let targetYaw = 0,
targetPitch = 0;
const lerp = (a, b, t) => a + (b - a) * t;
const pointer = { x: 0, y: 0, inside: true };
const onMove = e => {
const ww = Math.max(1, window.innerWidth);
const wh = Math.max(1, window.innerHeight);
const cx = ww * 0.5;
const cy = wh * 0.5;
const nx = (e.clientX - cx) / (ww * 0.5);
const ny = (e.clientY - cy) / (wh * 0.5);
pointer.x = Math.max(-1, Math.min(1, nx));
pointer.y = Math.max(-1, Math.min(1, ny));
pointer.inside = true;
};
const onLeave = () => {
pointer.inside = false;
};
const onBlur = () => {
pointer.inside = false;
};
let onPointerMove = null;
if (animationType === 'hover') {
onPointerMove = e => {
onMove(e);
startRAF();
};
window.addEventListener('pointermove', onPointerMove, { passive: true });
window.addEventListener('mouseleave', onLeave);
window.addEventListener('blur', onBlur);
program.uniforms.uUseBaseWobble.value = 0;
} else if (animationType === '3drotate') {
program.uniforms.uUseBaseWobble.value = 0;
} else {
program.uniforms.uUseBaseWobble.value = 1;
}
const render = t => {
const time = (t - t0) * 0.001;
program.uniforms.iTime.value = time;
let continueRAF = true;
if (animationType === 'hover') {
const maxPitch = 0.6 * HOVSTR;
const maxYaw = 0.6 * HOVSTR;
targetYaw = (pointer.inside ? -pointer.x : 0) * maxYaw;
targetPitch = (pointer.inside ? pointer.y : 0) * maxPitch;
const prevYaw = yaw;
const prevPitch = pitch;
const prevRoll = roll;
yaw = lerp(prevYaw, targetYaw, INERT);
pitch = lerp(prevPitch, targetPitch, INERT);
roll = lerp(prevRoll, 0, 0.1);
program.uniforms.uRot.value = setMat3FromEuler(yaw, pitch, roll, rotBuf);
if (NOISE_IS_ZERO) {
const settled =
Math.abs(yaw - targetYaw) < 1e-4 && Math.abs(pitch - targetPitch) < 1e-4 && Math.abs(roll) < 1e-4;
if (settled) continueRAF = false;
}
} else if (animationType === '3drotate') {
const tScaled = time * TS;
yaw = tScaled * wY;
pitch = Math.sin(tScaled * wX + phX) * 0.6;
roll = Math.sin(tScaled * wZ + phZ) * 0.5;
program.uniforms.uRot.value = setMat3FromEuler(yaw, pitch, roll, rotBuf);
if (TS < 1e-6) continueRAF = false;
} else {
rotBuf[0] = 1;
rotBuf[1] = 0;
rotBuf[2] = 0;
rotBuf[3] = 0;
rotBuf[4] = 1;
rotBuf[5] = 0;
rotBuf[6] = 0;
rotBuf[7] = 0;
rotBuf[8] = 1;
program.uniforms.uRot.value = rotBuf;
if (TS < 1e-6) continueRAF = false;
}
renderer.render({ scene: mesh });
if (continueRAF) {
raf = requestAnimationFrame(render);
} else {
raf = 0;
}
};
if (suspendWhenOffscreen) {
const io = new IntersectionObserver(entries => {
const vis = entries.some(e => e.isIntersecting);
if (vis) startRAF();
else stopRAF();
});
io.observe(container);
startRAF();
container.__prismIO = io;
} else {
startRAF();
}
return () => {
stopRAF();
ro.disconnect();
if (animationType === 'hover') {
if (onPointerMove) window.removeEventListener('pointermove', onPointerMove);
window.removeEventListener('mouseleave', onLeave);
window.removeEventListener('blur', onBlur);
}
if (suspendWhenOffscreen) {
const io = container.__prismIO;
if (io) io.disconnect();
delete container.__prismIO;
}
if (gl.canvas.parentElement === container) container.removeChild(gl.canvas);
};
}, [
height,
baseWidth,
animationType,
glow,
noise,
offset?.x,
offset?.y,
scale,
transparent,
hueShift,
colorFrequency,
timeScale,
hoverStrength,
inertia,
bloom,
suspendWhenOffscreen
]);
return <div className="prism-container" ref={containerRef} />;
};
export default Prism;
Component CSS
.prism-container {
position: relative;
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: dark veil code: ## Integrate the <DarkVeil /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: DarkVeil
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import DarkVeil from './DarkVeil';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<DarkVeil />
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| hueShift | number | 0 | Shifts the hue of the entire animation. |
| noiseIntensity | number | 0 | Intensity of the noise/grain effect. |
| scanlineIntensity | number | 0 | Intensity of the scanline effect. |
| speed | number | 0.5 | Speed of the animation. |
| scanlineFrequency | number | 0 | Frequency of the scanlines. |
| warpAmount | number | 0 | Amount of warp distortion applied to the effect. |
| resolutionScale | number | 1 | Scale factor for the resolution. |
Full Component Source
import { useRef, useEffect } from 'react';
import { Renderer, Program, Mesh, Triangle, Vec2 } from 'ogl';
import './DarkVeil.css';
const vertex = `
attribute vec2 position;
void main(){gl_Position=vec4(position,0.0,1.0);}
`;
const fragment = `
#ifdef GL_ES
precision lowp float;
#endif
uniform vec2 uResolution;
uniform float uTime;
uniform float uHueShift;
uniform float uNoise;
uniform float uScan;
uniform float uScanFreq;
uniform float uWarp;
#define iTime uTime
#define iResolution uResolution
vec4 buf[8];
float rand(vec2 c){return fract(sin(dot(c,vec2(12.9898,78.233)))*43758.5453);}
mat3 rgb2yiq=mat3(0.299,0.587,0.114,0.596,-0.274,-0.322,0.211,-0.523,0.312);
mat3 yiq2rgb=mat3(1.0,0.956,0.621,1.0,-0.272,-0.647,1.0,-1.106,1.703);
vec3 hueShiftRGB(vec3 col,float deg){
vec3 yiq=rgb2yiq*col;
float rad=radians(deg);
float cosh=cos(rad),sinh=sin(rad);
vec3 yiqShift=vec3(yiq.x,yiq.y*cosh-yiq.z*sinh,yiq.y*sinh+yiq.z*cosh);
return clamp(yiq2rgb*yiqShift,0.0,1.0);
}
vec4 sigmoid(vec4 x){return 1./(1.+exp(-x));}
vec4 cppn_fn(vec2 coordinate,float in0,float in1,float in2){
buf[6]=vec4(coordinate.x,coordinate.y,0.3948333106474662+in0,0.36+in1);
buf[7]=vec4(0.14+in2,sqrt(coordinate.x*coordinate.x+coordinate.y*coordinate.y),0.,0.);
buf[0]=mat4(vec4(6.5404263,-3.6126034,0.7590882,-1.13613),vec4(2.4582713,3.1660357,1.2219609,0.06276096),vec4(-5.478085,-6.159632,1.8701609,-4.7742867),vec4(6.039214,-5.542865,-0.90925294,3.251348))*buf[6]+mat4(vec4(0.8473259,-5.722911,3.975766,1.6522468),vec4(-0.24321538,0.5839259,-1.7661959,-5.350116),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(0.21808943,1.1243913,-1.7969975,5.0294676);
buf[1]=mat4(vec4(-3.3522482,-6.0612736,0.55641043,-4.4719114),vec4(0.8631464,1.7432913,5.643898,1.6106541),vec4(2.4941394,-3.5012043,1.7184316,6.357333),vec4(3.310376,8.209261,1.1355612,-1.165539))*buf[6]+mat4(vec4(5.24046,-13.034365,0.009859298,15.870829),vec4(2.987511,3.129433,-0.89023495,-1.6822904),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-5.9457836,-6.573602,-0.8812491,1.5436668);
buf[0]=sigmoid(buf[0]);buf[1]=sigmoid(buf[1]);
buf[2]=mat4(vec4(-15.219568,8.095543,-2.429353,-1.9381982),vec4(-5.951362,4.3115187,2.6393783,1.274315),vec4(-7.3145227,6.7297835,5.2473326,5.9411426),vec4(5.0796127,8.979051,-1.7278991,-1.158976))*buf[6]+mat4(vec4(-11.967154,-11.608155,6.1486754,11.237008),vec4(2.124141,-6.263192,-1.7050359,-0.7021966),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-4.17164,-3.2281182,-4.576417,-3.6401186);
buf[3]=mat4(vec4(3.1832156,-13.738922,1.879223,3.233465),vec4(0.64300746,12.768129,1.9141049,0.50990224),vec4(-0.049295485,4.4807224,1.4733979,1.801449),vec4(5.0039253,13.000481,3.3991797,-4.5561905))*buf[6]+mat4(vec4(-0.1285731,7.720628,-3.1425676,4.742367),vec4(0.6393625,3.714393,-0.8108378,-0.39174938),vec4(0.,0.,0.,0.),vec4(0.,0.,0.,0.))*buf[7]+vec4(-1.1811101,-21.621881,0.7851888,1.2329718);
buf[2]=sigmoid(buf[2]);buf[3]=sigmoid(buf[3]);
buf[4]=mat4(vec4(5.214916,-7.183024,2.7228765,2.6592617),vec4(-5.601878,-25.3591,4.067988,0.4602802),vec4(-10.57759,24.286327,21.102104,37.546658),vec4(4.3024497,-1.9625226,2.3458803,-1.372816))*buf[0]+mat4(vec4(-17.6526,-10.507558,2.2587414,12.462782),vec4(6.265566,-502.75443,-12.642513,0.9112289),vec4(-10.983244,20.741234,-9.701768,-0.7635988),vec4(5.383626,1.4819539,-4.1911616,-4.8444734))*buf[1]+mat4(vec4(12.785233,-16.345072,-0.39901125,1.7955981),vec4(-30.48365,-1.8345358,1.4542528,-1.1118771),vec4(19.872723,-7.337935,-42.941723,-98.52709),vec4(8.337645,-2.7312303,-2.2927687,-36.142323))*buf[2]+mat4(vec4(-16.298317,3.5471997,-0.44300047,-9.444417),vec4(57.5077,-35.609753,16.163465,-4.1534753),vec4(-0.07470326,-3.8656476,-7.0901804,3.1523974),vec4(-12.559385,-7.077619,1.490437,-0.8211543))*buf[3]+vec4(-7.67914,15.927437,1.3207729,-1.6686112);
buf[5]=mat4(vec4(-1.4109162,-0.372762,-3.770383,-21.367174),vec4(-6.2103205,-9.35908,0.92529047,8.82561),vec4(11.460242,-22.348068,13.625772,-18.693201),vec4(-0.3429052,-3.9905605,-2.4626114,-0.45033523))*buf[0]+mat4(vec4(7.3481627,-4.3661838,-6.3037653,-3.868115),vec4(1.5462853,6.5488915,1.9701879,-0.58291394),vec4(6.5858274,-2.2180402,3.7127688,-1.3730392),vec4(-5.7973905,10.134961,-2.3395722,-5.965605))*buf[1]+mat4(vec4(-2.5132585,-6.6685553,-1.4029363,-0.16285264),vec4(-0.37908727,0.53738135,4.389061,-1.3024765),vec4(-0.70647055,2.0111287,-5.1659346,-3.728635),vec4(-13.562562,10.487719,-0.9173751,-2.6487076))*buf[2]+mat4(vec4(-8.645013,6.5546675,-6.3944063,-5.5933375),vec4(-0.57783127,-1.077275,36.91025,5.736769),vec4(14.283112,3.7146652,7.1452246,-4.5958776),vec4(2.7192075,3.6021907,-4.366337,-2.3653464))*buf[3]+vec4(-5.9000807,-4.329569,1.2427121,8.59503);
buf[4]=sigmoid(buf[4]);buf[5]=sigmoid(buf[5]);
buf[6]=mat4(vec4(-1.61102,0.7970257,1.4675229,0.20917463),vec4(-28.793737,-7.1390953,1.5025433,4.656581),vec4(-10.94861,39.66238,0.74318546,-10.095605),vec4(-0.7229728,-1.5483948,0.7301322,2.1687684))*buf[0]+mat4(vec4(3.2547753,21.489103,-1.0194173,-3.3100595),vec4(-3.7316632,-3.3792162,-7.223193,-0.23685838),vec4(13.1804495,0.7916005,5.338587,5.687114),vec4(-4.167605,-17.798311,-6.815736,-1.6451967))*buf[1]+mat4(vec4(0.604885,-7.800309,-7.213122,-2.741014),vec4(-3.522382,-0.12359311,-0.5258442,0.43852118),vec4(9.6752825,-22.853785,2.062431,0.099892326),vec4(-4.3196306,-17.730087,2.5184598,5.30267))*buf[2]+mat4(vec4(-6.545563,-15.790176,-6.0438633,-5.415399),vec4(-43.591583,28.551912,-16.00161,18.84728),vec4(4.212382,8.394307,3.0958717,8.657522),vec4(-5.0237565,-4.450633,-4.4768,-5.5010443))*buf[3]+mat4(vec4(1.6985557,-67.05806,6.897715,1.9004834),vec4(1.8680354,2.3915145,2.5231109,4.081538),vec4(11.158006,1.7294737,2.0738268,7.386411),vec4(-4.256034,-306.24686,8.258898,-17.132736))*buf[4]+mat4(vec4(1.6889864,-4.5852966,3.8534803,-6.3482175),vec4(1.3543309,-1.2640043,9.932754,2.9079645),vec4(-5.2770967,0.07150358,-0.13962056,3.3269649),vec4(28.34703,-4.918278,6.1044083,4.085355))*buf[5]+vec4(6.6818056,12.522166,-3.7075126,-4.104386);
buf[7]=mat4(vec4(-8.265602,-4.7027016,5.098234,0.7509808),vec4(8.6507845,-17.15949,16.51939,-8.884479),vec4(-4.036479,-2.3946867,-2.6055532,-1.9866527),vec4(-2.2167742,-1.8135649,-5.9759874,4.8846445))*buf[0]+mat4(vec4(6.7790847,3.5076547,-2.8191125,-2.7028968),vec4(-5.743024,-0.27844876,1.4958696,-5.0517144),vec4(13.122226,15.735168,-2.9397483,-4.101023),vec4(-14.375265,-5.030483,-6.2599335,2.9848232))*buf[1]+mat4(vec4(4.0950394,-0.94011575,-5.674733,4.755022),vec4(4.3809423,4.8310084,1.7425908,-3.437416),vec4(2.117492,0.16342592,-104.56341,16.949184),vec4(-5.22543,-2.994248,3.8350096,-1.9364246))*buf[2]+mat4(vec4(-5.900337,1.7946124,-13.604192,-3.8060522),vec4(6.6583457,31.911177,25.164474,91.81147),vec4(11.840538,4.1503043,-0.7314397,6.768467),vec4(-6.3967767,4.034772,6.1714606,-0.32874924))*buf[3]+mat4(vec4(3.4992442,-196.91893,-8.923708,2.8142626),vec4(3.4806502,-3.1846354,5.1725626,5.1804223),vec4(-2.4009497,15.585794,1.2863957,2.0252278),vec4(-71.25271,-62.441242,-8.138444,0.50670296))*buf[4]+mat4(vec4(-12.291733,-11.176166,-7.3474145,4.390294),vec4(10.805477,5.6337385,-0.9385842,-4.7348723),vec4(-12.869276,-7.039391,5.3029537,7.5436664),vec4(1.4593618,8.91898,3.5101583,5.840625))*buf[5]+vec4(2.2415268,-6.705987,-0.98861027,-2.117676);
buf[6]=sigmoid(buf[6]);buf[7]=sigmoid(buf[7]);
buf[0]=mat4(vec4(1.6794263,1.3817469,2.9625452,0.),vec4(-1.8834411,-1.4806935,-3.5924516,0.),vec4(-1.3279216,-1.0918057,-2.3124623,0.),vec4(0.2662234,0.23235129,0.44178495,0.))*buf[0]+mat4(vec4(-0.6299101,-0.5945583,-0.9125601,0.),vec4(0.17828953,0.18300213,0.18182953,0.),vec4(-2.96544,-2.5819945,-4.9001055,0.),vec4(1.4195864,1.1868085,2.5176322,0.))*buf[1]+mat4(vec4(-1.2584374,-1.0552157,-2.1688404,0.),vec4(-0.7200217,-0.52666044,-1.438251,0.),vec4(0.15345335,0.15196142,0.272854,0.),vec4(0.945728,0.8861938,1.2766753,0.))*buf[2]+mat4(vec4(-2.4218085,-1.968602,-4.35166,0.),vec4(-22.683098,-18.0544,-41.954372,0.),vec4(0.63792,0.5470648,1.1078634,0.),vec4(-1.5489894,-1.3075932,-2.6444845,0.))*buf[3]+mat4(vec4(-0.49252132,-0.39877754,-0.91366625,0.),vec4(0.95609266,0.7923952,1.640221,0.),vec4(0.30616966,0.15693925,0.8639857,0.),vec4(1.1825981,0.94504964,2.176963,0.))*buf[4]+mat4(vec4(0.35446745,0.3293795,0.59547555,0.),vec4(-0.58784515,-0.48177817,-1.0614829,0.),vec4(2.5271258,1.9991658,4.6846647,0.),vec4(0.13042648,0.08864098,0.30187556,0.))*buf[5]+mat4(vec4(-1.7718065,-1.4033192,-3.3355875,0.),vec4(3.1664357,2.638297,5.378702,0.),vec4(-3.1724713,-2.6107926,-5.549295,0.),vec4(-2.851368,-2.249092,-5.3013067,0.))*buf[6]+mat4(vec4(1.5203838,1.2212278,2.8404984,0.),vec4(1.5210563,1.2651345,2.683903,0.),vec4(2.9789467,2.4364579,5.2347264,0.),vec4(2.2270417,1.8825914,3.8028636,0.))*buf[7]+vec4(-1.5468478,-3.6171484,0.24762098,0.);
buf[0]=sigmoid(buf[0]);
return vec4(buf[0].x,buf[0].y,buf[0].z,1.);
}
void mainImage(out vec4 fragColor,in vec2 fragCoord){
vec2 uv=fragCoord/uResolution.xy*2.-1.;
uv.y*=-1.;
uv+=uWarp*vec2(sin(uv.y*6.283+uTime*0.5),cos(uv.x*6.283+uTime*0.5))*0.05;
fragColor=cppn_fn(uv,0.1*sin(0.3*uTime),0.1*sin(0.69*uTime),0.1*sin(0.44*uTime));
}
void main(){
vec4 col;mainImage(col,gl_FragCoord.xy);
col.rgb=hueShiftRGB(col.rgb,uHueShift);
float scanline_val=sin(gl_FragCoord.y*uScanFreq)*0.5+0.5;
col.rgb*=1.-(scanline_val*scanline_val)*uScan;
col.rgb+=(rand(gl_FragCoord.xy+uTime)-0.5)*uNoise;
gl_FragColor=vec4(clamp(col.rgb,0.0,1.0),1.0);
}
`;
export default function DarkVeil({
hueShift = 0,
noiseIntensity = 0,
scanlineIntensity = 0,
speed = 0.5,
scanlineFrequency = 0,
warpAmount = 0,
resolutionScale = 1
}) {
const ref = useRef(null);
useEffect(() => {
const canvas = ref.current;
const parent = canvas.parentElement;
const renderer = new Renderer({
dpr: Math.min(window.devicePixelRatio, 2),
canvas
});
const gl = renderer.gl;
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex,
fragment,
uniforms: {
uTime: { value: 0 },
uResolution: { value: new Vec2() },
uHueShift: { value: hueShift },
uNoise: { value: noiseIntensity },
uScan: { value: scanlineIntensity },
uScanFreq: { value: scanlineFrequency },
uWarp: { value: warpAmount }
}
});
const mesh = new Mesh(gl, { geometry, program });
const resize = () => {
const w = parent.clientWidth,
h = parent.clientHeight;
renderer.setSize(w * resolutionScale, h * resolutionScale);
program.uniforms.uResolution.value.set(w, h);
};
window.addEventListener('resize', resize);
resize();
const start = performance.now();
let frame = 0;
const loop = () => {
program.uniforms.uTime.value = ((performance.now() - start) / 1000) * speed;
program.uniforms.uHueShift.value = hueShift;
program.uniforms.uNoise.value = noiseIntensity;
program.uniforms.uScan.value = scanlineIntensity;
program.uniforms.uScanFreq.value = scanlineFrequency;
program.uniforms.uWarp.value = warpAmount;
renderer.render({ scene: mesh });
frame = requestAnimationFrame(loop);
};
loop();
return () => {
cancelAnimationFrame(frame);
window.removeEventListener('resize', resize);
};
}, [hueShift, noiseIntensity, scanlineIntensity, speed, scanlineFrequency, warpAmount, resolutionScale]);
return <canvas ref={ref} className="darkveil-canvas" />;
}
Component CSS
.darkveil-canvas {
width: 100%;
height: 100%;
display: block;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: light pillar code: ## Integrate the <LightPillar /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: LightPillar
Variant: JavaScript + CSS
Dependencies: three
Usage Example
import LightPillar from './LightPillar';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<LightPillar
topColor="#5227FF"
bottomColor="#FF9FFC"
intensity={1.0}
rotationSpeed={0.3}
glowAmount={0.005}
pillarWidth={3.0}
pillarHeight={0.4}
noiseIntensity={0.5}
pillarRotation={0}
interactive={false}
mixBlendMode="normal"
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| topColor | string | '#5227FF' | Hex color string for the top gradient color of the light pillar. |
| bottomColor | string | '#FF9FFC' | Hex color string for the bottom gradient color of the light pillar. |
| intensity | number | 1.0 | Controls the overall brightness and intensity of the effect. |
| rotationSpeed | number | 0.3 | Speed multiplier for the pillar rotation animation. |
| interactive | boolean | false | Enable mouse interaction to control the pillar rotation. |
| glowAmount | number | 0.005 | Controls the glow intensity and spread of the light effect. |
| pillarWidth | number | 3.0 | Width/radius of the light pillar. |
| pillarHeight | number | 0.4 | Height scaling factor for the pillar distortion. |
| noiseIntensity | number | 0.5 | Intensity of the film grain noise postprocessing effect. |
| className | string | '' | Additional CSS class names to apply to the container element. |
| mixBlendMode | string | 'screen' | CSS mix-blend-mode property to control how the component blends with its background. |
| pillarRotation | number | 0 | Rotation angle of the pillar in degrees (0-360). |
| quality | 'low' | 'medium' | 'high' |
Full Component Source
import { useRef, useEffect, useState } from 'react';
import * as THREE from 'three';
import './LightPillar.css';
const LightPillar = ({
topColor = '#5227FF',
bottomColor = '#FF9FFC',
intensity = 1.0,
rotationSpeed = 0.3,
interactive = false,
className = '',
glowAmount = 0.005,
pillarWidth = 3.0,
pillarHeight = 0.4,
noiseIntensity = 0.5,
mixBlendMode = 'screen',
pillarRotation = 0,
quality = 'high'
}) => {
const containerRef = useRef(null);
const rafRef = useRef(null);
const rendererRef = useRef(null);
const materialRef = useRef(null);
const sceneRef = useRef(null);
const cameraRef = useRef(null);
const geometryRef = useRef(null);
const mouseRef = useRef(new THREE.Vector2(0, 0));
const timeRef = useRef(0);
const rotationSpeedRef = useRef(rotationSpeed);
const [webGLSupported, setWebGLSupported] = useState(true);
useEffect(() => {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) {
setWebGLSupported(false);
}
}, []);
useEffect(() => {
if (!containerRef.current || !webGLSupported) return;
const container = containerRef.current;
const width = container.clientWidth;
const height = container.clientHeight;
const scene = new THREE.Scene();
sceneRef.current = scene;
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
cameraRef.current = camera;
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const isLowEndDevice = isMobile || (navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4);
let effectiveQuality = quality;
if (isLowEndDevice && quality === 'high') effectiveQuality = 'medium';
if (isMobile && quality !== 'low') effectiveQuality = 'low';
const qualitySettings = {
low: { iterations: 24, waveIterations: 1, pixelRatio: 0.5, precision: 'mediump', stepMultiplier: 1.5 },
medium: { iterations: 40, waveIterations: 2, pixelRatio: 0.65, precision: 'mediump', stepMultiplier: 1.2 },
high: {
iterations: 80,
waveIterations: 4,
pixelRatio: Math.min(window.devicePixelRatio, 2),
precision: 'highp',
stepMultiplier: 1.0
}
};
const settings = qualitySettings[effectiveQuality] || qualitySettings.medium;
let renderer;
try {
renderer = new THREE.WebGLRenderer({
antialias: false,
alpha: true,
powerPreference: effectiveQuality === 'high' ? 'high-performance' : 'low-power',
precision: settings.precision,
stencil: false,
depth: false
});
} catch (error) {
setWebGLSupported(false);
return;
}
renderer.setSize(width, height);
renderer.setPixelRatio(settings.pixelRatio);
container.appendChild(renderer.domElement);
rendererRef.current = renderer;
const parseColor = hex => {
const color = new THREE.Color(hex);
return new THREE.Vector3(color.r, color.g, color.b);
};
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
`;
const fragmentShader = `
precision ${settings.precision} float;
uniform float uTime;
uniform vec2 uResolution;
uniform vec2 uMouse;
uniform vec3 uTopColor;
uniform vec3 uBottomColor;
uniform float uIntensity;
uniform bool uInteractive;
uniform float uGlowAmount;
uniform float uPillarWidth;
uniform float uPillarHeight;
uniform float uNoiseIntensity;
uniform float uRotCos;
uniform float uRotSin;
uniform float uPillarRotCos;
uniform float uPillarRotSin;
uniform float uWaveSin;
uniform float uWaveCos;
varying vec2 vUv;
const float STEP_MULT = ${settings.stepMultiplier.toFixed(1)};
const int MAX_ITER = ${settings.iterations};
const int WAVE_ITER = ${settings.waveIterations};
void main() {
vec2 uv = (vUv * 2.0 - 1.0) * vec2(uResolution.x / uResolution.y, 1.0);
uv = vec2(uPillarRotCos * uv.x - uPillarRotSin * uv.y, uPillarRotSin * uv.x + uPillarRotCos * uv.y);
vec3 ro = vec3(0.0, 0.0, -10.0);
vec3 rd = normalize(vec3(uv, 1.0));
float rotC = uRotCos;
float rotS = uRotSin;
if(uInteractive && (uMouse.x != 0.0 || uMouse.y != 0.0)) {
float a = uMouse.x * 6.283185;
rotC = cos(a);
rotS = sin(a);
}
vec3 col = vec3(0.0);
float t = 0.1;
for(int i = 0; i < MAX_ITER; i++) {
vec3 p = ro + rd * t;
p.xz = vec2(rotC * p.x - rotS * p.z, rotS * p.x + rotC * p.z);
vec3 q = p;
q.y = p.y * uPillarHeight + uTime;
float freq = 1.0;
float amp = 1.0;
for(int j = 0; j < WAVE_ITER; j++) {
q.xz = vec2(uWaveCos * q.x - uWaveSin * q.z, uWaveSin * q.x + uWaveCos * q.z);
q += cos(q.zxy * freq - uTime * float(j) * 2.0) * amp;
freq *= 2.0;
amp *= 0.5;
}
float d = length(cos(q.xz)) - 0.2;
float bound = length(p.xz) - uPillarWidth;
float k = 4.0;
float h = max(k - abs(d - bound), 0.0);
d = max(d, bound) + h * h * 0.0625 / k;
d = abs(d) * 0.15 + 0.01;
float grad = clamp((15.0 - p.y) / 30.0, 0.0, 1.0);
col += mix(uBottomColor, uTopColor, grad) / d;
t += d * STEP_MULT;
if(t > 50.0) break;
}
float widthNorm = uPillarWidth / 3.0;
col = tanh(col * uGlowAmount / widthNorm);
col -= fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453) / 15.0 * uNoiseIntensity;
gl_FragColor = vec4(col * uIntensity, 1.0);
}
`;
const pillarRotRad = (pillarRotation * Math.PI) / 180;
const waveSin = Math.sin(0.4);
const waveCos = Math.cos(0.4);
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
uTime: { value: 0 },
uResolution: { value: new THREE.Vector2(width, height) },
uMouse: { value: mouseRef.current },
uTopColor: { value: parseColor(topColor) },
uBottomColor: { value: parseColor(bottomColor) },
uIntensity: { value: intensity },
uInteractive: { value: interactive },
uGlowAmount: { value: glowAmount },
uPillarWidth: { value: pillarWidth },
uPillarHeight: { value: pillarHeight },
uNoiseIntensity: { value: noiseIntensity },
uRotCos: { value: 1.0 },
uRotSin: { value: 0.0 },
uPillarRotCos: { value: Math.cos(pillarRotRad) },
uPillarRotSin: { value: Math.sin(pillarRotRad) },
uWaveSin: { value: waveSin },
uWaveCos: { value: waveCos }
},
transparent: true,
depthWrite: false,
depthTest: false
});
materialRef.current = material;
const geometry = new THREE.PlaneGeometry(2, 2);
geometryRef.current = geometry;
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
let mouseMoveTimeout = null;
const handleMouseMove = event => {
if (!interactive) return;
if (mouseMoveTimeout) return;
mouseMoveTimeout = window.setTimeout(() => {
mouseMoveTimeout = null;
}, 16);
const rect = container.getBoundingClientRect();
const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
mouseRef.current.set(x, y);
};
if (interactive) {
container.addEventListener('mousemove', handleMouseMove, { passive: true });
}
let lastTime = performance.now();
const targetFPS = effectiveQuality === 'low' ? 30 : 60;
const frameTime = 1000 / targetFPS;
const animate = currentTime => {
if (!materialRef.current || !rendererRef.current || !sceneRef.current || !cameraRef.current) return;
const deltaTime = currentTime - lastTime;
if (deltaTime >= frameTime) {
timeRef.current += 0.016 * rotationSpeedRef.current;
const t = timeRef.current;
materialRef.current.uniforms.uTime.value = t;
materialRef.current.uniforms.uRotCos.value = Math.cos(t * 0.3);
materialRef.current.uniforms.uRotSin.value = Math.sin(t * 0.3);
rendererRef.current.render(sceneRef.current, cameraRef.current);
lastTime = currentTime - (deltaTime % frameTime);
}
rafRef.current = requestAnimationFrame(animate);
};
rafRef.current = requestAnimationFrame(animate);
let resizeTimeout = null;
const handleResize = () => {
if (resizeTimeout) {
clearTimeout(resizeTimeout);
}
resizeTimeout = window.setTimeout(() => {
if (!rendererRef.current || !materialRef.current || !containerRef.current) return;
const newWidth = containerRef.current.clientWidth;
const newHeight = containerRef.current.clientHeight;
rendererRef.current.setSize(newWidth, newHeight);
materialRef.current.uniforms.uResolution.value.set(newWidth, newHeight);
}, 150);
};
window.addEventListener('resize', handleResize, { passive: true });
return () => {
window.removeEventListener('resize', handleResize);
if (interactive) {
container.removeEventListener('mousemove', handleMouseMove);
}
if (rafRef.current) {
cancelAnimationFrame(rafRef.current);
}
if (rendererRef.current) {
rendererRef.current.dispose();
rendererRef.current.forceContextLoss();
if (container.contains(rendererRef.current.domElement)) {
container.removeChild(rendererRef.current.domElement);
}
}
if (materialRef.current) materialRef.current.dispose();
if (geometryRef.current) geometryRef.current.dispose();
rendererRef.current = null;
materialRef.current = null;
sceneRef.current = null;
cameraRef.current = null;
geometryRef.current = null;
rafRef.current = null;
};
}, [webGLSupported, quality]);
useEffect(() => {
rotationSpeedRef.current = rotationSpeed;
}, [rotationSpeed]);
useEffect(() => {
if (!materialRef.current) return;
const parseColor = hex => {
const color = new THREE.Color(hex);
return new THREE.Vector3(color.r, color.g, color.b);
};
materialRef.current.uniforms.uTopColor.value = parseColor(topColor);
}, [topColor]);
useEffect(() => {
if (!materialRef.current) return;
const parseColor = hex => {
const color = new THREE.Color(hex);
return new THREE.Vector3(color.r, color.g, color.b);
};
materialRef.current.uniforms.uBottomColor.value = parseColor(bottomColor);
}, [bottomColor]);
useEffect(() => {
if (!materialRef.current) return;
materialRef.current.uniforms.uIntensity.value = intensity;
}, [intensity]);
useEffect(() => {
if (!materialRef.current) return;
materialRef.current.uniforms.uInteractive.value = interactive;
}, [interactive]);
useEffect(() => {
if (!materialRef.current) return;
materialRef.current.uniforms.uGlowAmount.value = glowAmount;
}, [glowAmount]);
useEffect(() => {
if (!materialRef.current) return;
materialRef.current.uniforms.uPillarWidth.value = pillarWidth;
}, [pillarWidth]);
useEffect(() => {
if (!materialRef.current) return;
materialRef.current.uniforms.uPillarHeight.value = pillarHeight;
}, [pillarHeight]);
useEffect(() => {
if (!materialRef.current) return;
materialRef.current.uniforms.uNoiseIntensity.value = noiseIntensity;
}, [noiseIntensity]);
useEffect(() => {
if (!materialRef.current) return;
const pillarRotRad = (pillarRotation * Math.PI) / 180;
materialRef.current.uniforms.uPillarRotCos.value = Math.cos(pillarRotRad);
materialRef.current.uniforms.uPillarRotSin.value = Math.sin(pillarRotRad);
}, [pillarRotation]);
if (!webGLSupported) {
return (
<div className={`light-pillar-fallback ${className}`} style={{ mixBlendMode }}>
WebGL not supported
</div>
);
}
return <div ref={containerRef} className={`light-pillar-container ${className}`} style={{ mixBlendMode }} />;
};
export default LightPillar;
Component CSS
.light-pillar-fallback {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.1);
color: #888;
font-size: 14px;
}
.light-pillar-container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: silk code: ## Integrate the <Silk /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Silk
Variant: JavaScript + CSS
Usage Example
import Silk from './Silk';
<Silk
speed={5}
scale={1}
color="#7B7481"
noiseIntensity={1.5}
rotation={0}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| speed | number | 5 | Controls the animation speed of the silk effect. |
| scale | number | 1 | Controls the scale of the silk pattern. |
| color | string | '#7B7481' | Hex color code for the silk pattern. |
| noiseIntensity | number | 1.5 | Controls the intensity of the noise effect. |
| rotation | number | 0 | Controls the rotation of the silk pattern (in radians). |
Full Component Source
/* eslint-disable react/no-unknown-property */
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { forwardRef, useRef, useMemo, useLayoutEffect } from 'react';
import { Color } from 'three';
const hexToNormalizedRGB = hex => {
hex = hex.replace('#', '');
return [
parseInt(hex.slice(0, 2), 16) / 255,
parseInt(hex.slice(2, 4), 16) / 255,
parseInt(hex.slice(4, 6), 16) / 255
];
};
const vertexShader = `
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vPosition = position;
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
varying vec2 vUv;
varying vec3 vPosition;
uniform float uTime;
uniform vec3 uColor;
uniform float uSpeed;
uniform float uScale;
uniform float uRotation;
uniform float uNoiseIntensity;
const float e = 2.71828182845904523536;
float noise(vec2 texCoord) {
float G = e;
vec2 r = (G * sin(G * texCoord));
return fract(r.x * r.y * (1.0 + texCoord.x));
}
vec2 rotateUvs(vec2 uv, float angle) {
float c = cos(angle);
float s = sin(angle);
mat2 rot = mat2(c, -s, s, c);
return rot * uv;
}
void main() {
float rnd = noise(gl_FragCoord.xy);
vec2 uv = rotateUvs(vUv * uScale, uRotation);
vec2 tex = uv * uScale;
float tOffset = uSpeed * uTime;
tex.y += 0.03 * sin(8.0 * tex.x - tOffset);
float pattern = 0.6 +
0.4 * sin(5.0 * (tex.x + tex.y +
cos(3.0 * tex.x + 5.0 * tex.y) +
0.02 * tOffset) +
sin(20.0 * (tex.x + tex.y - 0.1 * tOffset)));
vec4 col = vec4(uColor, 1.0) * vec4(pattern) - rnd / 15.0 * uNoiseIntensity;
col.a = 1.0;
gl_FragColor = col;
}
`;
const SilkPlane = forwardRef(function SilkPlane({ uniforms }, ref) {
const { viewport } = useThree();
useLayoutEffect(() => {
if (ref.current) {
ref.current.scale.set(viewport.width, viewport.height, 1);
}
}, [ref, viewport]);
useFrame((_, delta) => {
ref.current.material.uniforms.uTime.value += 0.1 * delta;
});
return (
<mesh ref={ref}>
<planeGeometry args={[1, 1, 1, 1]} />
<shaderMaterial uniforms={uniforms} vertexShader={vertexShader} fragmentShader={fragmentShader} />
</mesh>
);
});
SilkPlane.displayName = 'SilkPlane';
const Silk = ({ speed = 5, scale = 1, color = '#7B7481', noiseIntensity = 1.5, rotation = 0 }) => {
const meshRef = useRef();
const uniforms = useMemo(
() => ({
uSpeed: { value: speed },
uScale: { value: scale },
uNoiseIntensity: { value: noiseIntensity },
uColor: { value: new Color(...hexToNormalizedRGB(color)) },
uRotation: { value: rotation },
uTime: { value: 0 }
}),
[speed, scale, noiseIntensity, color, rotation]
);
return (
<Canvas dpr={[1, 2]} frameloop="always">
<SilkPlane ref={meshRef} uniforms={uniforms} />
</Canvas>
);
};
export default Silk;
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: floating lines code: ## Integrate the <FloatingLines /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: FloatingLines
Variant: JavaScript + CSS
Dependencies: three
Usage Example
import FloatingLines from './FloatingLines';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<FloatingLines
enabledWaves={['top', 'middle', 'bottom']}
// Array - specify line count per wave; Number - same count for all waves
lineCount={[10, 15, 20]}
// Array - specify line distance per wave; Number - same distance for all waves
lineDistance={[8, 6, 4]}
bendRadius={5.0}
bendStrength={-0.5}
interactive={true}
parallax={true}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| linesGradient | string[] | undefined | Array of hex color strings for gradient coloring of lines (max 8 colors). |
| enabledWaves | Array<'top' | 'middle' | 'bottom'> |
| lineCount | number | number[] | [6] |
| lineDistance | number | number[] | [5] |
| topWavePosition | { x: number; y: number; rotate: number } | undefined | Position and rotation settings for the top wave layer. |
| middleWavePosition | { x: number; y: number; rotate: number } | undefined | Position and rotation settings for the middle wave layer. |
| bottomWavePosition | { x: number; y: number; rotate: number } | { x: 2.0, y: -0.7, rotate: -1 } | Position and rotation settings for the bottom wave layer. |
| animationSpeed | number | 1 | Speed multiplier for the wave animation. |
| interactive | boolean | true | Whether the lines react to mouse/pointer movement. |
| bendRadius | number | 10.0 | Radius of the area affected by mouse interaction. |
| bendStrength | number | -5.0 | Intensity of the bend effect when interacting with mouse. |
| mouseDamping | number | 0.05 | Smoothing factor for mouse movement tracking (0-1). |
| parallax | boolean | true | Enable parallax effect with mouse movement. |
| parallaxStrength | number | 0.2 | Strength of the parallax effect. |
| mixBlendMode | React.CSSProperties['mixBlendMode'] | 'screen' | CSS mix-blend-mode applied to the canvas element. |
Full Component Source
import { useEffect, useRef } from 'react';
import {
Clock,
Mesh,
OrthographicCamera,
PlaneGeometry,
Scene,
ShaderMaterial,
Vector2,
Vector3,
WebGLRenderer
} from 'three';
import './FloatingLines.css';
const vertexShader = `
precision highp float;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
precision highp float;
uniform float iTime;
uniform vec3 iResolution;
uniform float animationSpeed;
uniform bool enableTop;
uniform bool enableMiddle;
uniform bool enableBottom;
uniform int topLineCount;
uniform int middleLineCount;
uniform int bottomLineCount;
uniform float topLineDistance;
uniform float middleLineDistance;
uniform float bottomLineDistance;
uniform vec3 topWavePosition;
uniform vec3 middleWavePosition;
uniform vec3 bottomWavePosition;
uniform vec2 iMouse;
uniform bool interactive;
uniform float bendRadius;
uniform float bendStrength;
uniform float bendInfluence;
uniform bool parallax;
uniform float parallaxStrength;
uniform vec2 parallaxOffset;
uniform vec3 lineGradient[8];
uniform int lineGradientCount;
const vec3 BLACK = vec3(0.0);
const vec3 PINK = vec3(233.0, 71.0, 245.0) / 255.0;
const vec3 BLUE = vec3(47.0, 75.0, 162.0) / 255.0;
mat2 rotate(float r) {
return mat2(cos(r), sin(r), -sin(r), cos(r));
}
vec3 background_color(vec2 uv) {
vec3 col = vec3(0.0);
float y = sin(uv.x - 0.2) * 0.3 - 0.1;
float m = uv.y - y;
col += mix(BLUE, BLACK, smoothstep(0.0, 1.0, abs(m)));
col += mix(PINK, BLACK, smoothstep(0.0, 1.0, abs(m - 0.8)));
return col * 0.5;
}
vec3 getLineColor(float t, vec3 baseColor) {
if (lineGradientCount <= 0) {
return baseColor;
}
vec3 gradientColor;
if (lineGradientCount == 1) {
gradientColor = lineGradient[0];
} else {
float clampedT = clamp(t, 0.0, 0.9999);
float scaled = clampedT * float(lineGradientCount - 1);
int idx = int(floor(scaled));
float f = fract(scaled);
int idx2 = min(idx + 1, lineGradientCount - 1);
vec3 c1 = lineGradient[idx];
vec3 c2 = lineGradient[idx2];
gradientColor = mix(c1, c2, f);
}
return gradientColor * 0.5;
}
float wave(vec2 uv, float offset, vec2 screenUv, vec2 mouseUv, bool shouldBend) {
float time = iTime * animationSpeed;
float x_offset = offset;
float x_movement = time * 0.1;
float amp = sin(offset + time * 0.2) * 0.3;
float y = sin(uv.x + x_offset + x_movement) * amp;
if (shouldBend) {
vec2 d = screenUv - mouseUv;
float influence = exp(-dot(d, d) * bendRadius); // radial falloff around cursor
float bendOffset = (mouseUv.y - screenUv.y) * influence * bendStrength * bendInfluence;
y += bendOffset;
}
float m = uv.y - y;
return 0.0175 / max(abs(m) + 0.01, 1e-3) + 0.01;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 baseUv = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
baseUv.y *= -1.0;
if (parallax) {
baseUv += parallaxOffset;
}
vec3 col = vec3(0.0);
vec3 b = lineGradientCount > 0 ? vec3(0.0) : background_color(baseUv);
vec2 mouseUv = vec2(0.0);
if (interactive) {
mouseUv = (2.0 * iMouse - iResolution.xy) / iResolution.y;
mouseUv.y *= -1.0;
}
if (enableBottom) {
for (int i = 0; i < bottomLineCount; ++i) {
float fi = float(i);
float t = fi / max(float(bottomLineCount - 1), 1.0);
vec3 lineCol = getLineColor(t, b);
float angle = bottomWavePosition.z * log(length(baseUv) + 1.0);
vec2 ruv = baseUv * rotate(angle);
col += lineCol * wave(
ruv + vec2(bottomLineDistance * fi + bottomWavePosition.x, bottomWavePosition.y),
1.5 + 0.2 * fi,
baseUv,
mouseUv,
interactive
) * 0.2;
}
}
if (enableMiddle) {
for (int i = 0; i < middleLineCount; ++i) {
float fi = float(i);
float t = fi / max(float(middleLineCount - 1), 1.0);
vec3 lineCol = getLineColor(t, b);
float angle = middleWavePosition.z * log(length(baseUv) + 1.0);
vec2 ruv = baseUv * rotate(angle);
col += lineCol * wave(
ruv + vec2(middleLineDistance * fi + middleWavePosition.x, middleWavePosition.y),
2.0 + 0.15 * fi,
baseUv,
mouseUv,
interactive
);
}
}
if (enableTop) {
for (int i = 0; i < topLineCount; ++i) {
float fi = float(i);
float t = fi / max(float(topLineCount - 1), 1.0);
vec3 lineCol = getLineColor(t, b);
float angle = topWavePosition.z * log(length(baseUv) + 1.0);
vec2 ruv = baseUv * rotate(angle);
ruv.x *= -1.0;
col += lineCol * wave(
ruv + vec2(topLineDistance * fi + topWavePosition.x, topWavePosition.y),
1.0 + 0.2 * fi,
baseUv,
mouseUv,
interactive
) * 0.1;
}
}
fragColor = vec4(col, 1.0);
}
void main() {
vec4 color = vec4(0.0);
mainImage(color, gl_FragCoord.xy);
gl_FragColor = color;
}
`;
const MAX_GRADIENT_STOPS = 8;
function hexToVec3(hex) {
let value = hex.trim();
if (value.startsWith('#')) {
value = value.slice(1);
}
let r = 255;
let g = 255;
let b = 255;
if (value.length === 3) {
r = parseInt(value[0] + value[0], 16);
g = parseInt(value[1] + value[1], 16);
b = parseInt(value[2] + value[2], 16);
} else if (value.length === 6) {
r = parseInt(value.slice(0, 2), 16);
g = parseInt(value.slice(2, 4), 16);
b = parseInt(value.slice(4, 6), 16);
}
return new Vector3(r / 255, g / 255, b / 255);
}
export default function FloatingLines({
linesGradient,
enabledWaves = ['top', 'middle', 'bottom'],
lineCount = [6],
lineDistance = [5],
topWavePosition,
middleWavePosition,
bottomWavePosition = { x: 2.0, y: -0.7, rotate: -1 },
animationSpeed = 1,
interactive = true,
bendRadius = 5.0,
bendStrength = -0.5,
mouseDamping = 0.05,
parallax = true,
parallaxStrength = 0.2,
mixBlendMode = 'screen'
}) {
const containerRef = useRef(null);
const targetMouseRef = useRef(new Vector2(-1000, -1000));
const currentMouseRef = useRef(new Vector2(-1000, -1000));
const targetInfluenceRef = useRef(0);
const currentInfluenceRef = useRef(0);
const targetParallaxRef = useRef(new Vector2(0, 0));
const currentParallaxRef = useRef(new Vector2(0, 0));
const getLineCount = waveType => {
if (typeof lineCount === 'number') return lineCount;
if (!enabledWaves.includes(waveType)) return 0;
const index = enabledWaves.indexOf(waveType);
return lineCount[index] ?? 6;
};
const getLineDistance = waveType => {
if (typeof lineDistance === 'number') return lineDistance;
if (!enabledWaves.includes(waveType)) return 0.1;
const index = enabledWaves.indexOf(waveType);
return lineDistance[index] ?? 0.1;
};
const topLineCount = enabledWaves.includes('top') ? getLineCount('top') : 0;
const middleLineCount = enabledWaves.includes('middle') ? getLineCount('middle') : 0;
const bottomLineCount = enabledWaves.includes('bottom') ? getLineCount('bottom') : 0;
const topLineDistance = enabledWaves.includes('top') ? getLineDistance('top') * 0.01 : 0.01;
const middleLineDistance = enabledWaves.includes('middle') ? getLineDistance('middle') * 0.01 : 0.01;
const bottomLineDistance = enabledWaves.includes('bottom') ? getLineDistance('bottom') * 0.01 : 0.01;
useEffect(() => {
const container = containerRef.current;
if (!container) return;
let active = true;
const scene = new Scene();
const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
camera.position.z = 1;
const renderer = new WebGLRenderer({ antialias: true, alpha: false });
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.domElement.style.width = '100%';
renderer.domElement.style.height = '100%';
container.appendChild(renderer.domElement);
const uniforms = {
iTime: { value: 0 },
iResolution: { value: new Vector3(1, 1, 1) },
animationSpeed: { value: animationSpeed },
enableTop: { value: enabledWaves.includes('top') },
enableMiddle: { value: enabledWaves.includes('middle') },
enableBottom: { value: enabledWaves.includes('bottom') },
topLineCount: { value: topLineCount },
middleLineCount: { value: middleLineCount },
bottomLineCount: { value: bottomLineCount },
topLineDistance: { value: topLineDistance },
middleLineDistance: { value: middleLineDistance },
bottomLineDistance: { value: bottomLineDistance },
topWavePosition: {
value: new Vector3(topWavePosition?.x ?? 10.0, topWavePosition?.y ?? 0.5, topWavePosition?.rotate ?? -0.4)
},
middleWavePosition: {
value: new Vector3(
middleWavePosition?.x ?? 5.0,
middleWavePosition?.y ?? 0.0,
middleWavePosition?.rotate ?? 0.2
)
},
bottomWavePosition: {
value: new Vector3(
bottomWavePosition?.x ?? 2.0,
bottomWavePosition?.y ?? -0.7,
bottomWavePosition?.rotate ?? 0.4
)
},
iMouse: { value: new Vector2(-1000, -1000) },
interactive: { value: interactive },
bendRadius: { value: bendRadius },
bendStrength: { value: bendStrength },
bendInfluence: { value: 0 },
parallax: { value: parallax },
parallaxStrength: { value: parallaxStrength },
parallaxOffset: { value: new Vector2(0, 0) },
lineGradient: {
value: Array.from({ length: MAX_GRADIENT_STOPS }, () => new Vector3(1, 1, 1))
},
lineGradientCount: { value: 0 }
};
if (linesGradient && linesGradient.length > 0) {
const stops = linesGradient.slice(0, MAX_GRADIENT_STOPS);
uniforms.lineGradientCount.value = stops.length;
stops.forEach((hex, i) => {
const color = hexToVec3(hex);
uniforms.lineGradient.value[i].set(color.x, color.y, color.z);
});
}
const material = new ShaderMaterial({
uniforms,
vertexShader,
fragmentShader
});
const geometry = new PlaneGeometry(2, 2);
const mesh = new Mesh(geometry, material);
scene.add(mesh);
const clock = new Clock();
const setSize = () => {
if (!active) return;
const width = container.clientWidth || 1;
const height = container.clientHeight || 1;
renderer.setSize(width, height, false);
const canvasWidth = renderer.domElement.width;
const canvasHeight = renderer.domElement.height;
uniforms.iResolution.value.set(canvasWidth, canvasHeight, 1);
};
setSize();
const ro =
typeof ResizeObserver !== 'undefined'
? new ResizeObserver(() => {
if (!active) return;
setSize();
})
: null;
if (ro) ro.observe(container);
const handlePointerMove = event => {
const rect = renderer.domElement.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const dpr = renderer.getPixelRatio();
targetMouseRef.current.set(x * dpr, (rect.height - y) * dpr);
targetInfluenceRef.current = 1.0;
if (parallax) {
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const offsetX = (x - centerX) / rect.width;
const offsetY = -(y - centerY) / rect.height;
targetParallaxRef.current.set(offsetX * parallaxStrength, offsetY * parallaxStrength);
}
};
const handlePointerLeave = () => {
targetInfluenceRef.current = 0.0;
};
if (interactive) {
renderer.domElement.addEventListener('pointermove', handlePointerMove);
renderer.domElement.addEventListener('pointerleave', handlePointerLeave);
}
let raf = 0;
const renderLoop = () => {
if (!active) return;
uniforms.iTime.value = clock.getElapsedTime();
if (interactive) {
currentMouseRef.current.lerp(targetMouseRef.current, mouseDamping);
uniforms.iMouse.value.copy(currentMouseRef.current);
currentInfluenceRef.current += (targetInfluenceRef.current - currentInfluenceRef.current) * mouseDamping;
uniforms.bendInfluence.value = currentInfluenceRef.current;
}
if (parallax) {
currentParallaxRef.current.lerp(targetParallaxRef.current, mouseDamping);
uniforms.parallaxOffset.value.copy(currentParallaxRef.current);
}
renderer.render(scene, camera);
raf = requestAnimationFrame(renderLoop);
};
renderLoop();
return () => {
active = false;
cancelAnimationFrame(raf);
if (ro) ro.disconnect();
if (interactive) {
renderer.domElement.removeEventListener('pointermove', handlePointerMove);
renderer.domElement.removeEventListener('pointerleave', handlePointerLeave);
}
geometry.dispose();
material.dispose();
renderer.dispose();
renderer.forceContextLoss();
if (renderer.domElement.parentElement) {
renderer.domElement.parentElement.removeChild(renderer.domElement);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
linesGradient,
enabledWaves,
lineCount,
lineDistance,
topWavePosition,
middleWavePosition,
bottomWavePosition,
animationSpeed,
interactive,
bendRadius,
bendStrength,
mouseDamping,
parallax,
parallaxStrength
]);
return (
<div
ref={containerRef}
className="floating-lines-container"
style={{
mixBlendMode: mixBlendMode
}}
/>
);
}
Component CSS
.floating-lines-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: side rays code: ## Integrate the <SideRays /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: SideRays
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import SideRays from './SideRays';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<SideRays
speed={2.5}
rayColor1="#EAB308"
rayColor2="#96c8ff"
intensity={2}
spread={2}
origin="top-right"
tilt={0}
saturation={1.5}
blend={0.75}
falloff={1.6}
opacity={1.0}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| speed | number | 1.0 | Animation speed of the rays |
| rayColor1 | string | "#ffaa6e" | Color of the first ray layer in hex format |
| rayColor2 | string | "#96c8ff" | Color of the second ray layer in hex format |
| intensity | number | 1.0 | Overall brightness of the rays |
| spread | number | 1.0 | Angular width of the ray fan — higher values create a wider spread between the two ray layers |
| origin | "top-right" | "top-left" | "bottom-right" |
| tilt | number | 0 | Rotation of the ray fan in degrees — positive values tilt clockwise |
| saturation | number | 1.0 | Color saturation of the rays — 0 renders in grayscale, values above 1 boost color |
| blend | number | 0.78 | Balance between the two ray layers — 0 is all ray 1, 1 is all ray 2 |
| falloff | number | 2.0 | How steeply brightness diminishes with distance from the source — higher = tighter glow |
| opacity | number | 1.0 | Overall opacity of the effect |
| className | string | "" | Additional CSS classes to apply to the container |
Full Component Source
import { useRef, useEffect, useState } from 'react';
import { Renderer, Program, Triangle, Mesh } from 'ogl';
import './SideRays.css';
const hexToRgb = hex => {
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return m ? [parseInt(m[1], 16) / 255, parseInt(m[2], 16) / 255, parseInt(m[3], 16) / 255] : [1, 1, 1];
};
const originToFlip = origin => {
switch (origin) {
case 'top-left': return [1, 0];
case 'bottom-right': return [0, 1];
case 'bottom-left': return [1, 1];
default: return [0, 0];
}
};
const SideRays = ({
speed = 2.5,
rayColor1 = '#EAB308',
rayColor2 = '#96c8ff',
intensity = 2,
spread = 2,
origin = 'top-right',
tilt = 0,
saturation = 1.5,
blend = 0.75,
falloff = 1.6,
opacity = 1.0,
className = ''
}) => {
const containerRef = useRef(null);
const uniformsRef = useRef(null);
const rendererRef = useRef(null);
const animationIdRef = useRef(null);
const meshRef = useRef(null);
const cleanupFunctionRef = useRef(null);
const [isVisible, setIsVisible] = useState(false);
const observerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
observerRef.current = new IntersectionObserver(
entries => {
const entry = entries[0];
setIsVisible(entry.isIntersecting);
},
{ threshold: 0.1 }
);
observerRef.current.observe(containerRef.current);
return () => {
if (observerRef.current) {
observerRef.current.disconnect();
observerRef.current = null;
}
};
}, []);
useEffect(() => {
if (!isVisible || !containerRef.current) return;
if (cleanupFunctionRef.current) {
cleanupFunctionRef.current();
cleanupFunctionRef.current = null;
}
const initializeWebGL = async () => {
if (!containerRef.current) return;
await new Promise(resolve => setTimeout(resolve, 10));
if (!containerRef.current) return;
const renderer = new Renderer({
dpr: Math.min(window.devicePixelRatio, 2),
alpha: true
});
rendererRef.current = renderer;
const gl = renderer.gl;
gl.canvas.style.width = '100%';
gl.canvas.style.height = '100%';
while (containerRef.current.firstChild) {
containerRef.current.removeChild(containerRef.current.firstChild);
}
containerRef.current.appendChild(gl.canvas);
const vert = `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}`;
const frag = `precision highp float;
uniform float iTime;
uniform vec2 iResolution;
uniform float iSpeed;
uniform vec3 iRayColor1;
uniform vec3 iRayColor2;
uniform float iIntensity;
uniform float iSpread;
uniform float iFlipX;
uniform float iFlipY;
uniform float iTilt;
uniform float iSaturation;
uniform float iBlend;
uniform float iFalloff;
uniform float iOpacity;
float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord, float seedA, float seedB, float speed) {
vec2 sourceToCoord = coord - raySource;
float cosAngle = dot(normalize(sourceToCoord), rayRefDirection);
return clamp(
(0.45 + 0.15 * sin(cosAngle * seedA + iTime * speed)) +
(0.3 + 0.2 * cos(-cosAngle * seedB + iTime * speed)),
0.0, 1.0) *
clamp((iResolution.x - length(sourceToCoord)) / iResolution.x, 0.5, 1.0);
}
void main() {
vec2 fragCoord = gl_FragCoord.xy;
if (iFlipX > 0.5) fragCoord.x = iResolution.x - fragCoord.x;
if (iFlipY > 0.5) fragCoord.y = iResolution.y - fragCoord.y;
vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);
vec2 rayPos = vec2(iResolution.x * 1.1, -0.5 * iResolution.y);
float tiltRad = iTilt * 3.14159265 / 180.0;
float cs = cos(tiltRad);
float sn = sin(tiltRad);
vec2 rel = coord - rayPos;
vec2 tiltedCoord = vec2(rel.x * cs - rel.y * sn, rel.x * sn + rel.y * cs) + rayPos;
float halfSpread = iSpread * 0.275;
vec2 rayRefDir1 = normalize(vec2(cos(0.785398 + halfSpread), sin(0.785398 + halfSpread)));
vec2 rayRefDir2 = normalize(vec2(cos(0.785398 - halfSpread), sin(0.785398 - halfSpread)));
vec4 rays1 = vec4(iRayColor1, 1.0) * rayStrength(rayPos, rayRefDir1, tiltedCoord, 36.2214, 21.11349, iSpeed);
vec4 rays2 = vec4(iRayColor2, 1.0) * rayStrength(rayPos, rayRefDir2, tiltedCoord, 22.3991, 18.0234, iSpeed * 0.2);
vec4 color = rays1 * (1.0 - iBlend) * 0.9 + rays2 * iBlend * 0.9;
float distanceToLight = length(fragCoord.xy - vec2(rayPos.x, iResolution.y - rayPos.y)) / iResolution.y;
float brightness = iIntensity * 0.4 / pow(max(distanceToLight, 0.001), iFalloff);
color.rgb *= brightness;
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
color.rgb = mix(vec3(gray), color.rgb, iSaturation);
color.a = max(color.r, max(color.g, color.b)) * iOpacity;
gl_FragColor = color;
}`;
const [flipX, flipY] = originToFlip(origin);
const uniforms = {
iTime: { value: 0 },
iResolution: { value: [1, 1] },
iSpeed: { value: speed },
iRayColor1: { value: hexToRgb(rayColor1) },
iRayColor2: { value: hexToRgb(rayColor2) },
iIntensity: { value: intensity },
iSpread: { value: spread },
iFlipX: { value: flipX },
iFlipY: { value: flipY },
iTilt: { value: tilt },
iSaturation: { value: saturation },
iBlend: { value: blend },
iFalloff: { value: falloff },
iOpacity: { value: opacity }
};
uniformsRef.current = uniforms;
const geometry = new Triangle(gl);
const program = new Program(gl, { vertex: vert, fragment: frag, uniforms });
const mesh = new Mesh(gl, { geometry, program });
meshRef.current = mesh;
const updateSize = () => {
if (!containerRef.current || !renderer) return;
renderer.dpr = Math.min(window.devicePixelRatio, 2);
const { clientWidth: w, clientHeight: h } = containerRef.current;
renderer.setSize(w, h);
uniforms.iResolution.value = [w * renderer.dpr, h * renderer.dpr];
};
const loop = t => {
if (!rendererRef.current || !uniformsRef.current || !meshRef.current) return;
uniforms.iTime.value = t * 0.001;
try {
renderer.render({ scene: mesh });
animationIdRef.current = requestAnimationFrame(loop);
} catch (e) {
return;
}
};
window.addEventListener('resize', updateSize);
updateSize();
animationIdRef.current = requestAnimationFrame(loop);
cleanupFunctionRef.current = () => {
if (animationIdRef.current) {
cancelAnimationFrame(animationIdRef.current);
animationIdRef.current = null;
}
window.removeEventListener('resize', updateSize);
if (renderer) {
try {
const loseCtx = renderer.gl.getExtension('WEBGL_lose_context');
if (loseCtx) loseCtx.loseContext();
const canvas = renderer.gl.canvas;
if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);
} catch (e) {}
}
rendererRef.current = null;
uniformsRef.current = null;
meshRef.current = null;
};
};
initializeWebGL();
return () => {
if (cleanupFunctionRef.current) {
cleanupFunctionRef.current();
cleanupFunctionRef.current = null;
}
};
}, [isVisible, speed, rayColor1, rayColor2, intensity, spread, origin, tilt, saturation, blend, falloff, opacity]);
useEffect(() => {
if (!uniformsRef.current) return;
const u = uniformsRef.current;
u.iSpeed.value = speed;
u.iRayColor1.value = hexToRgb(rayColor1);
u.iRayColor2.value = hexToRgb(rayColor2);
u.iIntensity.value = intensity;
u.iSpread.value = spread;
const [flipX, flipY] = originToFlip(origin);
u.iFlipX.value = flipX;
u.iFlipY.value = flipY;
u.iTilt.value = tilt;
u.iSaturation.value = saturation;
u.iBlend.value = blend;
u.iFalloff.value = falloff;
u.iOpacity.value = opacity;
}, [speed, rayColor1, rayColor2, intensity, spread, origin, tilt, saturation, blend, falloff, opacity]);
return <div ref={containerRef} className={`side-rays-container ${className}`.trim()} />;
};
export default SideRays;
Component CSS
.side-rays-container {
width: 100%;
height: 100%;
position: relative;
pointer-events: none;
z-index: 3;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: light rays code: ## Integrate the <LightRays /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: LightRays
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import LightRays from './LightRays';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<LightRays
raysOrigin="top-center"
raysColor="#00ffff"
raysSpeed={1.5}
lightSpread={0.8}
rayLength={1.2}
followMouse={true}
mouseInfluence={0.1}
noiseAmount={0.1}
distortion={0.05}
className="custom-rays"
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| raysOrigin | RaysOrigin | "top-center" | Origin position of the light rays. Options: 'top-center', 'top-left', 'top-right', 'right', 'left', 'bottom-center', 'bottom-right', 'bottom-left' |
| raysColor | string | "#ffffff" | Color of the light rays in hex format |
| raysSpeed | number | 1 | Animation speed of the rays |
| lightSpread | number | 0.5 | How wide the light rays spread. Lower values = tighter rays, higher values = wider spread |
| rayLength | number | 1.0 | Maximum length/reach of the rays |
| pulsating | boolean | false | Enable pulsing animation effect |
| fadeDistance | number | 1.0 | How far rays fade out from origin |
| saturation | number | 1.0 | Color saturation level (0-1) |
| followMouse | boolean | false | Make rays rotate towards the mouse cursor |
| mouseInfluence | number | 0.5 | How much mouse affects rays (0-1) |
| noiseAmount | number | 0.0 | Add noise/grain to rays (0-1) |
| distortion | number | 0.0 | Apply wave distortion to rays |
| className | string | "" | Additional CSS classes to apply to the container |
Full Component Source
import { useRef, useEffect, useState } from 'react';
import { Renderer, Program, Triangle, Mesh } from 'ogl';
import './LightRays.css';
const DEFAULT_COLOR = '#ffffff';
const hexToRgb = hex => {
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return m ? [parseInt(m[1], 16) / 255, parseInt(m[2], 16) / 255, parseInt(m[3], 16) / 255] : [1, 1, 1];
};
const getAnchorAndDir = (origin, w, h) => {
const outside = 0.2;
switch (origin) {
case 'top-left':
return { anchor: [0, -outside * h], dir: [0, 1] };
case 'top-right':
return { anchor: [w, -outside * h], dir: [0, 1] };
case 'left':
return { anchor: [-outside * w, 0.5 * h], dir: [1, 0] };
case 'right':
return { anchor: [(1 + outside) * w, 0.5 * h], dir: [-1, 0] };
case 'bottom-left':
return { anchor: [0, (1 + outside) * h], dir: [0, -1] };
case 'bottom-center':
return { anchor: [0.5 * w, (1 + outside) * h], dir: [0, -1] };
case 'bottom-right':
return { anchor: [w, (1 + outside) * h], dir: [0, -1] };
default: // "top-center"
return { anchor: [0.5 * w, -outside * h], dir: [0, 1] };
}
};
const LightRays = ({
raysOrigin = 'top-center',
raysColor = DEFAULT_COLOR,
raysSpeed = 1,
lightSpread = 1,
rayLength = 2,
pulsating = false,
fadeDistance = 1.0,
saturation = 1.0,
followMouse = true,
mouseInfluence = 0.1,
noiseAmount = 0.0,
distortion = 0.0,
className = ''
}) => {
const containerRef = useRef(null);
const uniformsRef = useRef(null);
const rendererRef = useRef(null);
const mouseRef = useRef({ x: 0.5, y: 0.5 });
const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });
const animationIdRef = useRef(null);
const meshRef = useRef(null);
const cleanupFunctionRef = useRef(null);
const [isVisible, setIsVisible] = useState(false);
const observerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
observerRef.current = new IntersectionObserver(
entries => {
const entry = entries[0];
setIsVisible(entry.isIntersecting);
},
{ threshold: 0.1 }
);
observerRef.current.observe(containerRef.current);
return () => {
if (observerRef.current) {
observerRef.current.disconnect();
observerRef.current = null;
}
};
}, []);
useEffect(() => {
if (!isVisible || !containerRef.current) return;
if (cleanupFunctionRef.current) {
cleanupFunctionRef.current();
cleanupFunctionRef.current = null;
}
const initializeWebGL = async () => {
if (!containerRef.current) return;
await new Promise(resolve => setTimeout(resolve, 10));
if (!containerRef.current) return;
const renderer = new Renderer({
dpr: Math.min(window.devicePixelRatio, 2),
alpha: true
});
rendererRef.current = renderer;
const gl = renderer.gl;
gl.canvas.style.width = '100%';
gl.canvas.style.height = '100%';
while (containerRef.current.firstChild) {
containerRef.current.removeChild(containerRef.current.firstChild);
}
containerRef.current.appendChild(gl.canvas);
const vert = `
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = position * 0.5 + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}`;
const frag = `precision highp float;
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 rayPos;
uniform vec2 rayDir;
uniform vec3 raysColor;
uniform float raysSpeed;
uniform float lightSpread;
uniform float rayLength;
uniform float pulsating;
uniform float fadeDistance;
uniform float saturation;
uniform vec2 mousePos;
uniform float mouseInfluence;
uniform float noiseAmount;
uniform float distortion;
varying vec2 vUv;
float noise(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}
float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord,
float seedA, float seedB, float speed) {
vec2 sourceToCoord = coord - raySource;
vec2 dirNorm = normalize(sourceToCoord);
float cosAngle = dot(dirNorm, rayRefDirection);
float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2;
float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001));
float distance = length(sourceToCoord);
float maxDistance = iResolution.x * rayLength;
float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0);
float fadeFalloff = clamp((iResolution.x * fadeDistance - distance) / (iResolution.x * fadeDistance), 0.5, 1.0);
float pulse = pulsating > 0.5 ? (0.8 + 0.2 * sin(iTime * speed * 3.0)) : 1.0;
float baseStrength = clamp(
(0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) +
(0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)),
0.0, 1.0
);
return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y);
vec2 finalRayDir = rayDir;
if (mouseInfluence > 0.0) {
vec2 mouseScreenPos = mousePos * iResolution.xy;
vec2 mouseDirection = normalize(mouseScreenPos - rayPos);
finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence));
}
vec4 rays1 = vec4(1.0) *
rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349,
1.5 * raysSpeed);
vec4 rays2 = vec4(1.0) *
rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234,
1.1 * raysSpeed);
fragColor = rays1 * 0.5 + rays2 * 0.4;
if (noiseAmount > 0.0) {
float n = noise(coord * 0.01 + iTime * 0.1);
fragColor.rgb *= (1.0 - noiseAmount + noiseAmount * n);
}
float brightness = 1.0 - (coord.y / iResolution.y);
fragColor.x *= 0.1 + brightness * 0.8;
fragColor.y *= 0.3 + brightness * 0.6;
fragColor.z *= 0.5 + brightness * 0.5;
if (saturation != 1.0) {
float gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114));
fragColor.rgb = mix(vec3(gray), fragColor.rgb, saturation);
}
fragColor.rgb *= raysColor;
}
void main() {
vec4 color;
mainImage(color, gl_FragCoord.xy);
gl_FragColor = color;
}`;
const uniforms = {
iTime: { value: 0 },
iResolution: { value: [1, 1] },
rayPos: { value: [0, 0] },
rayDir: { value: [0, 1] },
raysColor: { value: hexToRgb(raysColor) },
raysSpeed: { value: raysSpeed },
lightSpread: { value: lightSpread },
rayLength: { value: rayLength },
pulsating: { value: pulsating ? 1.0 : 0.0 },
fadeDistance: { value: fadeDistance },
saturation: { value: saturation },
mousePos: { value: [0.5, 0.5] },
mouseInfluence: { value: mouseInfluence },
noiseAmount: { value: noiseAmount },
distortion: { value: distortion }
};
uniformsRef.current = uniforms;
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex: vert,
fragment: frag,
uniforms
});
const mesh = new Mesh(gl, { geometry, program });
meshRef.current = mesh;
const updatePlacement = () => {
if (!containerRef.current || !renderer) return;
renderer.dpr = Math.min(window.devicePixelRatio, 2);
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current;
renderer.setSize(wCSS, hCSS);
const dpr = renderer.dpr;
const w = wCSS * dpr;
const h = hCSS * dpr;
uniforms.iResolution.value = [w, h];
const { anchor, dir } = getAnchorAndDir(raysOrigin, w, h);
uniforms.rayPos.value = anchor;
uniforms.rayDir.value = dir;
};
const loop = t => {
if (!rendererRef.current || !uniformsRef.current || !meshRef.current) {
return;
}
uniforms.iTime.value = t * 0.001;
if (followMouse && mouseInfluence > 0.0) {
const smoothing = 0.92;
smoothMouseRef.current.x = smoothMouseRef.current.x * smoothing + mouseRef.current.x * (1 - smoothing);
smoothMouseRef.current.y = smoothMouseRef.current.y * smoothing + mouseRef.current.y * (1 - smoothing);
uniforms.mousePos.value = [smoothMouseRef.current.x, smoothMouseRef.current.y];
}
try {
renderer.render({ scene: mesh });
animationIdRef.current = requestAnimationFrame(loop);
} catch (error) {
console.warn('WebGL rendering error:', error);
return;
}
};
window.addEventListener('resize', updatePlacement);
updatePlacement();
animationIdRef.current = requestAnimationFrame(loop);
cleanupFunctionRef.current = () => {
if (animationIdRef.current) {
cancelAnimationFrame(animationIdRef.current);
animationIdRef.current = null;
}
window.removeEventListener('resize', updatePlacement);
if (renderer) {
try {
const canvas = renderer.gl.canvas;
const loseContextExt = renderer.gl.getExtension('WEBGL_lose_context');
if (loseContextExt) {
loseContextExt.loseContext();
}
if (canvas && canvas.parentNode) {
canvas.parentNode.removeChild(canvas);
}
} catch (error) {
console.warn('Error during WebGL cleanup:', error);
}
}
rendererRef.current = null;
uniformsRef.current = null;
meshRef.current = null;
};
};
initializeWebGL();
return () => {
if (cleanupFunctionRef.current) {
cleanupFunctionRef.current();
cleanupFunctionRef.current = null;
}
};
}, [
isVisible,
raysOrigin,
raysColor,
raysSpeed,
lightSpread,
rayLength,
pulsating,
fadeDistance,
saturation,
followMouse,
mouseInfluence,
noiseAmount,
distortion
]);
useEffect(() => {
if (!uniformsRef.current || !containerRef.current || !rendererRef.current) return;
const u = uniformsRef.current;
const renderer = rendererRef.current;
u.raysColor.value = hexToRgb(raysColor);
u.raysSpeed.value = raysSpeed;
u.lightSpread.value = lightSpread;
u.rayLength.value = rayLength;
u.pulsating.value = pulsating ? 1.0 : 0.0;
u.fadeDistance.value = fadeDistance;
u.saturation.value = saturation;
u.mouseInfluence.value = mouseInfluence;
u.noiseAmount.value = noiseAmount;
u.distortion.value = distortion;
const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current;
const dpr = renderer.dpr;
const { anchor, dir } = getAnchorAndDir(raysOrigin, wCSS * dpr, hCSS * dpr);
u.rayPos.value = anchor;
u.rayDir.value = dir;
}, [
raysColor,
raysSpeed,
lightSpread,
raysOrigin,
rayLength,
pulsating,
fadeDistance,
saturation,
mouseInfluence,
noiseAmount,
distortion
]);
useEffect(() => {
const handleMouseMove = e => {
if (!containerRef.current || !rendererRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
mouseRef.current = { x, y };
};
if (followMouse) {
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}
}, [followMouse]);
return <div ref={containerRef} className={`light-rays-container ${className}`.trim()} />;
};
export default LightRays;
Component CSS
.light-rays-container {
width: 100%;
height: 100%;
position: relative;
pointer-events: none;
z-index: 3;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: pixel blast code: ## Integrate the <PixelBlast /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: PixelBlast
Variant: JavaScript + CSS
Dependencies: three postprocessing
Usage Example
// Component inspired by github.com/zavalit/bayer-dithering-webgl-demo
import PixelBlast from './PixelBlast';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<PixelBlast
variant="circle"
pixelSize={6}
color="#B497CF"
patternScale={3}
patternDensity={1.2}
pixelSizeJitter={0.5}
enableRipples
rippleSpeed={0.4}
rippleThickness={0.12}
rippleIntensityScale={1.5}
liquid
liquidStrength={0.12}
liquidRadius={1.2}
liquidWobbleSpeed={5}
speed={0.6}
edgeFade={0.25}
transparent
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | 'square' | 'circle' | 'triangle' |
| pixelSize | number | 4 | Base pixel size (auto scaled for DPI). |
| color | string | '#B497CF' | Pixel color. |
| patternScale | number | 2 | Noise/pattern scale. |
| patternDensity | number | 1 | Pattern density adjustment. |
| pixelSizeJitter | number | 0 | Random jitter applied to coverage. |
| enableRipples | boolean | true | Enable click ripple waves. |
| rippleSpeed | number | 0.3 | Ripple propagation speed. |
| rippleThickness | number | 0.1 | Ripple ring thickness. |
| rippleIntensityScale | number | 1 | Ripple intensity multiplier. |
| liquid | boolean | false | Enable liquid distortion effect. |
| liquidStrength | number | 0.1 | Liquid distortion strength. |
| liquidRadius | number | 1 | Liquid touch brush radius scale. |
| liquidWobbleSpeed | number | 4.5 | Liquid wobble frequency. |
| speed | number | 0.5 | Animation time scale. |
| edgeFade | number | 0.25 | Edge fade distance (0-1). |
| noiseAmount | number | 0 | Post noise amount. |
| transparent | boolean | true | Transparent background. |
Full Component Source
import { Effect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import './PixelBlast.css';
const createTouchTexture = () => {
const size = 64;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('2D context not available');
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const texture = new THREE.Texture(canvas);
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.generateMipmaps = false;
const trail = [];
let last = null;
const maxAge = 64;
let radius = 0.1 * size;
const speed = 1 / maxAge;
const clear = () => {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
};
const drawPoint = p => {
const pos = { x: p.x * size, y: (1 - p.y) * size };
let intensity = 1;
const easeOutSine = t => Math.sin((t * Math.PI) / 2);
const easeOutQuad = t => -t * (t - 2);
if (p.age < maxAge * 0.3) intensity = easeOutSine(p.age / (maxAge * 0.3));
else intensity = easeOutQuad(1 - (p.age - maxAge * 0.3) / (maxAge * 0.7)) || 0;
intensity *= p.force;
const color = `${((p.vx + 1) / 2) * 255}, ${((p.vy + 1) / 2) * 255}, ${intensity * 255}`;
const offset = size * 5;
ctx.shadowOffsetX = offset;
ctx.shadowOffsetY = offset;
ctx.shadowBlur = radius;
ctx.shadowColor = `rgba(${color},${0.22 * intensity})`;
ctx.beginPath();
ctx.fillStyle = 'rgba(255,0,0,1)';
ctx.arc(pos.x - offset, pos.y - offset, radius, 0, Math.PI * 2);
ctx.fill();
};
const addTouch = norm => {
let force = 0;
let vx = 0;
let vy = 0;
if (last) {
const dx = norm.x - last.x;
const dy = norm.y - last.y;
if (dx === 0 && dy === 0) return;
const dd = dx * dx + dy * dy;
const d = Math.sqrt(dd);
vx = dx / (d || 1);
vy = dy / (d || 1);
force = Math.min(dd * 10000, 1);
}
last = { x: norm.x, y: norm.y };
trail.push({ x: norm.x, y: norm.y, age: 0, force, vx, vy });
};
const update = () => {
clear();
for (let i = trail.length - 1; i >= 0; i--) {
const point = trail[i];
const f = point.force * speed * (1 - point.age / maxAge);
point.x += point.vx * f;
point.y += point.vy * f;
point.age++;
if (point.age > maxAge) trail.splice(i, 1);
}
for (let i = 0; i < trail.length; i++) drawPoint(trail[i]);
texture.needsUpdate = true;
};
return {
canvas,
texture,
addTouch,
update,
set radiusScale(v) {
radius = 0.1 * size * v;
},
get radiusScale() {
return radius / (0.1 * size);
},
size
};
};
const createLiquidEffect = (texture, opts) => {
const fragment = `
uniform sampler2D uTexture;
uniform float uStrength;
uniform float uTime;
uniform float uFreq;
void mainUv(inout vec2 uv) {
vec4 tex = texture2D(uTexture, uv);
float vx = tex.r * 2.0 - 1.0;
float vy = tex.g * 2.0 - 1.0;
float intensity = tex.b;
float wave = 0.5 + 0.5 * sin(uTime * uFreq + intensity * 6.2831853);
float amt = uStrength * intensity * wave;
uv += vec2(vx, vy) * amt;
}
`;
return new Effect('LiquidEffect', fragment, {
uniforms: new Map([
['uTexture', new THREE.Uniform(texture)],
['uStrength', new THREE.Uniform(opts?.strength ?? 0.025)],
['uTime', new THREE.Uniform(0)],
['uFreq', new THREE.Uniform(opts?.freq ?? 4.5)]
])
});
};
const SHAPE_MAP = {
square: 0,
circle: 1,
triangle: 2,
diamond: 3
};
const VERTEX_SRC = `
void main() {
gl_Position = vec4(position, 1.0);
}
`;
const FRAGMENT_SRC = `
precision highp float;
uniform vec3 uColor;
uniform vec2 uResolution;
uniform float uTime;
uniform float uPixelSize;
uniform float uScale;
uniform float uDensity;
uniform float uPixelJitter;
uniform int uEnableRipples;
uniform float uRippleSpeed;
uniform float uRippleThickness;
uniform float uRippleIntensity;
uniform float uEdgeFade;
uniform int uShapeType;
const int SHAPE_SQUARE = 0;
const int SHAPE_CIRCLE = 1;
const int SHAPE_TRIANGLE = 2;
const int SHAPE_DIAMOND = 3;
const int MAX_CLICKS = 10;
uniform vec2 uClickPos [MAX_CLICKS];
uniform float uClickTimes[MAX_CLICKS];
out vec4 fragColor;
float Bayer2(vec2 a) {
a = floor(a);
return fract(a.x / 2. + a.y * a.y * .75);
}
#define Bayer4(a) (Bayer2(.5*(a))*0.25 + Bayer2(a))
#define Bayer8(a) (Bayer4(.5*(a))*0.25 + Bayer2(a))
#define FBM_OCTAVES 5
#define FBM_LACUNARITY 1.25
#define FBM_GAIN 1.0
float hash11(float n){ return fract(sin(n)*43758.5453); }
float vnoise(vec3 p){
vec3 ip = floor(p);
vec3 fp = fract(p);
float n000 = hash11(dot(ip + vec3(0.0,0.0,0.0), vec3(1.0,57.0,113.0)));
float n100 = hash11(dot(ip + vec3(1.0,0.0,0.0), vec3(1.0,57.0,113.0)));
float n010 = hash11(dot(ip + vec3(0.0,1.0,0.0), vec3(1.0,57.0,113.0)));
float n110 = hash11(dot(ip + vec3(1.0,1.0,0.0), vec3(1.0,57.0,113.0)));
float n001 = hash11(dot(ip + vec3(0.0,0.0,1.0), vec3(1.0,57.0,113.0)));
float n101 = hash11(dot(ip + vec3(1.0,0.0,1.0), vec3(1.0,57.0,113.0)));
float n011 = hash11(dot(ip + vec3(0.0,1.0,1.0), vec3(1.0,57.0,113.0)));
float n111 = hash11(dot(ip + vec3(1.0,1.0,1.0), vec3(1.0,57.0,113.0)));
vec3 w = fp*fp*fp*(fp*(fp*6.0-15.0)+10.0);
float x00 = mix(n000, n100, w.x);
float x10 = mix(n010, n110, w.x);
float x01 = mix(n001, n101, w.x);
float x11 = mix(n011, n111, w.x);
float y0 = mix(x00, x10, w.y);
float y1 = mix(x01, x11, w.y);
return mix(y0, y1, w.z) * 2.0 - 1.0;
}
float fbm2(vec2 uv, float t){
vec3 p = vec3(uv * uScale, t);
float amp = 1.0;
float freq = 1.0;
float sum = 1.0;
for (int i = 0; i < FBM_OCTAVES; ++i){
sum += amp * vnoise(p * freq);
freq *= FBM_LACUNARITY;
amp *= FBM_GAIN;
}
return sum * 0.5 + 0.5;
}
float maskCircle(vec2 p, float cov){
float r = sqrt(cov) * .25;
float d = length(p - 0.5) - r;
float aa = 0.5 * fwidth(d);
return cov * (1.0 - smoothstep(-aa, aa, d * 2.0));
}
float maskTriangle(vec2 p, vec2 id, float cov){
bool flip = mod(id.x + id.y, 2.0) > 0.5;
if (flip) p.x = 1.0 - p.x;
float r = sqrt(cov);
float d = p.y - r*(1.0 - p.x);
float aa = fwidth(d);
return cov * clamp(0.5 - d/aa, 0.0, 1.0);
}
float maskDiamond(vec2 p, float cov){
float r = sqrt(cov) * 0.564;
return step(abs(p.x - 0.49) + abs(p.y - 0.49), r);
}
void main(){
float pixelSize = uPixelSize;
vec2 fragCoord = gl_FragCoord.xy - uResolution * .5;
float aspectRatio = uResolution.x / uResolution.y;
vec2 pixelId = floor(fragCoord / pixelSize);
vec2 pixelUV = fract(fragCoord / pixelSize);
float cellPixelSize = 8.0 * pixelSize;
vec2 cellId = floor(fragCoord / cellPixelSize);
vec2 cellCoord = cellId * cellPixelSize;
vec2 uv = cellCoord / uResolution * vec2(aspectRatio, 1.0);
float base = fbm2(uv, uTime * 0.05);
base = base * 0.5 - 0.65;
float feed = base + (uDensity - 0.5) * 0.3;
float speed = uRippleSpeed;
float thickness = uRippleThickness;
const float dampT = 1.0;
const float dampR = 10.0;
if (uEnableRipples == 1) {
for (int i = 0; i < MAX_CLICKS; ++i){
vec2 pos = uClickPos[i];
if (pos.x < 0.0) continue;
float cellPixelSize = 8.0 * pixelSize;
vec2 cuv = (((pos - uResolution * .5 - cellPixelSize * .5) / (uResolution))) * vec2(aspectRatio, 1.0);
float t = max(uTime - uClickTimes[i], 0.0);
float r = distance(uv, cuv);
float waveR = speed * t;
float ring = exp(-pow((r - waveR) / thickness, 2.0));
float atten = exp(-dampT * t) * exp(-dampR * r);
feed = max(feed, ring * atten * uRippleIntensity);
}
}
float bayer = Bayer8(fragCoord / uPixelSize) - 0.5;
float bw = step(0.5, feed + bayer);
float h = fract(sin(dot(floor(fragCoord / uPixelSize), vec2(127.1, 311.7))) * 43758.5453);
float jitterScale = 1.0 + (h - 0.5) * uPixelJitter;
float coverage = bw * jitterScale;
float M;
if (uShapeType == SHAPE_CIRCLE) M = maskCircle (pixelUV, coverage);
else if (uShapeType == SHAPE_TRIANGLE) M = maskTriangle(pixelUV, pixelId, coverage);
else if (uShapeType == SHAPE_DIAMOND) M = maskDiamond(pixelUV, coverage);
else M = coverage;
if (uEdgeFade > 0.0) {
vec2 norm = gl_FragCoord.xy / uResolution;
float edge = min(min(norm.x, norm.y), min(1.0 - norm.x, 1.0 - norm.y));
float fade = smoothstep(0.0, uEdgeFade, edge);
M *= fade;
}
vec3 color = uColor;
// sRGB gamma correction - convert linear to sRGB for accurate color output
vec3 srgbColor = mix(
color * 12.92,
1.055 * pow(color, vec3(1.0 / 2.4)) - 0.055,
step(0.0031308, color)
);
fragColor = vec4(srgbColor, M);
}
`;
const MAX_CLICKS = 10;
const PixelBlast = ({
variant = 'square',
pixelSize = 3,
color = '#B497CF',
className,
style,
antialias = true,
patternScale = 2,
patternDensity = 1,
liquid = false,
liquidStrength = 0.1,
liquidRadius = 1,
pixelSizeJitter = 0,
enableRipples = true,
rippleIntensityScale = 1,
rippleThickness = 0.1,
rippleSpeed = 0.3,
liquidWobbleSpeed = 4.5,
autoPauseOffscreen = true,
speed = 0.5,
transparent = true,
edgeFade = 0.5,
noiseAmount = 0
}) => {
const containerRef = useRef(null);
const visibilityRef = useRef({ visible: true });
const speedRef = useRef(speed);
const threeRef = useRef(null);
const prevConfigRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
speedRef.current = speed;
const needsReinitKeys = ['antialias', 'liquid', 'noiseAmount'];
const cfg = { antialias, liquid, noiseAmount };
let mustReinit = false;
if (!threeRef.current) mustReinit = true;
else if (prevConfigRef.current) {
for (const k of needsReinitKeys)
if (prevConfigRef.current[k] !== cfg[k]) {
mustReinit = true;
break;
}
}
if (mustReinit) {
if (threeRef.current) {
const t = threeRef.current;
t.resizeObserver?.disconnect();
cancelAnimationFrame(t.raf);
t.quad?.geometry.dispose();
t.material.dispose();
t.composer?.dispose();
t.renderer.dispose();
t.renderer.forceContextLoss();
if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);
threeRef.current = null;
}
const canvas = document.createElement('canvas');
const renderer = new THREE.WebGLRenderer({
canvas,
antialias,
alpha: true,
powerPreference: 'high-performance'
});
renderer.domElement.style.width = '100%';
renderer.domElement.style.height = '100%';
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
container.appendChild(renderer.domElement);
if (transparent) renderer.setClearAlpha(0);
else renderer.setClearColor(0x000000, 1);
const uniforms = {
uResolution: { value: new THREE.Vector2(0, 0) },
uTime: { value: 0 },
uColor: { value: new THREE.Color(color) },
uClickPos: {
value: Array.from({ length: MAX_CLICKS }, () => new THREE.Vector2(-1, -1))
},
uClickTimes: { value: new Float32Array(MAX_CLICKS) },
uShapeType: { value: SHAPE_MAP[variant] ?? 0 },
uPixelSize: { value: pixelSize * renderer.getPixelRatio() },
uScale: { value: patternScale },
uDensity: { value: patternDensity },
uPixelJitter: { value: pixelSizeJitter },
uEnableRipples: { value: enableRipples ? 1 : 0 },
uRippleSpeed: { value: rippleSpeed },
uRippleThickness: { value: rippleThickness },
uRippleIntensity: { value: rippleIntensityScale },
uEdgeFade: { value: edgeFade }
};
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const material = new THREE.ShaderMaterial({
vertexShader: VERTEX_SRC,
fragmentShader: FRAGMENT_SRC,
uniforms,
transparent: true,
depthTest: false,
depthWrite: false,
glslVersion: THREE.GLSL3
});
const quadGeom = new THREE.PlaneGeometry(2, 2);
const quad = new THREE.Mesh(quadGeom, material);
scene.add(quad);
const clock = new THREE.Clock();
const setSize = () => {
const w = container.clientWidth || 1;
const h = container.clientHeight || 1;
renderer.setSize(w, h, false);
uniforms.uResolution.value.set(renderer.domElement.width, renderer.domElement.height);
if (threeRef.current?.composer)
threeRef.current.composer.setSize(renderer.domElement.width, renderer.domElement.height);
uniforms.uPixelSize.value = pixelSize * renderer.getPixelRatio();
};
setSize();
const ro = new ResizeObserver(setSize);
ro.observe(container);
const randomFloat = () => {
if (typeof window !== 'undefined' && window.crypto?.getRandomValues) {
const u32 = new Uint32Array(1);
window.crypto.getRandomValues(u32);
return u32[0] / 0xffffffff;
}
return Math.random();
};
const timeOffset = randomFloat() * 1000;
let composer;
let touch;
let liquidEffect;
if (liquid) {
touch = createTouchTexture();
touch.radiusScale = liquidRadius;
composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
liquidEffect = createLiquidEffect(touch.texture, {
strength: liquidStrength,
freq: liquidWobbleSpeed
});
const effectPass = new EffectPass(camera, liquidEffect);
effectPass.renderToScreen = true;
composer.addPass(renderPass);
composer.addPass(effectPass);
}
if (noiseAmount > 0) {
if (!composer) {
composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
}
const noiseEffect = new Effect(
'NoiseEffect',
`uniform float uTime; uniform float uAmount; float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453);} void mainUv(inout vec2 uv){} void mainImage(const in vec4 inputColor,const in vec2 uv,out vec4 outputColor){ float n=hash(floor(uv*vec2(1920.0,1080.0))+floor(uTime*60.0)); float g=(n-0.5)*uAmount; outputColor=inputColor+vec4(vec3(g),0.0);} `,
{
uniforms: new Map([
['uTime', new THREE.Uniform(0)],
['uAmount', new THREE.Uniform(noiseAmount)]
])
}
);
const noisePass = new EffectPass(camera, noiseEffect);
noisePass.renderToScreen = true;
if (composer && composer.passes.length > 0) composer.passes.forEach(p => (p.renderToScreen = false));
composer.addPass(noisePass);
}
if (composer) composer.setSize(renderer.domElement.width, renderer.domElement.height);
const mapToPixels = e => {
const rect = renderer.domElement.getBoundingClientRect();
const scaleX = renderer.domElement.width / rect.width;
const scaleY = renderer.domElement.height / rect.height;
const fx = (e.clientX - rect.left) * scaleX;
const fy = (rect.height - (e.clientY - rect.top)) * scaleY;
return {
fx,
fy,
w: renderer.domElement.width,
h: renderer.domElement.height
};
};
const onPointerDown = e => {
const { fx, fy } = mapToPixels(e);
const ix = threeRef.current?.clickIx ?? 0;
uniforms.uClickPos.value[ix].set(fx, fy);
uniforms.uClickTimes.value[ix] = uniforms.uTime.value;
if (threeRef.current) threeRef.current.clickIx = (ix + 1) % MAX_CLICKS;
};
const onPointerMove = e => {
if (!touch) return;
const { fx, fy, w, h } = mapToPixels(e);
touch.addTouch({ x: fx / w, y: fy / h });
};
renderer.domElement.addEventListener('pointerdown', onPointerDown, {
passive: true
});
renderer.domElement.addEventListener('pointermove', onPointerMove, {
passive: true
});
let raf = 0;
const animate = () => {
if (autoPauseOffscreen && !visibilityRef.current.visible) {
raf = requestAnimationFrame(animate);
return;
}
uniforms.uTime.value = timeOffset + clock.getElapsedTime() * speedRef.current;
if (liquidEffect) liquidEffect.uniforms.get('uTime').value = uniforms.uTime.value;
if (composer) {
if (touch) touch.update();
composer.passes.forEach(p => {
const effs = p.effects;
if (effs)
effs.forEach(eff => {
const u = eff.uniforms?.get('uTime');
if (u) u.value = uniforms.uTime.value;
});
});
composer.render();
} else renderer.render(scene, camera);
raf = requestAnimationFrame(animate);
};
raf = requestAnimationFrame(animate);
threeRef.current = {
renderer,
scene,
camera,
material,
clock,
clickIx: 0,
uniforms,
resizeObserver: ro,
raf,
quad,
timeOffset,
composer,
touch,
liquidEffect
};
} else {
const t = threeRef.current;
t.uniforms.uShapeType.value = SHAPE_MAP[variant] ?? 0;
t.uniforms.uPixelSize.value = pixelSize * t.renderer.getPixelRatio();
t.uniforms.uColor.value.set(color);
t.uniforms.uScale.value = patternScale;
t.uniforms.uDensity.value = patternDensity;
t.uniforms.uPixelJitter.value = pixelSizeJitter;
t.uniforms.uEnableRipples.value = enableRipples ? 1 : 0;
t.uniforms.uRippleIntensity.value = rippleIntensityScale;
t.uniforms.uRippleThickness.value = rippleThickness;
t.uniforms.uRippleSpeed.value = rippleSpeed;
t.uniforms.uEdgeFade.value = edgeFade;
if (transparent) t.renderer.setClearAlpha(0);
else t.renderer.setClearColor(0x000000, 1);
if (t.liquidEffect) {
const uStrength = t.liquidEffect;
if (uStrength) uStrength.value = liquidStrength;
const uFreq = t.liquidEffect.uniforms.get('uFreq');
if (uFreq) uFreq.value = liquidWobbleSpeed;
}
if (t.touch) t.touch.radiusScale = liquidRadius;
}
prevConfigRef.current = cfg;
return () => {
if (threeRef.current && mustReinit) return;
if (!threeRef.current) return;
const t = threeRef.current;
t.resizeObserver?.disconnect();
cancelAnimationFrame(t.raf);
t.quad?.geometry.dispose();
t.material.dispose();
t.composer?.dispose();
t.renderer.dispose();
t.renderer.forceContextLoss();
if (t.renderer.domElement.parentElement === container) container.removeChild(t.renderer.domElement);
threeRef.current = null;
};
}, [
antialias,
liquid,
noiseAmount,
pixelSize,
patternScale,
patternDensity,
enableRipples,
rippleIntensityScale,
rippleThickness,
rippleSpeed,
pixelSizeJitter,
edgeFade,
transparent,
liquidStrength,
liquidRadius,
liquidWobbleSpeed,
autoPauseOffscreen,
variant,
color,
speed
]);
return (
<div
ref={containerRef}
className={`pixel-blast-container ${className ?? ''}`}
style={style}
aria-label="PixelBlast interactive background"
/>
);
};
export default PixelBlast;
Component CSS
.pixel-blast-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: color bends code: ## Integrate the <ColorBends /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: ColorBends
Variant: JavaScript + CSS
Dependencies: three
Usage Example
import ColorBends from './ColorBends';
<ColorBends
colors={["#ff5c7a", "#8a5cff", "#00ffd1"]}
rotation={90}
speed={0.2}
scale={1}
frequency={1}
warpStrength={1}
mouseInfluence={1}
noise={0.15}
parallax={0.5}
iterations={1}
intensity={1.5}
bandWidth={6}
transparent
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| rotation | number | 90 | Base rotation angle in degrees. |
| autoRotate | number | 0 | Automatic rotation speed in degrees/sec. |
| speed | number | 0.2 | Animation time scale of the shader. |
| colors | string[] | [] | Palette of up to 8 hex colors used to blend the bends. |
| transparent | boolean | true | Whether the background is transparent (uses alpha). |
| scale | number | 1 | Zoom factor of the pattern. |
| frequency | number | 1 | Wave frequency used in the pattern. |
| warpStrength | number | 1 | Amount of warping/distortion applied to waves. |
| mouseInfluence | number | 1 | How strongly the waves react to pointer movement. |
| parallax | number | 0.5 | Parallax factor shifting content with pointer. |
| noise | number | 0.15 | Adds subtle grain. 0 disables noise. |
| iterations | number | 1 | Number of extra warp passes (1-5). Higher values create more complex patterns. |
| intensity | number | 1.5 | Brightness multiplier for the final color output. |
| bandWidth | number | 6 | Controls the width/falloff of each color band. |
| className | string | '' | Additional CSS classes for the container. |
| style | React.CSSProperties | {} | Inline styles for the container. |
Full Component Source
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import './ColorBends.css';
const MAX_COLORS = 8;
const frag = `
#define MAX_COLORS ${MAX_COLORS}
uniform vec2 uCanvas;
uniform float uTime;
uniform float uSpeed;
uniform vec2 uRot;
uniform int uColorCount;
uniform vec3 uColors[MAX_COLORS];
uniform int uTransparent;
uniform float uScale;
uniform float uFrequency;
uniform float uWarpStrength;
uniform vec2 uPointer; // in NDC [-1,1]
uniform float uMouseInfluence;
uniform float uParallax;
uniform float uNoise;
uniform int uIterations;
uniform float uIntensity;
uniform float uBandWidth;
varying vec2 vUv;
void main() {
float t = uTime * uSpeed;
vec2 p = vUv * 2.0 - 1.0;
p += uPointer * uParallax * 0.1;
vec2 rp = vec2(p.x * uRot.x - p.y * uRot.y, p.x * uRot.y + p.y * uRot.x);
vec2 q = vec2(rp.x * (uCanvas.x / uCanvas.y), rp.y);
q /= max(uScale, 0.0001);
q /= 0.5 + 0.2 * dot(q, q);
q += 0.2 * cos(t) - 7.56;
vec2 toward = (uPointer - rp);
q += toward * uMouseInfluence * 0.2;
for (int j = 0; j < 5; j++) {
if (j >= uIterations - 1) break;
vec2 rr = sin(1.5 * (q.yx * uFrequency) + 2.0 * cos(q * uFrequency));
q += (rr - q) * 0.15;
}
vec3 col = vec3(0.0);
float a = 1.0;
if (uColorCount > 0) {
vec2 s = q;
vec3 sumCol = vec3(0.0);
float cover = 0.0;
for (int i = 0; i < MAX_COLORS; ++i) {
if (i >= uColorCount) break;
s -= 0.01;
vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));
float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(i)) / 4.0);
float kBelow = clamp(uWarpStrength, 0.0, 1.0);
float kMix = pow(kBelow, 0.3); // strong response across 0..1
float gain = 1.0 + max(uWarpStrength - 1.0, 0.0); // allow >1 to amplify displacement
vec2 disp = (r - s) * kBelow;
vec2 warped = s + disp * gain;
float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(i)) / 4.0);
float m = mix(m0, m1, kMix);
float w = 1.0 - exp(-uBandWidth / exp(uBandWidth * m));
sumCol += uColors[i] * w;
cover = max(cover, w);
}
col = clamp(sumCol, 0.0, 1.0);
a = uTransparent > 0 ? cover : 1.0;
} else {
vec2 s = q;
for (int k = 0; k < 3; ++k) {
s -= 0.01;
vec2 r = sin(1.5 * (s.yx * uFrequency) + 2.0 * cos(s * uFrequency));
float m0 = length(r + sin(5.0 * r.y * uFrequency - 3.0 * t + float(k)) / 4.0);
float kBelow = clamp(uWarpStrength, 0.0, 1.0);
float kMix = pow(kBelow, 0.3);
float gain = 1.0 + max(uWarpStrength - 1.0, 0.0);
vec2 disp = (r - s) * kBelow;
vec2 warped = s + disp * gain;
float m1 = length(warped + sin(5.0 * warped.y * uFrequency - 3.0 * t + float(k)) / 4.0);
float m = mix(m0, m1, kMix);
col[k] = 1.0 - exp(-uBandWidth / exp(uBandWidth * m));
}
a = uTransparent > 0 ? max(max(col.r, col.g), col.b) : 1.0;
}
col *= uIntensity;
if (uNoise > 0.0001) {
float n = fract(sin(dot(gl_FragCoord.xy + vec2(uTime), vec2(12.9898, 78.233))) * 43758.5453123);
col += (n - 0.5) * uNoise;
col = clamp(col, 0.0, 1.0);
}
vec3 rgb = (uTransparent > 0) ? col * a : col;
gl_FragColor = vec4(rgb, a);
}
`;
const vert = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
`;
export default function ColorBends({
className,
style,
rotation = 90,
speed = 0.2,
colors = [],
transparent = true,
autoRotate = 0,
scale = 1,
frequency = 1,
warpStrength = 1,
mouseInfluence = 1,
parallax = 0.5,
noise = 0.15,
iterations = 1,
intensity = 1.5,
bandWidth = 6
}) {
const containerRef = useRef(null);
const rendererRef = useRef(null);
const rafRef = useRef(null);
const materialRef = useRef(null);
const resizeObserverRef = useRef(null);
const rotationRef = useRef(rotation);
const autoRotateRef = useRef(autoRotate);
const pointerTargetRef = useRef(new THREE.Vector2(0, 0));
const pointerCurrentRef = useRef(new THREE.Vector2(0, 0));
const pointerSmoothRef = useRef(8);
useEffect(() => {
const container = containerRef.current;
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const geometry = new THREE.PlaneGeometry(2, 2);
const uColorsArray = Array.from({ length: MAX_COLORS }, () => new THREE.Vector3(0, 0, 0));
const material = new THREE.ShaderMaterial({
vertexShader: vert,
fragmentShader: frag,
uniforms: {
uCanvas: { value: new THREE.Vector2(1, 1) },
uTime: { value: 0 },
uSpeed: { value: speed },
uRot: { value: new THREE.Vector2(1, 0) },
uColorCount: { value: 0 },
uColors: { value: uColorsArray },
uTransparent: { value: transparent ? 1 : 0 },
uScale: { value: scale },
uFrequency: { value: frequency },
uWarpStrength: { value: warpStrength },
uPointer: { value: new THREE.Vector2(0, 0) },
uMouseInfluence: { value: mouseInfluence },
uParallax: { value: parallax },
uNoise: { value: noise },
uIterations: { value: iterations },
uIntensity: { value: intensity },
uBandWidth: { value: bandWidth }
},
premultipliedAlpha: true,
transparent: true
});
materialRef.current = material;
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
const renderer = new THREE.WebGLRenderer({
antialias: false,
powerPreference: 'high-performance',
alpha: true
});
rendererRef.current = renderer;
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.setClearColor(0x000000, transparent ? 0 : 1);
renderer.domElement.style.width = '100%';
renderer.domElement.style.height = '100%';
renderer.domElement.style.display = 'block';
container.appendChild(renderer.domElement);
const clock = new THREE.Clock();
const handleResize = () => {
const w = container.clientWidth || 1;
const h = container.clientHeight || 1;
renderer.setSize(w, h, false);
material.uniforms.uCanvas.value.set(w, h);
};
handleResize();
if ('ResizeObserver' in window) {
const ro = new ResizeObserver(handleResize);
ro.observe(container);
resizeObserverRef.current = ro;
} else {
window.addEventListener('resize', handleResize);
}
const loop = () => {
const dt = clock.getDelta();
const elapsed = clock.elapsedTime;
material.uniforms.uTime.value = elapsed;
const deg = (rotationRef.current % 360) + autoRotateRef.current * elapsed;
const rad = (deg * Math.PI) / 180;
const c = Math.cos(rad);
const s = Math.sin(rad);
material.uniforms.uRot.value.set(c, s);
const cur = pointerCurrentRef.current;
const tgt = pointerTargetRef.current;
const amt = Math.min(1, dt * pointerSmoothRef.current);
cur.lerp(tgt, amt);
material.uniforms.uPointer.value.copy(cur);
renderer.render(scene, camera);
rafRef.current = requestAnimationFrame(loop);
};
rafRef.current = requestAnimationFrame(loop);
return () => {
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
if (resizeObserverRef.current) resizeObserverRef.current.disconnect();
else window.removeEventListener('resize', handleResize);
geometry.dispose();
material.dispose();
renderer.dispose();
renderer.forceContextLoss();
if (renderer.domElement && renderer.domElement.parentElement === container) {
container.removeChild(renderer.domElement);
}
};
}, [bandWidth, frequency, intensity, iterations, mouseInfluence, noise, parallax, scale, speed, transparent, warpStrength]);
useEffect(() => {
const material = materialRef.current;
const renderer = rendererRef.current;
if (!material) return;
rotationRef.current = rotation;
autoRotateRef.current = autoRotate;
material.uniforms.uSpeed.value = speed;
material.uniforms.uScale.value = scale;
material.uniforms.uFrequency.value = frequency;
material.uniforms.uWarpStrength.value = warpStrength;
material.uniforms.uMouseInfluence.value = mouseInfluence;
material.uniforms.uParallax.value = parallax;
material.uniforms.uNoise.value = noise;
material.uniforms.uIterations.value = iterations;
material.uniforms.uIntensity.value = intensity;
material.uniforms.uBandWidth.value = bandWidth;
const toVec3 = hex => {
const h = hex.replace('#', '').trim();
const v =
h.length === 3
? [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)]
: [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
return new THREE.Vector3(v[0] / 255, v[1] / 255, v[2] / 255);
};
const arr = (colors || []).filter(Boolean).slice(0, MAX_COLORS).map(toVec3);
for (let i = 0; i < MAX_COLORS; i++) {
const vec = material.uniforms.uColors.value[i];
if (i < arr.length) vec.copy(arr[i]);
else vec.set(0, 0, 0);
}
material.uniforms.uColorCount.value = arr.length;
material.uniforms.uTransparent.value = transparent ? 1 : 0;
if (renderer) renderer.setClearColor(0x000000, transparent ? 0 : 1);
}, [
rotation,
autoRotate,
speed,
scale,
frequency,
warpStrength,
mouseInfluence,
parallax,
noise,
iterations,
intensity,
bandWidth,
colors,
transparent
]);
useEffect(() => {
const material = materialRef.current;
const container = containerRef.current;
if (!material || !container) return;
const handlePointerMove = e => {
const rect = container.getBoundingClientRect();
const x = ((e.clientX - rect.left) / (rect.width || 1)) * 2 - 1;
const y = -(((e.clientY - rect.top) / (rect.height || 1)) * 2 - 1);
pointerTargetRef.current.set(x, y);
};
container.addEventListener('pointermove', handlePointerMove);
return () => {
container.removeEventListener('pointermove', handlePointerMove);
};
}, []);
return <div ref={containerRef} className={`color-bends-container ${className}`} style={style} />;
}
Component CSS
.color-bends-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: evil eye code: ## Integrate the <EvilEye /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: EvilEye
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import EvilEye from './EvilEye';
<EvilEye
eyeColor="#FF6F37"
intensity={1.5}
pupilSize={0.6}
irisWidth={0.25}
glowIntensity={0.35}
scale={0.8}
noiseScale={1.0}
pupilFollow={1.0}
flameSpeed={1.0}
backgroundColor="#000000"
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| eyeColor | string | "#FF6F37" | Primary eye color in HEX format. |
| intensity | number | 1.5 | Brightness / HDR intensity of the eye color. |
| pupilSize | number | 0.6 | Size and darkness of the pupil slit. |
| irisWidth | number | 0.25 | Width of the main iris ring. |
| glowIntensity | number | 0.35 | Strength of the outer eye glow. |
| scale | number | 0.8 | Zoom level of the eye. Values > 1 zoom in, < 1 zoom out. |
| noiseScale | number | 1.0 | Scale of the fire/noise texture sampling. |
| pupilFollow | number | 1.0 | Intensity of pupil cursor tracking. 0 disables it. |
| flameSpeed | number | 1.0 | Independent flame flicker animation speed. |
| backgroundColor | string | "#000000" | Background color in HEX format. |
Full Component Source
import { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';
import { useEffect, useRef } from 'react';
import './EvilEye.css';
function hexToVec3(hex) {
const h = hex.replace('#', '');
return [
parseInt(h.slice(0, 2), 16) / 255,
parseInt(h.slice(2, 4), 16) / 255,
parseInt(h.slice(4, 6), 16) / 255
];
}
function generateNoiseTexture(size = 256) {
const data = new Uint8Array(size * size * 4);
function hash(x, y, s) {
let n = x * 374761393 + y * 668265263 + s * 1274126177;
n = Math.imul(n ^ (n >>> 13), 1274126177);
return ((n ^ (n >>> 16)) >>> 0) / 4294967296;
}
function noise(px, py, freq, seed) {
const fx = (px / size) * freq;
const fy = (py / size) * freq;
const ix = Math.floor(fx);
const iy = Math.floor(fy);
const tx = fx - ix;
const ty = fy - iy;
const w = freq | 0;
const v00 = hash(((ix % w) + w) % w, ((iy % w) + w) % w, seed);
const v10 = hash((((ix + 1) % w) + w) % w, ((iy % w) + w) % w, seed);
const v01 = hash(((ix % w) + w) % w, (((iy + 1) % w) + w) % w, seed);
const v11 = hash((((ix + 1) % w) + w) % w, (((iy + 1) % w) + w) % w, seed);
return v00 * (1 - tx) * (1 - ty) + v10 * tx * (1 - ty) + v01 * (1 - tx) * ty + v11 * tx * ty;
}
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
let v = 0;
let amp = 0.4;
let totalAmp = 0;
for (let o = 0; o < 8; o++) {
const f = 32 * (1 << o);
v += amp * noise(x, y, f, o * 31);
totalAmp += amp;
amp *= 0.65;
}
v /= totalAmp;
v = (v - 0.5) * 2.2 + 0.5;
v = Math.max(0, Math.min(1, v));
const val = Math.round(v * 255);
const i = (y * size + x) * 4;
data[i] = val;
data[i + 1] = val;
data[i + 2] = val;
data[i + 3] = 255;
}
}
return data;
}
const vertexShader = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
const fragmentShader = `
precision highp float;
uniform float uTime;
uniform vec3 uResolution;
uniform sampler2D uNoiseTexture;
uniform float uPupilSize;
uniform float uIrisWidth;
uniform float uGlowIntensity;
uniform float uIntensity;
uniform float uScale;
uniform float uNoiseScale;
uniform vec2 uMouse;
uniform float uPupilFollow;
uniform float uFlameSpeed;
uniform vec3 uEyeColor;
uniform vec3 uBgColor;
void main() {
vec2 uv = (gl_FragCoord.xy * 2.0 - uResolution.xy) / uResolution.y;
uv /= uScale;
float ft = uTime * uFlameSpeed;
float polarRadius = length(uv) * 2.0;
float polarAngle = (2.0 * atan(uv.x, uv.y)) / 6.28 * 0.3;
vec2 polarUv = vec2(polarRadius, polarAngle);
vec4 noiseA = texture2D(uNoiseTexture, polarUv * vec2(0.2, 7.0) * uNoiseScale + vec2(-ft * 0.1, 0.0));
vec4 noiseB = texture2D(uNoiseTexture, polarUv * vec2(0.3, 4.0) * uNoiseScale + vec2(-ft * 0.2, 0.0));
vec4 noiseC = texture2D(uNoiseTexture, polarUv * vec2(0.1, 5.0) * uNoiseScale + vec2(-ft * 0.1, 0.0));
float distanceMask = 1.0 - length(uv);
// Inner ring
float innerRing = clamp(-1.0 * ((distanceMask - 0.7) / uIrisWidth), 0.0, 1.0);
innerRing = (innerRing * distanceMask - 0.2) / 0.28;
innerRing += noiseA.r - 0.5;
innerRing *= 1.3;
innerRing = clamp(innerRing, 0.0, 1.0);
float outerRing = clamp(-1.0 * ((distanceMask - 0.5) / 0.2), 0.0, 1.0);
outerRing = (outerRing * distanceMask - 0.1) / 0.38;
outerRing += noiseC.r - 0.5;
outerRing *= 1.3;
outerRing = clamp(outerRing, 0.0, 1.0);
innerRing += outerRing;
// Inner eye
float innerEye = distanceMask - 0.1 * 2.0;
innerEye *= noiseB.r * 2.0;
// Pupil with cursor tracking
vec2 pupilOffset = uMouse * uPupilFollow * 0.12;
vec2 pupilUv = uv - pupilOffset;
float pupil = 1.0 - length(pupilUv * vec2(9.0, 2.3));
pupil *= uPupilSize;
pupil = clamp(pupil, 0.0, 1.0);
pupil /= 0.35;
// Outer eye
float outerEyeGlow = 1.0 - length(uv * vec2(0.5, 1.5));
outerEyeGlow = clamp(outerEyeGlow + 0.5, 0.0, 1.0);
outerEyeGlow += noiseC.r - 0.5;
float outerBgGlow = outerEyeGlow;
outerEyeGlow = pow(outerEyeGlow, 2.0);
outerEyeGlow += distanceMask;
outerEyeGlow *= uGlowIntensity;
outerEyeGlow = clamp(outerEyeGlow, 0.0, 1.0);
outerEyeGlow *= pow(1.0 - distanceMask, 2.0) * 2.5;
// Outer eye bg glow
outerBgGlow += distanceMask;
outerBgGlow = pow(outerBgGlow, 0.5);
outerBgGlow *= 0.15;
vec3 color = uEyeColor * uIntensity * clamp(max(innerRing + innerEye, outerEyeGlow + outerBgGlow) - pupil, 0.0, 3.0);
color += uBgColor;
gl_FragColor = vec4(color, 1.0);
}
`;
export default function EvilEye({
eyeColor = '#FF6F37',
intensity = 1.5,
pupilSize = 0.6,
irisWidth = 0.25,
glowIntensity = 0.35,
scale = 0.8,
noiseScale = 1.0,
pupilFollow = 1.0,
flameSpeed = 1.0,
backgroundColor = '#000000'
}) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const container = containerRef.current;
const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
const noiseData = generateNoiseTexture(256);
const noiseTexture = new Texture(gl, {
image: noiseData,
width: 256,
height: 256,
generateMipmaps: false,
flipY: false,
});
noiseTexture.minFilter = gl.LINEAR;
noiseTexture.magFilter = gl.LINEAR;
noiseTexture.wrapS = gl.REPEAT;
noiseTexture.wrapT = gl.REPEAT;
const mouse = { x: 0, y: 0, tx: 0, ty: 0 };
function onMouseMove(e) {
const rect = container.getBoundingClientRect();
mouse.tx = ((e.clientX - rect.left) / rect.width) * 2 - 1;
mouse.ty = -(((e.clientY - rect.top) / rect.height) * 2 - 1);
}
function onMouseLeave() {
mouse.tx = 0;
mouse.ty = 0;
}
container.addEventListener('mousemove', onMouseMove);
container.addEventListener('mouseleave', onMouseLeave);
let program;
function resize() {
renderer.setSize(container.offsetWidth, container.offsetHeight);
if (program) {
program.uniforms.uResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];
}
}
window.addEventListener('resize', resize);
resize();
const geometry = new Triangle(gl);
program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
uTime: { value: 0 },
uResolution: { value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height] },
uNoiseTexture: { value: noiseTexture },
uPupilSize: { value: pupilSize },
uIrisWidth: { value: irisWidth },
uGlowIntensity: { value: glowIntensity },
uIntensity: { value: intensity },
uScale: { value: scale },
uNoiseScale: { value: noiseScale },
uMouse: { value: [0, 0] },
uPupilFollow: { value: pupilFollow },
uFlameSpeed: { value: flameSpeed },
uEyeColor: { value: hexToVec3(eyeColor) },
uBgColor: { value: hexToVec3(backgroundColor) }
}
});
const mesh = new Mesh(gl, { geometry, program });
container.appendChild(gl.canvas);
let animationFrameId;
function update(time) {
animationFrameId = requestAnimationFrame(update);
mouse.x += (mouse.tx - mouse.x) * 0.05;
mouse.y += (mouse.ty - mouse.y) * 0.05;
program.uniforms.uMouse.value = [mouse.x, mouse.y];
program.uniforms.uTime.value = time * 0.001;
renderer.render({ scene: mesh });
}
animationFrameId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animationFrameId);
window.removeEventListener('resize', resize);
container.removeEventListener('mousemove', onMouseMove);
container.removeEventListener('mouseleave', onMouseLeave);
container.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, [eyeColor, intensity, pupilSize, irisWidth, glowIntensity, scale, noiseScale, pupilFollow, flameSpeed, backgroundColor]);
return <div ref={containerRef} className="evil-eye-container" />;
}
Component CSS
.evil-eye-container {
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: line waves code: ## Integrate the <LineWaves /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: LineWaves
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import LineWaves from './LineWaves';
<LineWaves
speed={0.3}
innerLineCount={32}
outerLineCount={36}
warpIntensity={1.0}
rotation={-45}
edgeFadeWidth={0.0}
colorCycleSpeed={1.0}
brightness={0.2}
color1="#ffffff"
color2="#ffffff"
color3="#ffffff"
enableMouseInteraction={true}
mouseInfluence={2.0}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| speed | number | 0.3 | Overall animation speed multiplier. |
| innerLineCount | number | 32.0 | Number of lines in the inner (center) wave region. |
| outerLineCount | number | 36.0 | Number of lines in the outer (edge) wave region. |
| warpIntensity | number | 1.0 | Intensity of the wave distortion effect. |
| rotation | number | -45 | Rotation of the wave pattern in degrees. |
| edgeFadeWidth | number | 0.0 | Width of the edge fade between inner and outer regions. |
| colorCycleSpeed | number | 1.0 | Speed of color cycling animation. |
| brightness | number | 0.2 | Overall brightness multiplier. |
| color1 | string | "#ffffff" | First color channel in HEX format. |
| color2 | string | "#ffffff" | Second color channel in HEX format. |
| color3 | string | "#ffffff" | Third color channel in HEX format. |
| enableMouseInteraction | boolean | true | Enable cursor-reactive wave distortion. |
| mouseInfluence | number | 2.0 | Strength of mouse influence on the wave pattern. |
Full Component Source
import { Renderer, Program, Mesh, Triangle } from 'ogl';
import { useEffect, useRef } from 'react';
import './LineWaves.css';
function hexToVec3(hex) {
const h = hex.replace('#', '');
return [
parseInt(h.slice(0, 2), 16) / 255,
parseInt(h.slice(2, 4), 16) / 255,
parseInt(h.slice(4, 6), 16) / 255
];
}
const vertexShader = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
const fragmentShader = `
precision highp float;
uniform float uTime;
uniform vec3 uResolution;
uniform float uSpeed;
uniform float uInnerLines;
uniform float uOuterLines;
uniform float uWarpIntensity;
uniform float uRotation;
uniform float uEdgeFadeWidth;
uniform float uColorCycleSpeed;
uniform float uBrightness;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform vec3 uColor3;
uniform vec2 uMouse;
uniform float uMouseInfluence;
uniform bool uEnableMouse;
#define HALF_PI 1.5707963
float hashF(float n) {
return fract(sin(n * 127.1) * 43758.5453123);
}
float smoothNoise(float x) {
float i = floor(x);
float f = fract(x);
float u = f * f * (3.0 - 2.0 * f);
return mix(hashF(i), hashF(i + 1.0), u);
}
float displaceA(float coord, float t) {
float result = sin(coord * 2.123) * 0.2;
result += sin(coord * 3.234 + t * 4.345) * 0.1;
result += sin(coord * 0.589 + t * 0.934) * 0.5;
return result;
}
float displaceB(float coord, float t) {
float result = sin(coord * 1.345) * 0.3;
result += sin(coord * 2.734 + t * 3.345) * 0.2;
result += sin(coord * 0.189 + t * 0.934) * 0.3;
return result;
}
vec2 rotate2D(vec2 p, float angle) {
float c = cos(angle);
float s = sin(angle);
return vec2(p.x * c - p.y * s, p.x * s + p.y * c);
}
void main() {
vec2 coords = gl_FragCoord.xy / uResolution.xy;
coords = coords * 2.0 - 1.0;
coords = rotate2D(coords, uRotation);
float halfT = uTime * uSpeed * 0.5;
float fullT = uTime * uSpeed;
float mouseWarp = 0.0;
if (uEnableMouse) {
vec2 mPos = rotate2D(uMouse * 2.0 - 1.0, uRotation);
float mDist = length(coords - mPos);
mouseWarp = uMouseInfluence * exp(-mDist * mDist * 4.0);
}
float warpAx = coords.x + displaceA(coords.y, halfT) * uWarpIntensity + mouseWarp;
float warpAy = coords.y - displaceA(coords.x * cos(fullT) * 1.235, halfT) * uWarpIntensity;
float warpBx = coords.x + displaceB(coords.y, halfT) * uWarpIntensity + mouseWarp;
float warpBy = coords.y - displaceB(coords.x * sin(fullT) * 1.235, halfT) * uWarpIntensity;
vec2 fieldA = vec2(warpAx, warpAy);
vec2 fieldB = vec2(warpBx, warpBy);
vec2 blended = mix(fieldA, fieldB, mix(fieldA, fieldB, 0.5));
float fadeTop = smoothstep(uEdgeFadeWidth, uEdgeFadeWidth + 0.4, blended.y);
float fadeBottom = smoothstep(-uEdgeFadeWidth, -(uEdgeFadeWidth + 0.4), blended.y);
float vMask = 1.0 - max(fadeTop, fadeBottom);
float tileCount = mix(uOuterLines, uInnerLines, vMask);
float scaledY = blended.y * tileCount;
float nY = smoothNoise(abs(scaledY));
float ridge = pow(
step(abs(nY - blended.x) * 2.0, HALF_PI) * cos(2.0 * (nY - blended.x)),
5.0
);
float lines = 0.0;
for (float i = 1.0; i < 3.0; i += 1.0) {
lines += pow(max(fract(scaledY), fract(-scaledY)), i * 2.0);
}
float pattern = vMask * lines;
float cycleT = fullT * uColorCycleSpeed;
float rChannel = (pattern + lines * ridge) * (cos(blended.y + cycleT * 0.234) * 0.5 + 1.0);
float gChannel = (pattern + vMask * ridge) * (sin(blended.x + cycleT * 1.745) * 0.5 + 1.0);
float bChannel = (pattern + lines * ridge) * (cos(blended.x + cycleT * 0.534) * 0.5 + 1.0);
vec3 col = (rChannel * uColor1 + gChannel * uColor2 + bChannel * uColor3) * uBrightness;
float alpha = clamp(length(col), 0.0, 1.0);
gl_FragColor = vec4(col, alpha);
}
`;
export default function LineWaves({
speed = 0.3,
innerLineCount = 32.0,
outerLineCount = 36.0,
warpIntensity = 1.0,
rotation = -45,
edgeFadeWidth = 0.0,
colorCycleSpeed = 1.0,
brightness = 0.2,
color1 = '#ffffff',
color2 = '#ffffff',
color3 = '#ffffff',
enableMouseInteraction = true,
mouseInfluence = 2.0
}) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const container = containerRef.current;
const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
let program;
let currentMouse = [0.5, 0.5];
let targetMouse = [0.5, 0.5];
function handleMouseMove(e) {
const rect = gl.canvas.getBoundingClientRect();
targetMouse = [
(e.clientX - rect.left) / rect.width,
1.0 - (e.clientY - rect.top) / rect.height
];
}
function handleMouseLeave() {
targetMouse = [0.5, 0.5];
}
function resize() {
renderer.setSize(container.offsetWidth, container.offsetHeight);
if (program) {
program.uniforms.uResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];
}
}
window.addEventListener('resize', resize);
resize();
const geometry = new Triangle(gl);
const rotationRad = (rotation * Math.PI) / 180;
program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
uTime: { value: 0 },
uResolution: { value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height] },
uSpeed: { value: speed },
uInnerLines: { value: innerLineCount },
uOuterLines: { value: outerLineCount },
uWarpIntensity: { value: warpIntensity },
uRotation: { value: rotationRad },
uEdgeFadeWidth: { value: edgeFadeWidth },
uColorCycleSpeed: { value: colorCycleSpeed },
uBrightness: { value: brightness },
uColor1: { value: hexToVec3(color1) },
uColor2: { value: hexToVec3(color2) },
uColor3: { value: hexToVec3(color3) },
uMouse: { value: new Float32Array([0.5, 0.5]) },
uMouseInfluence: { value: mouseInfluence },
uEnableMouse: { value: enableMouseInteraction }
}
});
const mesh = new Mesh(gl, { geometry, program });
container.appendChild(gl.canvas);
if (enableMouseInteraction) {
gl.canvas.addEventListener('mousemove', handleMouseMove);
gl.canvas.addEventListener('mouseleave', handleMouseLeave);
}
let animationFrameId;
function update(time) {
animationFrameId = requestAnimationFrame(update);
program.uniforms.uTime.value = time * 0.001;
if (enableMouseInteraction) {
currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);
currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);
program.uniforms.uMouse.value[0] = currentMouse[0];
program.uniforms.uMouse.value[1] = currentMouse[1];
} else {
program.uniforms.uMouse.value[0] = 0.5;
program.uniforms.uMouse.value[1] = 0.5;
}
renderer.render({ scene: mesh });
}
animationFrameId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animationFrameId);
window.removeEventListener('resize', resize);
if (enableMouseInteraction) {
gl.canvas.removeEventListener('mousemove', handleMouseMove);
gl.canvas.removeEventListener('mouseleave', handleMouseLeave);
}
container.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, [speed, innerLineCount, outerLineCount, warpIntensity, rotation, edgeFadeWidth, colorCycleSpeed, brightness, color1, color2, color3, enableMouseInteraction, mouseInfluence]);
return <div ref={containerRef} className="line-waves-container" />;
}
Component CSS
.line-waves-container {
width: 100%;
height: 100%;
}Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: radar code: ## Integrate the <Radar /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Radar
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Radar from './Radar';
<Radar
speed={1.0}
scale={0.5}
ringCount={10}
spokeCount={10}
ringThickness={0.05}
spokeThickness={0.01}
sweepSpeed={1.0}
sweepWidth={2.0}
sweepLobes={1}
color="#9f29ff"
backgroundColor="#000000"
falloff={2.0}
brightness={1.0}
enableMouseInteraction={true}
mouseInfluence={0.1}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| speed | number | 1.0 | Overall animation speed multiplier. |
| scale | number | 0.5 | Zoom level of the radar pattern. |
| ringCount | number | 10.0 | Number of concentric rings. |
| spokeCount | number | 10.0 | Number of radial spoke lines. |
| ringThickness | number | 0.05 | Thickness of the concentric ring lines. |
| spokeThickness | number | 0.01 | Thickness of the radial spoke lines. |
| sweepSpeed | number | 1.0 | Rotation speed of the sweep beam. |
| sweepWidth | number | 2.0 | Width of the sweep trail (higher = thinner). |
| sweepLobes | number | 1.0 | Number of sweep beams around the radar. |
| color | string | "#9f29ff" | Primary radar color in HEX format. |
| backgroundColor | string | "#000000" | Background color in HEX format. |
| falloff | number | 2.0 | Edge fade intensity based on distance from center. |
| brightness | number | 1.0 | Overall brightness multiplier. |
| enableMouseInteraction | boolean | true | Enable cursor-reactive center offset. |
| mouseInfluence | number | 0.1 | Strength of the mouse offset effect. |
Full Component Source
import { Renderer, Program, Mesh, Triangle } from 'ogl';
import { useEffect, useRef } from 'react';
import './Radar.css';
function hexToVec3(hex) {
const h = hex.replace('#', '');
return [
parseInt(h.slice(0, 2), 16) / 255,
parseInt(h.slice(2, 4), 16) / 255,
parseInt(h.slice(4, 6), 16) / 255
];
}
const vertexShader = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
const fragmentShader = `
precision highp float;
uniform float uTime;
uniform vec3 uResolution;
uniform float uSpeed;
uniform float uScale;
uniform float uRingCount;
uniform float uSpokeCount;
uniform float uRingThickness;
uniform float uSpokeThickness;
uniform float uSweepSpeed;
uniform float uSweepWidth;
uniform float uSweepLobes;
uniform vec3 uColor;
uniform vec3 uBgColor;
uniform float uFalloff;
uniform float uBrightness;
uniform vec2 uMouse;
uniform float uMouseInfluence;
uniform bool uEnableMouse;
#define TAU 6.28318530718
#define PI 3.14159265359
void main() {
vec2 st = gl_FragCoord.xy / uResolution.xy;
st = st * 2.0 - 1.0;
st.x *= uResolution.x / uResolution.y;
if (uEnableMouse) {
vec2 mShift = (uMouse * 2.0 - 1.0);
mShift.x *= uResolution.x / uResolution.y;
st -= mShift * uMouseInfluence;
}
st *= uScale;
float dist = length(st);
float theta = atan(st.y, st.x);
float t = uTime * uSpeed;
float ringPhase = dist * uRingCount - t;
float ringDist = abs(fract(ringPhase) - 0.5);
float ringGlow = 1.0 - smoothstep(0.0, uRingThickness, ringDist);
float spokeAngle = abs(fract(theta * uSpokeCount / TAU + 0.5) - 0.5) * TAU / uSpokeCount;
float arcDist = spokeAngle * dist;
float spokeGlow = (1.0 - smoothstep(0.0, uSpokeThickness, arcDist)) * smoothstep(0.0, 0.1, dist);
float sweepPhase = t * uSweepSpeed;
float sweepBeam = pow(max(0.5 * sin(uSweepLobes * theta + sweepPhase) + 0.5, 0.0), uSweepWidth);
float fade = smoothstep(1.05, 0.85, dist) * pow(max(1.0 - dist, 0.0), uFalloff);
float intensity = max((ringGlow + spokeGlow + sweepBeam) * fade * uBrightness, 0.0);
vec3 col = uColor * intensity + uBgColor;
float alpha = clamp(length(col), 0.0, 1.0);
gl_FragColor = vec4(col, alpha);
}
`;
export default function Radar({
speed = 1.0,
scale = 0.5,
ringCount = 10.0,
spokeCount = 10.0,
ringThickness = 0.05,
spokeThickness = 0.01,
sweepSpeed = 1.0,
sweepWidth = 2.0,
sweepLobes = 1.0,
color = '#9f29ff',
backgroundColor = '#000000',
falloff = 2.0,
brightness = 1.0,
enableMouseInteraction = true,
mouseInfluence = 0.1
}) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const container = containerRef.current;
const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
let program;
let currentMouse = [0.5, 0.5];
let targetMouse = [0.5, 0.5];
function handleMouseMove(e) {
const rect = gl.canvas.getBoundingClientRect();
targetMouse = [
(e.clientX - rect.left) / rect.width,
1.0 - (e.clientY - rect.top) / rect.height
];
}
function handleMouseLeave() {
targetMouse = [0.5, 0.5];
}
function resize() {
renderer.setSize(container.offsetWidth, container.offsetHeight);
if (program) {
program.uniforms.uResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];
}
}
window.addEventListener('resize', resize);
resize();
const geometry = new Triangle(gl);
program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
uTime: { value: 0 },
uResolution: { value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height] },
uSpeed: { value: speed },
uScale: { value: scale },
uRingCount: { value: ringCount },
uSpokeCount: { value: spokeCount },
uRingThickness: { value: ringThickness },
uSpokeThickness: { value: spokeThickness },
uSweepSpeed: { value: sweepSpeed },
uSweepWidth: { value: sweepWidth },
uSweepLobes: { value: sweepLobes },
uColor: { value: hexToVec3(color) },
uBgColor: { value: hexToVec3(backgroundColor) },
uFalloff: { value: falloff },
uBrightness: { value: brightness },
uMouse: { value: new Float32Array([0.5, 0.5]) },
uMouseInfluence: { value: mouseInfluence },
uEnableMouse: { value: enableMouseInteraction }
}
});
const mesh = new Mesh(gl, { geometry, program });
container.appendChild(gl.canvas);
if (enableMouseInteraction) {
gl.canvas.addEventListener('mousemove', handleMouseMove);
gl.canvas.addEventListener('mouseleave', handleMouseLeave);
}
let animationFrameId;
function update(time) {
animationFrameId = requestAnimationFrame(update);
program.uniforms.uTime.value = time * 0.001;
if (enableMouseInteraction) {
currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);
currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);
program.uniforms.uMouse.value[0] = currentMouse[0];
program.uniforms.uMouse.value[1] = currentMouse[1];
} else {
program.uniforms.uMouse.value[0] = 0.5;
program.uniforms.uMouse.value[1] = 0.5;
}
renderer.render({ scene: mesh });
}
animationFrameId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animationFrameId);
window.removeEventListener('resize', resize);
if (enableMouseInteraction) {
gl.canvas.removeEventListener('mousemove', handleMouseMove);
gl.canvas.removeEventListener('mouseleave', handleMouseLeave);
}
container.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, [speed, scale, ringCount, spokeCount, ringThickness, spokeThickness, sweepSpeed, sweepWidth, sweepLobes, color, backgroundColor, falloff, brightness, enableMouseInteraction, mouseInfluence]);
return <div ref={containerRef} className="radar-container" />;
}
Component CSS
.radar-container {
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: soft aurora code: ## Integrate the <SoftAurora /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: SoftAurora
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import SoftAurora from './SoftAurora';
<SoftAurora
speed={0.6}
scale={1.5}
brightness={1.0}
color1="#f7f7f7"
color2="#e100ff"
noiseFrequency={2.5}
noiseAmplitude={1.0}
bandHeight={0.5}
bandSpread={1.0}
octaveDecay={0.1}
layerOffset={0}
colorSpeed={1.0}
enableMouseInteraction={true}
mouseInfluence={0.25}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| speed | number | 0.6 | Overall animation speed multiplier. |
| scale | number | 1.5 | Scale of the noise pattern. |
| brightness | number | 1.0 | Overall brightness multiplier. |
| color1 | string | "#f7f7f7" | Tint color for the first aurora layer. |
| color2 | string | "#e100ff" | Tint color for the second aurora layer. |
| noiseFrequency | number | 2.5 | Base frequency of the Perlin noise. |
| noiseAmplitude | number | 1.0 | Base amplitude of the Perlin noise. |
| bandHeight | number | 0.5 | Vertical position of the aurora band (0-1). |
| bandSpread | number | 1.0 | Vertical spread of the aurora glow. |
| octaveDecay | number | 0.1 | Amplitude decay per noise octave. |
| layerOffset | number | 0 | Time offset between the two aurora layers. |
| colorSpeed | number | 1.0 | Speed of palette color shifting. |
| enableMouseInteraction | boolean | true | Enable cursor-reactive aurora offset. |
| mouseInfluence | number | 0.25 | Strength of the mouse offset effect. |
Full Component Source
import { Renderer, Program, Mesh, Triangle } from 'ogl';
import { useEffect, useRef } from 'react';
import './SoftAurora.css';
function hexToVec3(hex) {
const h = hex.replace('#', '');
return [
parseInt(h.slice(0, 2), 16) / 255,
parseInt(h.slice(2, 4), 16) / 255,
parseInt(h.slice(4, 6), 16) / 255
];
}
const vertexShader = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
const fragmentShader = `
precision highp float;
uniform float uTime;
uniform vec3 uResolution;
uniform float uSpeed;
uniform float uScale;
uniform float uBrightness;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform float uNoiseFreq;
uniform float uNoiseAmp;
uniform float uBandHeight;
uniform float uBandSpread;
uniform float uOctaveDecay;
uniform float uLayerOffset;
uniform float uColorSpeed;
uniform vec2 uMouse;
uniform float uMouseInfluence;
uniform bool uEnableMouse;
#define TAU 6.28318
vec3 gradientHash(vec3 p) {
p = vec3(
dot(p, vec3(127.1, 311.7, 234.6)),
dot(p, vec3(269.5, 183.3, 198.3)),
dot(p, vec3(169.5, 283.3, 156.9))
);
vec3 h = fract(sin(p) * 43758.5453123);
float phi = acos(2.0 * h.x - 1.0);
float theta = TAU * h.y;
return vec3(cos(theta) * sin(phi), sin(theta) * cos(phi), cos(phi));
}
float quinticSmooth(float t) {
float t2 = t * t;
float t3 = t * t2;
return 6.0 * t3 * t2 - 15.0 * t2 * t2 + 10.0 * t3;
}
vec3 cosineGradient(float t, vec3 a, vec3 b, vec3 c, vec3 d) {
return a + b * cos(TAU * (c * t + d));
}
float perlin3D(float amplitude, float frequency, float px, float py, float pz) {
float x = px * frequency;
float y = py * frequency;
float fx = floor(x); float fy = floor(y); float fz = floor(pz);
float cx = ceil(x); float cy = ceil(y); float cz = ceil(pz);
vec3 g000 = gradientHash(vec3(fx, fy, fz));
vec3 g100 = gradientHash(vec3(cx, fy, fz));
vec3 g010 = gradientHash(vec3(fx, cy, fz));
vec3 g110 = gradientHash(vec3(cx, cy, fz));
vec3 g001 = gradientHash(vec3(fx, fy, cz));
vec3 g101 = gradientHash(vec3(cx, fy, cz));
vec3 g011 = gradientHash(vec3(fx, cy, cz));
vec3 g111 = gradientHash(vec3(cx, cy, cz));
float d000 = dot(g000, vec3(x - fx, y - fy, pz - fz));
float d100 = dot(g100, vec3(x - cx, y - fy, pz - fz));
float d010 = dot(g010, vec3(x - fx, y - cy, pz - fz));
float d110 = dot(g110, vec3(x - cx, y - cy, pz - fz));
float d001 = dot(g001, vec3(x - fx, y - fy, pz - cz));
float d101 = dot(g101, vec3(x - cx, y - fy, pz - cz));
float d011 = dot(g011, vec3(x - fx, y - cy, pz - cz));
float d111 = dot(g111, vec3(x - cx, y - cy, pz - cz));
float sx = quinticSmooth(x - fx);
float sy = quinticSmooth(y - fy);
float sz = quinticSmooth(pz - fz);
float lx00 = mix(d000, d100, sx);
float lx10 = mix(d010, d110, sx);
float lx01 = mix(d001, d101, sx);
float lx11 = mix(d011, d111, sx);
float ly0 = mix(lx00, lx10, sy);
float ly1 = mix(lx01, lx11, sy);
return amplitude * mix(ly0, ly1, sz);
}
float auroraGlow(float t, vec2 shift) {
vec2 uv = gl_FragCoord.xy / uResolution.y;
uv += shift;
float noiseVal = 0.0;
float freq = uNoiseFreq;
float amp = uNoiseAmp;
vec2 samplePos = uv * uScale;
for (float i = 0.0; i < 3.0; i += 1.0) {
noiseVal += perlin3D(amp, freq, samplePos.x, samplePos.y, t);
amp *= uOctaveDecay;
freq *= 2.0;
}
float yBand = uv.y * 10.0 - uBandHeight * 10.0;
return 0.3 * max(exp(uBandSpread * (1.0 - 1.1 * abs(noiseVal + yBand))), 0.0);
}
void main() {
vec2 uv = gl_FragCoord.xy / uResolution.xy;
float t = uSpeed * 0.4 * uTime;
vec2 shift = vec2(0.0);
if (uEnableMouse) {
shift = (uMouse - 0.5) * uMouseInfluence;
}
vec3 col = vec3(0.0);
col += 0.99 * auroraGlow(t, shift) * cosineGradient(uv.x + uTime * uSpeed * 0.2 * uColorSpeed, vec3(0.5), vec3(0.5), vec3(1.0), vec3(0.3, 0.20, 0.20)) * uColor1;
col += 0.99 * auroraGlow(t + uLayerOffset, shift) * cosineGradient(uv.x + uTime * uSpeed * 0.1 * uColorSpeed, vec3(0.5), vec3(0.5), vec3(2.0, 1.0, 0.0), vec3(0.5, 0.20, 0.25)) * uColor2;
col *= uBrightness;
float alpha = clamp(length(col), 0.0, 1.0);
gl_FragColor = vec4(col, alpha);
}
`;
export default function SoftAurora({
speed = 0.6,
scale = 1.5,
brightness = 1.0,
color1 = '#f7f7f7',
color2 = '#e100ff',
noiseFrequency = 2.5,
noiseAmplitude = 1.0,
bandHeight = 0.5,
bandSpread = 1.0,
octaveDecay = 0.1,
layerOffset = 0,
colorSpeed = 1.0,
enableMouseInteraction = true,
mouseInfluence = 0.25
}) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const container = containerRef.current;
const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
let program;
let currentMouse = [0.5, 0.5];
let targetMouse = [0.5, 0.5];
function handleMouseMove(e) {
const rect = gl.canvas.getBoundingClientRect();
targetMouse = [
(e.clientX - rect.left) / rect.width,
1.0 - (e.clientY - rect.top) / rect.height
];
}
function handleMouseLeave() {
targetMouse = [0.5, 0.5];
}
function resize() {
renderer.setSize(container.offsetWidth, container.offsetHeight);
if (program) {
program.uniforms.uResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];
}
}
window.addEventListener('resize', resize);
resize();
const geometry = new Triangle(gl);
program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
uTime: { value: 0 },
uResolution: { value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height] },
uSpeed: { value: speed },
uScale: { value: scale },
uBrightness: { value: brightness },
uColor1: { value: hexToVec3(color1) },
uColor2: { value: hexToVec3(color2) },
uNoiseFreq: { value: noiseFrequency },
uNoiseAmp: { value: noiseAmplitude },
uBandHeight: { value: bandHeight },
uBandSpread: { value: bandSpread },
uOctaveDecay: { value: octaveDecay },
uLayerOffset: { value: layerOffset },
uColorSpeed: { value: colorSpeed },
uMouse: { value: new Float32Array([0.5, 0.5]) },
uMouseInfluence: { value: mouseInfluence },
uEnableMouse: { value: enableMouseInteraction }
}
});
const mesh = new Mesh(gl, { geometry, program });
container.appendChild(gl.canvas);
if (enableMouseInteraction) {
gl.canvas.addEventListener('mousemove', handleMouseMove);
gl.canvas.addEventListener('mouseleave', handleMouseLeave);
}
let animationFrameId;
function update(time) {
animationFrameId = requestAnimationFrame(update);
program.uniforms.uTime.value = time * 0.001;
if (enableMouseInteraction) {
currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);
currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);
program.uniforms.uMouse.value[0] = currentMouse[0];
program.uniforms.uMouse.value[1] = currentMouse[1];
} else {
program.uniforms.uMouse.value[0] = 0.5;
program.uniforms.uMouse.value[1] = 0.5;
}
renderer.render({ scene: mesh });
}
animationFrameId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animationFrameId);
window.removeEventListener('resize', resize);
if (enableMouseInteraction) {
gl.canvas.removeEventListener('mousemove', handleMouseMove);
gl.canvas.removeEventListener('mouseleave', handleMouseLeave);
}
container.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, [speed, scale, brightness, color1, color2, noiseFrequency, noiseAmplitude, bandHeight, bandSpread, octaveDecay, layerOffset, colorSpeed, enableMouseInteraction, mouseInfluence]);
return <div ref={containerRef} className="soft-aurora-container" />;
}
Component CSS
.soft-aurora-container {
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: aurora code: ## Integrate the <Aurora /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Aurora
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Aurora from './Aurora';
<Aurora
colorStops={["#7cff67","#B497CF","#5227FF"]}
blend={0.5}
amplitude={1.0}
speed={0.5}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| colorStops | [string, string, string] | ["#3A29FF", "#FF94B4", "#FF3232"] | An array of three hex colors defining the aurora gradient. |
| speed | number | 1.0 | Controls the animation speed. Higher values make the aurora move faster. |
| blend | number | 0.5 | Controls the blending of the aurora effect with the background. |
| amplitude | number | 1.0 | Controls the height intensity of the aurora effect. |
Full Component Source
import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';
import { useEffect, useRef } from 'react';
import './Aurora.css';
const VERT = `#version 300 es
in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const FRAG = `#version 300 es
precision highp float;
uniform float uTime;
uniform float uAmplitude;
uniform vec3 uColorStops[3];
uniform vec2 uResolution;
uniform float uBlend;
out vec4 fragColor;
vec3 permute(vec3 x) {
return mod(((x * 34.0) + 1.0) * x, 289.0);
}
float snoise(vec2 v){
const vec4 C = vec4(
0.211324865405187, 0.366025403784439,
-0.577350269189626, 0.024390243902439
);
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod(i, 289.0);
vec3 p = permute(
permute(i.y + vec3(0.0, i1.y, 1.0))
+ i.x + vec3(0.0, i1.x, 1.0)
);
vec3 m = max(
0.5 - vec3(
dot(x0, x0),
dot(x12.xy, x12.xy),
dot(x12.zw, x12.zw)
),
0.0
);
m = m * m;
m = m * m;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
struct ColorStop {
vec3 color;
float position;
};
#define COLOR_RAMP(colors, factor, finalColor) { \
int index = 0; \
for (int i = 0; i < 2; i++) { \
ColorStop currentColor = colors[i]; \
bool isInBetween = currentColor.position <= factor; \
index = int(mix(float(index), float(i), float(isInBetween))); \
} \
ColorStop currentColor = colors[index]; \
ColorStop nextColor = colors[index + 1]; \
float range = nextColor.position - currentColor.position; \
float lerpFactor = (factor - currentColor.position) / range; \
finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \
}
void main() {
vec2 uv = gl_FragCoord.xy / uResolution;
ColorStop colors[3];
colors[0] = ColorStop(uColorStops[0], 0.0);
colors[1] = ColorStop(uColorStops[1], 0.5);
colors[2] = ColorStop(uColorStops[2], 1.0);
vec3 rampColor;
COLOR_RAMP(colors, uv.x, rampColor);
float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;
height = exp(height);
height = (uv.y * 2.0 - height + 0.2);
float intensity = 0.6 * height;
float midPoint = 0.20;
float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);
vec3 auroraColor = intensity * rampColor;
fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);
}
`;
export default function Aurora(props) {
const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;
const propsRef = useRef(props);
propsRef.current = props;
const ctnDom = useRef(null);
useEffect(() => {
const ctn = ctnDom.current;
if (!ctn) return;
const renderer = new Renderer({
alpha: true,
premultipliedAlpha: true,
antialias: true
});
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.canvas.style.backgroundColor = 'transparent';
let program;
function resize() {
if (!ctn) return;
const width = ctn.offsetWidth;
const height = ctn.offsetHeight;
renderer.setSize(width, height);
if (program) {
program.uniforms.uResolution.value = [width, height];
}
}
window.addEventListener('resize', resize);
const geometry = new Triangle(gl);
if (geometry.attributes.uv) {
delete geometry.attributes.uv;
}
const colorStopsArray = colorStops.map(hex => {
const c = new Color(hex);
return [c.r, c.g, c.b];
});
program = new Program(gl, {
vertex: VERT,
fragment: FRAG,
uniforms: {
uTime: { value: 0 },
uAmplitude: { value: amplitude },
uColorStops: { value: colorStopsArray },
uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },
uBlend: { value: blend }
}
});
const mesh = new Mesh(gl, { geometry, program });
ctn.appendChild(gl.canvas);
let animateId = 0;
const update = t => {
animateId = requestAnimationFrame(update);
const { time = t * 0.01, speed = 1.0 } = propsRef.current;
program.uniforms.uTime.value = time * speed * 0.1;
program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;
program.uniforms.uBlend.value = propsRef.current.blend ?? blend;
const stops = propsRef.current.colorStops ?? colorStops;
program.uniforms.uColorStops.value = stops.map(hex => {
const c = new Color(hex);
return [c.r, c.g, c.b];
});
renderer.render({ scene: mesh });
};
animateId = requestAnimationFrame(update);
resize();
return () => {
cancelAnimationFrame(animateId);
window.removeEventListener('resize', resize);
if (ctn && gl.canvas.parentNode === ctn) {
ctn.removeChild(gl.canvas);
}
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [amplitude]);
return <div ref={ctnDom} className="aurora-container" />;
}
Component CSS
.aurora-container {
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: plasma code: ## Integrate the <Plasma /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Plasma
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Plasma from './Plasma';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Plasma
color="#ff6b35"
speed={0.6}
direction="forward"
scale={1.1}
opacity={0.8}
mouseInteractive={true}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| color | string | undefined | Optional hex color to tint the plasma effect. If not provided, uses original colors. |
| speed | number | 1.0 | Animation speed multiplier. Higher values = faster animation. |
| direction | 'forward' | 'reverse' | 'pingpong' |
| scale | number | 1.0 | Zoom level of the plasma pattern. Higher values zoom in. |
| opacity | number | 1.0 | Overall opacity of the effect (0-1). |
| mouseInteractive | boolean | false | Whether the plasma responds to mouse movement. |
Full Component Source
import { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle } from 'ogl';
import './Plasma.css';
const hexToRgb = hex => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!result) return [1, 0.5, 0.2];
return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];
};
const vertex = `#version 300 es
precision highp float;
in vec2 position;
in vec2 uv;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragment = `#version 300 es
precision highp float;
uniform vec2 iResolution;
uniform float iTime;
uniform vec3 uCustomColor;
uniform float uUseCustomColor;
uniform float uSpeed;
uniform float uDirection;
uniform float uScale;
uniform float uOpacity;
uniform vec2 uMouse;
uniform float uMouseInteractive;
out vec4 fragColor;
void mainImage(out vec4 o, vec2 C) {
vec2 center = iResolution.xy * 0.5;
C = (C - center) / uScale + center;
vec2 mouseOffset = (uMouse - center) * 0.0002;
C += mouseOffset * length(C - center) * step(0.5, uMouseInteractive);
float i, d, z, T = iTime * uSpeed * uDirection;
vec3 O, p, S;
for (vec2 r = iResolution.xy, Q; ++i < 60.; O += o.w/d*o.xyz) {
p = z*normalize(vec3(C-.5*r,r.y));
p.z -= 4.;
S = p;
d = p.y-T;
p.x += .4*(1.+p.y)*sin(d + p.x*0.1)*cos(.34*d + p.x*0.05);
Q = p.xz *= mat2(cos(p.y+vec4(0,11,33,0)-T));
z+= d = abs(sqrt(length(Q*Q)) - .25*(5.+S.y))/3.+8e-4;
o = 1.+sin(S.y+p.z*.5+S.z-length(S-p)+vec4(2,1,0,8));
}
o.xyz = tanh(O/1e4);
}
bool finite1(float x){ return !(isnan(x) || isinf(x)); }
vec3 sanitize(vec3 c){
return vec3(
finite1(c.r) ? c.r : 0.0,
finite1(c.g) ? c.g : 0.0,
finite1(c.b) ? c.b : 0.0
);
}
void main() {
vec4 o = vec4(0.0);
mainImage(o, gl_FragCoord.xy);
vec3 rgb = sanitize(o.rgb);
float intensity = (rgb.r + rgb.g + rgb.b) / 3.0;
vec3 customColor = intensity * uCustomColor;
vec3 finalColor = mix(rgb, customColor, step(0.5, uUseCustomColor));
float alpha = length(rgb) * uOpacity;
fragColor = vec4(finalColor, alpha);
}`;
export const Plasma = ({
color = '#ffffff',
speed = 1,
direction = 'forward',
scale = 1,
opacity = 1,
mouseInteractive = true
}) => {
const containerRef = useRef(null);
const mousePos = useRef({ x: 0, y: 0 });
useEffect(() => {
if (!containerRef.current) return;
const containerEl = containerRef.current;
const useCustomColor = color ? 1.0 : 0.0;
const customColorRgb = color ? hexToRgb(color) : [1, 1, 1];
const directionMultiplier = direction === 'reverse' ? -1.0 : 1.0;
let renderer;
try {
renderer = new Renderer({
webgl: 2,
alpha: true,
antialias: false,
dpr: Math.min(window.devicePixelRatio || 1, 2)
});
} catch {
return;
}
const gl = renderer.gl;
if (!gl) return;
const canvas = gl.canvas;
canvas.style.display = 'block';
canvas.style.width = '100%';
canvas.style.height = '100%';
containerEl.appendChild(canvas);
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex: vertex,
fragment: fragment,
uniforms: {
iTime: { value: 0 },
iResolution: { value: new Float32Array([1, 1]) },
uCustomColor: { value: new Float32Array(customColorRgb) },
uUseCustomColor: { value: useCustomColor },
uSpeed: { value: speed * 0.4 },
uDirection: { value: directionMultiplier },
uScale: { value: scale },
uOpacity: { value: opacity },
uMouse: { value: new Float32Array([0, 0]) },
uMouseInteractive: { value: mouseInteractive ? 1.0 : 0.0 }
}
});
const mesh = new Mesh(gl, { geometry, program });
const handleMouseMove = e => {
if (!mouseInteractive) return;
const rect = containerEl.getBoundingClientRect();
mousePos.current.x = e.clientX - rect.left;
mousePos.current.y = e.clientY - rect.top;
const mouseUniform = program.uniforms.uMouse.value;
mouseUniform[0] = mousePos.current.x;
mouseUniform[1] = mousePos.current.y;
};
if (mouseInteractive) {
containerEl.addEventListener('mousemove', handleMouseMove);
}
const setSize = () => {
const rect = containerEl.getBoundingClientRect();
const width = Math.max(1, Math.floor(rect.width));
const height = Math.max(1, Math.floor(rect.height));
renderer.setSize(width, height);
const res = program.uniforms.iResolution.value;
res[0] = gl.drawingBufferWidth;
res[1] = gl.drawingBufferHeight;
};
const ro = new ResizeObserver(setSize);
ro.observe(containerEl);
setSize();
let raf = 0;
let contextLost = false;
let isVisible = true;
const t0 = performance.now();
const loop = t => {
if (contextLost || !isVisible) return;
let timeValue = (t - t0) * 0.001;
if (direction === 'pingpong') {
const pingpongDuration = 10;
const segmentTime = timeValue % pingpongDuration;
const isForward = Math.floor(timeValue / pingpongDuration) % 2 === 0;
const u = segmentTime / pingpongDuration;
const smooth = u * u * (3 - 2 * u);
const pingpongTime = isForward ? smooth * pingpongDuration : (1 - smooth) * pingpongDuration;
program.uniforms.uDirection.value = 1.0;
program.uniforms.iTime.value = pingpongTime;
} else {
program.uniforms.iTime.value = timeValue;
}
renderer.render({ scene: mesh });
raf = requestAnimationFrame(loop);
};
const handleContextLost = (e) => {
e.preventDefault();
contextLost = true;
cancelAnimationFrame(raf);
};
const handleContextRestored = () => {
contextLost = false;
if (isVisible) {
cancelAnimationFrame(raf);
raf = requestAnimationFrame(loop);
}
};
canvas.addEventListener('webglcontextlost', handleContextLost);
canvas.addEventListener('webglcontextrestored', handleContextRestored);
const io = new IntersectionObserver(([entry]) => {
const wasVisible = isVisible;
isVisible = entry.isIntersecting;
if (isVisible && !wasVisible && !contextLost) {
cancelAnimationFrame(raf);
raf = requestAnimationFrame(loop);
}
}, { threshold: 0 });
io.observe(containerEl);
raf = requestAnimationFrame(loop);
return () => {
cancelAnimationFrame(raf);
ro.disconnect();
io.disconnect();
canvas.removeEventListener('webglcontextlost', handleContextLost);
canvas.removeEventListener('webglcontextrestored', handleContextRestored);
if (mouseInteractive && containerEl) {
containerEl.removeEventListener('mousemove', handleMouseMove);
}
try {
containerEl?.removeChild(canvas);
} catch {}
};
}, [color, speed, direction, scale, opacity, mouseInteractive]);
return <div ref={containerRef} className="plasma-container" />;
};
export default Plasma;
Component CSS
.plasma-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: plasma wave code: ## Integrate the <PlasmaWave /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: PlasmaWave
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import PlasmaWave from './PlasmaWave';
<PlasmaWave
colors={["#A855F7","#06B6D4"]}
speed1={0.05}
speed2={0.05}
focalLength={0.8}
bend1={1}
bend2={0.5}
dir2={1.0}
rotationDeg={0}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| colors | [string, string] | ["#A855F7", "#06B6D4"] | Array of two hex colors — one for each plasma wave band. |
| speed1 | number | 0.05 | Speed of the first plasma wave. |
| speed2 | number | 0.05 | Speed of the second plasma wave. |
| dir2 | number | 1.0 | Direction multiplier for the second wave. Use -1 to reverse. |
| focalLength | number | 0.8 | Focal length of the camera projection. |
| bend1 | number | 1 | Bend intensity of the first wave. |
| bend2 | number | 0.5 | Bend intensity of the second wave. |
| rotationDeg | number | 0 | Rotation angle of the scene in degrees. |
| xOffset | number | 0 | Horizontal offset of the viewport. |
| yOffset | number | 0 | Vertical offset of the viewport. |
Full Component Source
import { useRef, useEffect } from 'react';
import { Renderer, Camera, Transform, Program, Mesh, Geometry } from 'ogl';
import './PlasmaWave.css';
function hexToRgb(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
return [r, g, b];
}
const VERT = /* glsl */ `
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const FRAG = /* glsl */ `
precision mediump float;
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 uOffset;
uniform float uRotation;
uniform float uFocalLength;
uniform float uSpeed1;
uniform float uSpeed2;
uniform float uDir2;
uniform float uBend1;
uniform float uBend2;
uniform vec3 uColor1;
uniform vec3 uColor2;
const float lt = 0.3;
const float pi = 3.14159;
const float pi2 = 6.28318;
const float pi_2 = 1.5708;
#define MAX_STEPS 14
void mainImage(out vec4 C, in vec2 U) {
float t = iTime * pi;
float s = 1.0;
float d = 0.0;
vec2 R = iResolution;
vec3 o = vec3(0.0, 0.0, -7.0);
vec3 u = normalize(vec3((U - 0.5 * R) / R.y, uFocalLength));
vec2 k = vec2(0.0);
vec3 p;
float t1 = t * 0.7;
float t2 = t * 0.9;
float tSpeed1 = t * uSpeed1;
float tSpeed2 = t * uSpeed2 * uDir2;
for (int i = 0; i < MAX_STEPS; ++i) {
p = o + u * d;
p.x -= 15.0;
float px = p.x;
float wob1 = uBend1 + sin(t1 + px * 0.8) * 0.1;
float wob2 = uBend2 + cos(t2 + px * 1.1) * 0.1;
float px2 = px + pi_2;
vec2 sinOffset = sin(vec2(px, px2) + tSpeed1) * wob1;
vec2 cosOffset = cos(vec2(px, px2) + tSpeed2) * wob2;
vec2 yz = p.yz;
float pxLt = px + lt;
k.x = max(pxLt, length(yz - sinOffset) - lt);
k.y = max(pxLt, length(yz - cosOffset) - lt);
float current = min(k.x, k.y);
s = min(s, current);
if (s < 0.001 || d > 300.0) break;
d += s * 0.7;
}
float sqrtD = sqrt(d);
vec3 raw = max(cos(d * pi2) - s * sqrtD - vec3(k, 0.0), 0.0);
raw.gb += 0.1;
float maxC = max(raw.r, max(raw.g, raw.b));
if (maxC < 0.15) discard;
raw = raw * 0.4 + raw.brg * 0.6 + raw * raw;
float lum = dot(raw, vec3(0.299, 0.587, 0.114));
float w1 = max(0.0, 1.0 - k.x * 2.0);
float w2 = max(0.0, 1.0 - k.y * 2.0);
float wt = w1 + w2 + 0.001;
vec3 c = (uColor1 * w1 + uColor2 * w2) / wt * lum * 3.5;
C = vec4(c, 1.0);
}
void main() {
vec2 coord = gl_FragCoord.xy + uOffset;
coord -= 0.5 * iResolution;
float c = cos(uRotation), s = sin(uRotation);
coord = mat2(c, -s, s, c) * coord;
coord += 0.5 * iResolution;
vec4 color;
mainImage(color, coord);
gl_FragColor = color;
}
`;
export default function PlasmaWave(props) {
const {
xOffset = 0,
yOffset = 0,
rotationDeg = 0,
focalLength = 0.8,
speed1 = 0.05,
speed2 = 0.05,
dir2 = 1.0,
bend1 = 1,
bend2 = 0.5,
colors = ['#A855F7', '#06B6D4']
} = props;
const propsRef = useRef(props);
propsRef.current = props;
const containerRef = useRef(null);
useEffect(() => {
const ctn = containerRef.current;
if (!ctn) return;
const renderer = new Renderer({
alpha: true,
dpr: Math.min(window.devicePixelRatio, 1.5),
antialias: false,
depth: false,
stencil: false,
premultipliedAlpha: false,
preserveDrawingBuffer: false,
powerPreference: 'high-performance'
});
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
ctn.appendChild(gl.canvas);
const camera = new Camera(gl);
const scene = new Transform();
const geometry = new Geometry(gl, {
position: { size: 2, data: new Float32Array([-1, -1, 3, -1, -1, 3]) }
});
const uniformOffset = new Float32Array([xOffset, yOffset]);
const uniformResolution = new Float32Array([1, 1]);
const c1 = hexToRgb(colors[0]);
const c2 = hexToRgb(colors[1]);
const program = new Program(gl, {
vertex: VERT,
fragment: FRAG,
uniforms: {
iTime: { value: 0 },
iResolution: { value: uniformResolution },
uOffset: { value: uniformOffset },
uRotation: { value: (rotationDeg * Math.PI) / 180 },
uFocalLength: { value: focalLength },
uSpeed1: { value: speed1 },
uSpeed2: { value: speed2 },
uDir2: { value: dir2 },
uBend1: { value: bend1 },
uBend2: { value: bend2 },
uColor1: { value: c1 },
uColor2: { value: c2 }
}
});
new Mesh(gl, { geometry, program }).setParent(scene);
function resize() {
if (!ctn) return;
const { width, height } = ctn.getBoundingClientRect();
renderer.setSize(width, height);
uniformResolution[0] = width * renderer.dpr;
uniformResolution[1] = height * renderer.dpr;
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
}
const ro = new ResizeObserver(resize);
ro.observe(ctn);
resize();
const startTime = performance.now();
let animateId;
const update = (now) => {
const {
xOffset: xOff = 0,
yOffset: yOff = 0,
rotationDeg: rot = 0,
focalLength: fLen = 0.8,
speed1: s1 = 0.05,
speed2: s2 = 0.05,
dir2: d2 = 1.0,
bend1: b1 = 1,
bend2: b2 = 0.5,
colors: cols = ['#A855F7', '#06B6D4']
} = propsRef.current;
uniformOffset[0] = xOff;
uniformOffset[1] = yOff;
program.uniforms.iTime.value = (now - startTime) * 0.001;
program.uniforms.uRotation.value = (rot * Math.PI) / 180;
program.uniforms.uFocalLength.value = fLen;
program.uniforms.uSpeed1.value = s1;
program.uniforms.uSpeed2.value = s2;
program.uniforms.uDir2.value = d2;
program.uniforms.uBend1.value = b1;
program.uniforms.uBend2.value = b2;
program.uniforms.uColor1.value = hexToRgb(cols[0]);
program.uniforms.uColor2.value = hexToRgb(cols[1]);
renderer.render({ scene, camera });
animateId = requestAnimationFrame(update);
};
animateId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(animateId);
ro.disconnect();
if (ctn && gl.canvas.parentNode === ctn) {
ctn.removeChild(gl.canvas);
}
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <div ref={containerRef} className="plasma-wave-container" />;
}
Component CSS
.plasma-wave-container {
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: particles code: ## Integrate the <Particles /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Particles
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Particles from './Particles';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Particles
particleColors={["#ffffff"]}
particleCount={200}
particleSpread={10}
speed={0.1}
particleBaseSize={100}
moveParticlesOnHover={true}
alphaParticles={false}
disableRotation={false}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| particleCount | number | 200 | The number of particles to generate. |
| particleSpread | number | 10 | Controls how far particles are spread from the center. |
| speed | number | 0.1 | Speed factor controlling the animation pace. |
| particleColors | string[] | ['#ffffff'] | An array of hex color strings used to color the particles. |
| moveParticlesOnHover | boolean | false | Determines if particles should move in response to mouse hover. |
| particleHoverFactor | number | 1 | Multiplier for the particle movement when hovering. |
| alphaParticles | boolean | false | If true, particles are rendered with varying transparency; otherwise, as solid circles. |
| particleBaseSize | number | 100 | The base size of the particles. |
| sizeRandomness | number | 1 | Controls the variation in particle sizes (0 means all particles have the same size). |
| cameraDistance | number | 20 | Distance from the camera to the particle system. |
| disableRotation | boolean | false | If true, stops the particle system from rotating. |
| pixelRatio | number | 1 | Sets the pixel ratio for sharper rendering on high-DPI screens. |
Full Component Source
import { useEffect, useRef } from 'react';
import { Renderer, Camera, Geometry, Program, Mesh } from 'ogl';
import './Particles.css';
const defaultColors = ['#ffffff', '#ffffff', '#ffffff'];
const hexToRgb = hex => {
hex = hex.replace(/^#/, '');
if (hex.length === 3) {
hex = hex
.split('')
.map(c => c + c)
.join('');
}
const int = parseInt(hex, 16);
const r = ((int >> 16) & 255) / 255;
const g = ((int >> 8) & 255) / 255;
const b = (int & 255) / 255;
return [r, g, b];
};
const vertex = /* glsl */ `
attribute vec3 position;
attribute vec4 random;
attribute vec3 color;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform float uTime;
uniform float uSpread;
uniform float uBaseSize;
uniform float uSizeRandomness;
varying vec4 vRandom;
varying vec3 vColor;
void main() {
vRandom = random;
vColor = color;
vec3 pos = position * uSpread;
pos.z *= 10.0;
vec4 mPos = modelMatrix * vec4(pos, 1.0);
float t = uTime;
mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);
mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);
mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);
vec4 mvPos = viewMatrix * mPos;
if (uSizeRandomness == 0.0) {
gl_PointSize = uBaseSize;
} else {
gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);
}
gl_Position = projectionMatrix * mvPos;
}
`;
const fragment = /* glsl */ `
precision highp float;
uniform float uTime;
uniform float uAlphaParticles;
varying vec4 vRandom;
varying vec3 vColor;
void main() {
vec2 uv = gl_PointCoord.xy;
float d = length(uv - vec2(0.5));
if(uAlphaParticles < 0.5) {
if(d > 0.5) {
discard;
}
gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);
} else {
float circle = smoothstep(0.5, 0.4, d) * 0.8;
gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);
}
}
`;
const Particles = ({
particleCount = 200,
particleSpread = 10,
speed = 0.1,
particleColors,
moveParticlesOnHover = false,
particleHoverFactor = 1,
alphaParticles = false,
particleBaseSize = 100,
sizeRandomness = 1,
cameraDistance = 20,
disableRotation = false,
pixelRatio = 1,
className
}) => {
const containerRef = useRef(null);
const mouseRef = useRef({ x: 0, y: 0 });
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const renderer = new Renderer({
dpr: pixelRatio,
depth: false,
alpha: true
});
const gl = renderer.gl;
container.appendChild(gl.canvas);
gl.clearColor(0, 0, 0, 0);
const camera = new Camera(gl, { fov: 15 });
camera.position.set(0, 0, cameraDistance);
const resize = () => {
const width = container.clientWidth;
const height = container.clientHeight;
renderer.setSize(width, height);
camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });
};
window.addEventListener('resize', resize, false);
resize();
const handleMouseMove = e => {
const rect = container.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1);
mouseRef.current = { x, y };
};
if (moveParticlesOnHover) {
container.addEventListener('mousemove', handleMouseMove);
}
const count = particleCount;
const positions = new Float32Array(count * 3);
const randoms = new Float32Array(count * 4);
const colors = new Float32Array(count * 3);
const palette = particleColors && particleColors.length > 0 ? particleColors : defaultColors;
for (let i = 0; i < count; i++) {
let x, y, z, len;
do {
x = Math.random() * 2 - 1;
y = Math.random() * 2 - 1;
z = Math.random() * 2 - 1;
len = x * x + y * y + z * z;
} while (len > 1 || len === 0);
const r = Math.cbrt(Math.random());
positions.set([x * r, y * r, z * r], i * 3);
randoms.set([Math.random(), Math.random(), Math.random(), Math.random()], i * 4);
const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)]);
colors.set(col, i * 3);
}
const geometry = new Geometry(gl, {
position: { size: 3, data: positions },
random: { size: 4, data: randoms },
color: { size: 3, data: colors }
});
const program = new Program(gl, {
vertex,
fragment,
uniforms: {
uTime: { value: 0 },
uSpread: { value: particleSpread },
uBaseSize: { value: particleBaseSize * pixelRatio },
uSizeRandomness: { value: sizeRandomness },
uAlphaParticles: { value: alphaParticles ? 1 : 0 }
},
transparent: true,
depthTest: false
});
const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program });
let animationFrameId;
let lastTime = performance.now();
let elapsed = 0;
const update = t => {
animationFrameId = requestAnimationFrame(update);
const delta = t - lastTime;
lastTime = t;
elapsed += delta * speed;
program.uniforms.uTime.value = elapsed * 0.001;
if (moveParticlesOnHover) {
particles.position.x = -mouseRef.current.x * particleHoverFactor;
particles.position.y = -mouseRef.current.y * particleHoverFactor;
} else {
particles.position.x = 0;
particles.position.y = 0;
}
if (!disableRotation) {
particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1;
particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15;
particles.rotation.z += 0.01 * speed;
}
renderer.render({ scene: particles, camera });
};
animationFrameId = requestAnimationFrame(update);
return () => {
window.removeEventListener('resize', resize);
if (moveParticlesOnHover) {
container.removeEventListener('mousemove', handleMouseMove);
}
cancelAnimationFrame(animationFrameId);
if (container.contains(gl.canvas)) {
container.removeChild(gl.canvas);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
particleCount,
particleSpread,
speed,
moveParticlesOnHover,
particleHoverFactor,
alphaParticles,
particleBaseSize,
sizeRandomness,
cameraDistance,
disableRotation,
pixelRatio
]);
return <div ref={containerRef} className={`particles-container ${className}`} />;
};
export default Particles;
Component CSS
.particles-container {
position: relative;
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: gradient blinds code: ## Integrate the <GradientBlinds /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: GradientBlinds
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import GradientBlinds from './GradientBlinds';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<GradientBlinds
gradientColors={['#FF9FFC', '#5227FF']}
angle={0}
noise={0.3}
blindCount={12}
blindMinWidth={50}
spotlightRadius={0.5}
spotlightSoftness={1}
spotlightOpacity={1}
mouseDampening={0.15}
distortAmount={0}
shineDirection="left"
mixBlendMode="lighten"
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| gradientColors | string[] | ['#FF9FFC', '#5227FF'] | Array of hex colors (up to 8) forming the animated gradient. If one color is provided it is duplicated. |
| angle | number | 0 | Rotation of the gradient in degrees (0 = horizontal left→right). |
| noise | number | 0.3 | Strength of per‑pixel noise added to the final color (0 = clean). |
| blindCount | number | 16 | Target number of vertical blinds. Acts as an upper bound when blindMinWidth is set. |
| blindMinWidth | number | 60 | Minimum pixel width for each blind. Reduces effective blindCount if necessary to satisfy this width. |
| mouseDampening | number | 0.15 | Easing time constant (seconds) for the spotlight to follow the cursor. 0 = immediate. |
| mirrorGradient | boolean | false | Creates a mirrored ping‑pong gradient progression instead of a linear wrap. |
| spotlightRadius | number | 0.5 | Normalized spotlight radius relative to the shorter canvas dimension. |
| spotlightSoftness | number | 1 | Falloff exponent for spotlight edge. Higher = sharper edge (values >1 increase contrast). |
| spotlightOpacity | number | 1 | Overall intensity multiplier for the spotlight highlight. |
| distortAmount | number | 0 | Sin/cos warp intensity applied to UVs for subtle wavy distortion. |
| shineDirection | 'left' | 'right' | left |
| mixBlendMode | string | 'lighten' | CSS mix-blend-mode applied to the canvas (e.g. 'screen', 'overlay', 'multiply'). |
| paused | boolean | false | If true, stops rendering updates (freezing the current frame). |
| dpr | number | window.devicePixelRatio | Overrides device pixel ratio; lower for performance, higher for sharpness. |
| className | string | — | Additional class names for the root container. |
Full Component Source
import { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle } from 'ogl';
import './GradientBlinds.css';
const MAX_COLORS = 8;
const hexToRGB = hex => {
const c = hex.replace('#', '').padEnd(6, '0');
const r = parseInt(c.slice(0, 2), 16) / 255;
const g = parseInt(c.slice(2, 4), 16) / 255;
const b = parseInt(c.slice(4, 6), 16) / 255;
return [r, g, b];
};
const prepStops = stops => {
const base = (stops && stops.length ? stops : ['#FF9FFC', '#5227FF']).slice(0, MAX_COLORS);
if (base.length === 1) base.push(base[0]);
while (base.length < MAX_COLORS) base.push(base[base.length - 1]);
const arr = [];
for (let i = 0; i < MAX_COLORS; i++) arr.push(hexToRGB(base[i]));
const count = Math.max(2, Math.min(MAX_COLORS, stops?.length ?? 2));
return { arr, count };
};
const GradientBlinds = ({
className,
dpr,
paused = false,
gradientColors,
angle = 0,
noise = 0.3,
blindCount = 16,
blindMinWidth = 60,
mouseDampening = 0.15,
mirrorGradient = false,
spotlightRadius = 0.5,
spotlightSoftness = 1,
spotlightOpacity = 1,
distortAmount = 0,
shineDirection = 'left',
mixBlendMode = 'lighten'
}) => {
const containerRef = useRef(null);
const rafRef = useRef(null);
const programRef = useRef(null);
const meshRef = useRef(null);
const geometryRef = useRef(null);
const rendererRef = useRef(null);
const mouseTargetRef = useRef([0, 0]);
const lastTimeRef = useRef(0);
const firstResizeRef = useRef(true);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const renderer = new Renderer({
dpr: dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1),
alpha: true,
antialias: true
});
rendererRef.current = renderer;
const gl = renderer.gl;
const canvas = gl.canvas;
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.display = 'block';
container.appendChild(canvas);
const vertex = `
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragment = `
#ifdef GL_ES
precision mediump float;
#endif
uniform vec3 iResolution;
uniform vec2 iMouse;
uniform float iTime;
uniform float uAngle;
uniform float uNoise;
uniform float uBlindCount;
uniform float uSpotlightRadius;
uniform float uSpotlightSoftness;
uniform float uSpotlightOpacity;
uniform float uMirror;
uniform float uDistort;
uniform float uShineFlip;
uniform vec3 uColor0;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform vec3 uColor3;
uniform vec3 uColor4;
uniform vec3 uColor5;
uniform vec3 uColor6;
uniform vec3 uColor7;
uniform int uColorCount;
varying vec2 vUv;
float rand(vec2 co){
return fract(sin(dot(co, vec2(12.9898,78.233))) * 43758.5453);
}
vec2 rotate2D(vec2 p, float a){
float c = cos(a);
float s = sin(a);
return mat2(c, -s, s, c) * p;
}
vec3 getGradientColor(float t){
float tt = clamp(t, 0.0, 1.0);
int count = uColorCount;
if (count < 2) count = 2;
float scaled = tt * float(count - 1);
float seg = floor(scaled);
float f = fract(scaled);
if (seg < 1.0) return mix(uColor0, uColor1, f);
if (seg < 2.0 && count > 2) return mix(uColor1, uColor2, f);
if (seg < 3.0 && count > 3) return mix(uColor2, uColor3, f);
if (seg < 4.0 && count > 4) return mix(uColor3, uColor4, f);
if (seg < 5.0 && count > 5) return mix(uColor4, uColor5, f);
if (seg < 6.0 && count > 6) return mix(uColor5, uColor6, f);
if (seg < 7.0 && count > 7) return mix(uColor6, uColor7, f);
if (count > 7) return uColor7;
if (count > 6) return uColor6;
if (count > 5) return uColor5;
if (count > 4) return uColor4;
if (count > 3) return uColor3;
if (count > 2) return uColor2;
return uColor1;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv0 = fragCoord.xy / iResolution.xy;
float aspect = iResolution.x / iResolution.y;
vec2 p = uv0 * 2.0 - 1.0;
p.x *= aspect;
vec2 pr = rotate2D(p, uAngle);
pr.x /= aspect;
vec2 uv = pr * 0.5 + 0.5;
vec2 uvMod = uv;
if (uDistort > 0.0) {
float a = uvMod.y * 6.0;
float b = uvMod.x * 6.0;
float w = 0.01 * uDistort;
uvMod.x += sin(a) * w;
uvMod.y += cos(b) * w;
}
float t = uvMod.x;
if (uMirror > 0.5) {
t = 1.0 - abs(1.0 - 2.0 * fract(t));
}
vec3 base = getGradientColor(t);
vec2 offset = vec2(iMouse.x/iResolution.x, iMouse.y/iResolution.y);
float d = length(uv0 - offset);
float r = max(uSpotlightRadius, 1e-4);
float dn = d / r;
float spot = (1.0 - 2.0 * pow(dn, uSpotlightSoftness)) * uSpotlightOpacity;
vec3 cir = vec3(spot);
float stripe = fract(uvMod.x * max(uBlindCount, 1.0));
if (uShineFlip > 0.5) stripe = 1.0 - stripe;
vec3 ran = vec3(stripe);
vec3 col = cir + base - ran;
col += (rand(gl_FragCoord.xy + iTime) - 0.5) * uNoise;
fragColor = vec4(col, 1.0);
}
void main() {
vec4 color;
mainImage(color, vUv * iResolution.xy);
gl_FragColor = color;
}
`;
const { arr: colorArr, count: colorCount } = prepStops(gradientColors);
const uniforms = {
iResolution: {
value: [gl.drawingBufferWidth, gl.drawingBufferHeight, 1]
},
iMouse: { value: [0, 0] },
iTime: { value: 0 },
uAngle: { value: (angle * Math.PI) / 180 },
uNoise: { value: noise },
uBlindCount: { value: Math.max(1, blindCount) },
uSpotlightRadius: { value: spotlightRadius },
uSpotlightSoftness: { value: spotlightSoftness },
uSpotlightOpacity: { value: spotlightOpacity },
uMirror: { value: mirrorGradient ? 1 : 0 },
uDistort: { value: distortAmount },
uShineFlip: { value: shineDirection === 'right' ? 1 : 0 },
uColor0: { value: colorArr[0] },
uColor1: { value: colorArr[1] },
uColor2: { value: colorArr[2] },
uColor3: { value: colorArr[3] },
uColor4: { value: colorArr[4] },
uColor5: { value: colorArr[5] },
uColor6: { value: colorArr[6] },
uColor7: { value: colorArr[7] },
uColorCount: { value: colorCount }
};
const program = new Program(gl, {
vertex,
fragment,
uniforms
});
programRef.current = program;
const geometry = new Triangle(gl);
geometryRef.current = geometry;
const mesh = new Mesh(gl, { geometry, program });
meshRef.current = mesh;
const resize = () => {
const rect = container.getBoundingClientRect();
renderer.setSize(rect.width, rect.height);
uniforms.iResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight, 1];
if (blindMinWidth && blindMinWidth > 0) {
const maxByMinWidth = Math.max(1, Math.floor(rect.width / blindMinWidth));
const effective = blindCount ? Math.min(blindCount, maxByMinWidth) : maxByMinWidth;
uniforms.uBlindCount.value = Math.max(1, effective);
} else {
uniforms.uBlindCount.value = Math.max(1, blindCount);
}
if (firstResizeRef.current) {
firstResizeRef.current = false;
const cx = gl.drawingBufferWidth / 2;
const cy = gl.drawingBufferHeight / 2;
uniforms.iMouse.value = [cx, cy];
mouseTargetRef.current = [cx, cy];
}
};
resize();
const ro = new ResizeObserver(resize);
ro.observe(container);
const onPointerMove = e => {
const rect = canvas.getBoundingClientRect();
const scale = renderer.dpr || 1;
const x = (e.clientX - rect.left) * scale;
const y = (rect.height - (e.clientY - rect.top)) * scale;
mouseTargetRef.current = [x, y];
if (mouseDampening <= 0) {
uniforms.iMouse.value = [x, y];
}
};
canvas.addEventListener('pointermove', onPointerMove);
const loop = t => {
rafRef.current = requestAnimationFrame(loop);
uniforms.iTime.value = t * 0.001;
if (mouseDampening > 0) {
if (!lastTimeRef.current) lastTimeRef.current = t;
const dt = (t - lastTimeRef.current) / 1000;
lastTimeRef.current = t;
const tau = Math.max(1e-4, mouseDampening);
let factor = 1 - Math.exp(-dt / tau);
if (factor > 1) factor = 1;
const target = mouseTargetRef.current;
const cur = uniforms.iMouse.value;
cur[0] += (target[0] - cur[0]) * factor;
cur[1] += (target[1] - cur[1]) * factor;
} else {
lastTimeRef.current = t;
}
if (!paused && programRef.current && meshRef.current) {
try {
renderer.render({ scene: meshRef.current });
} catch (e) {
console.error(e);
}
}
};
rafRef.current = requestAnimationFrame(loop);
return () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
canvas.removeEventListener('pointermove', onPointerMove);
ro.disconnect();
if (canvas.parentElement === container) {
container.removeChild(canvas);
}
const callIfFn = (obj, key) => {
if (obj && typeof obj[key] === 'function') {
obj[key].call(obj);
}
};
callIfFn(programRef.current, 'remove');
callIfFn(geometryRef.current, 'remove');
callIfFn(meshRef.current, 'remove');
callIfFn(rendererRef.current, 'destroy');
programRef.current = null;
geometryRef.current = null;
meshRef.current = null;
rendererRef.current = null;
};
}, [
dpr,
paused,
gradientColors,
angle,
noise,
blindCount,
blindMinWidth,
mouseDampening,
mirrorGradient,
spotlightRadius,
spotlightSoftness,
spotlightOpacity,
distortAmount,
shineDirection
]);
return (
<div
ref={containerRef}
className={`gradient-blinds-container ${className}`}
style={{
...(mixBlendMode && {
mixBlendMode: mixBlendMode
})
}}
/>
);
};
export default GradientBlinds;
Component CSS
.gradient-blinds-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: grainient code: ## Integrate the <Grainient /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Grainient
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Grainient from './Grainient';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Grainient
color1="#FF9FFC"
color2="#5227FF"
color3="#B497CF"
timeSpeed={0.25}
colorBalance={0.0}
warpStrength={1.0}
warpFrequency={5.0}
warpSpeed={2.0}
warpAmplitude={50.0}
blendAngle={0.0}
blendSoftness={0.05}
rotationAmount={500.0}
noiseScale={2.0}
grainAmount={0.1}
grainScale={2.0}
grainAnimated={false}
contrast={1.5}
gamma={1.0}
saturation={1.0}
centerX={0.0}
centerY={0.0}
zoom={0.9}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| color1 | string | '#FF9FFC' | Primary light color used in the gradient blend. |
| color2 | string | '#5227FF' | Secondary accent color used in the gradient blend. |
| color3 | string | '#B497CF' | Deep base color used in the gradient blend. |
| timeSpeed | number | 0.25 | Animation speed multiplier for the gradient motion. |
| colorBalance | number | 0.0 | Shifts the palette balance toward dark or lighter tones. |
| warpStrength | number | 1.0 | Strength of the wave warp distortion (0 = none). |
| warpFrequency | number | 5.0 | Frequency of the wave warp. |
| warpSpeed | number | 2.0 | Speed multiplier for the warp animation. |
| warpAmplitude | number | 50.0 | Base amplitude for the warp distortion. |
| blendAngle | number | 0.0 | Rotation angle for the color blend axis (degrees). |
| blendSoftness | number | 0.05 | Softens the blend edges between color layers. |
| rotationAmount | number | 500.0 | Rotation amount driven by noise. |
| noiseScale | number | 2.0 | Scales the noise frequency that drives rotation. |
| grainAmount | number | 0.1 | Amount of film grain applied to the gradient. |
| grainScale | number | 2.0 | Scale of the grain pattern. |
| grainAnimated | boolean | false | Animate grain over time. |
| contrast | number | 1.5 | Overall contrast applied to the final color. |
| gamma | number | 1.0 | Gamma correction for the final color. |
| saturation | number | 1.0 | Saturation amount for the final color. |
| centerX | number | 0.0 | Horizontal offset of the gradient center. |
| centerY | number | 0.0 | Vertical offset of the gradient center. |
| zoom | number | 0.9 | Zoom level for the gradient field. |
| className | string | '' | Additional CSS classes applied to the container. |
Full Component Source
import { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle } from 'ogl';
import './Grainient.css';
const hexToRgb = hex => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!result) return [1, 1, 1];
return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];
};
const vertex = `#version 300 es
in vec2 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragment = `#version 300 es
precision highp float;
uniform vec2 iResolution;
uniform float iTime;
uniform float uTimeSpeed;
uniform float uColorBalance;
uniform float uWarpStrength;
uniform float uWarpFrequency;
uniform float uWarpSpeed;
uniform float uWarpAmplitude;
uniform float uBlendAngle;
uniform float uBlendSoftness;
uniform float uRotationAmount;
uniform float uNoiseScale;
uniform float uGrainAmount;
uniform float uGrainScale;
uniform float uGrainAnimated;
uniform float uContrast;
uniform float uGamma;
uniform float uSaturation;
uniform vec2 uCenterOffset;
uniform float uZoom;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform vec3 uColor3;
out vec4 fragColor;
#define S(a,b,t) smoothstep(a,b,t)
mat2 Rot(float a){float s=sin(a),c=cos(a);return mat2(c,-s,s,c);}
vec2 hash(vec2 p){p=vec2(dot(p,vec2(2127.1,81.17)),dot(p,vec2(1269.5,283.37)));return fract(sin(p)*43758.5453);}
float noise(vec2 p){vec2 i=floor(p),f=fract(p),u=f*f*(3.0-2.0*f);float n=mix(mix(dot(-1.0+2.0*hash(i+vec2(0.0,0.0)),f-vec2(0.0,0.0)),dot(-1.0+2.0*hash(i+vec2(1.0,0.0)),f-vec2(1.0,0.0)),u.x),mix(dot(-1.0+2.0*hash(i+vec2(0.0,1.0)),f-vec2(0.0,1.0)),dot(-1.0+2.0*hash(i+vec2(1.0,1.0)),f-vec2(1.0,1.0)),u.x),u.y);return 0.5+0.5*n;}
void mainImage(out vec4 o, vec2 C){
float t=iTime*uTimeSpeed;
vec2 uv=C/iResolution.xy;
float ratio=iResolution.x/iResolution.y;
vec2 tuv=uv-0.5+uCenterOffset;
tuv/=max(uZoom,0.001);
float degree=noise(vec2(t*0.1,tuv.x*tuv.y)*uNoiseScale);
tuv.y*=1.0/ratio;
tuv*=Rot(radians((degree-0.5)*uRotationAmount+180.0));
tuv.y*=ratio;
float frequency=uWarpFrequency;
float ws=max(uWarpStrength,0.001);
float amplitude=uWarpAmplitude/ws;
float warpTime=t*uWarpSpeed;
tuv.x+=sin(tuv.y*frequency+warpTime)/amplitude;
tuv.y+=sin(tuv.x*(frequency*1.5)+warpTime)/(amplitude*0.5);
vec3 colLav=uColor1;
vec3 colOrg=uColor2;
vec3 colDark=uColor3;
float b=uColorBalance;
float s=max(uBlendSoftness,0.0);
mat2 blendRot=Rot(radians(uBlendAngle));
float blendX=(tuv*blendRot).x;
float edge0=-0.3-b-s;
float edge1=0.2-b+s;
float v0=0.5-b+s;
float v1=-0.3-b-s;
vec3 layer1=mix(colDark,colOrg,S(edge0,edge1,blendX));
vec3 layer2=mix(colOrg,colLav,S(edge0,edge1,blendX));
vec3 col=mix(layer1,layer2,S(v0,v1,tuv.y));
vec2 grainUv=uv*max(uGrainScale,0.001);
if(uGrainAnimated>0.5){grainUv+=vec2(iTime*0.05);}
float grain=fract(sin(dot(grainUv,vec2(12.9898,78.233)))*43758.5453);
col+=(grain-0.5)*uGrainAmount;
col=(col-0.5)*uContrast+0.5;
float luma=dot(col,vec3(0.2126,0.7152,0.0722));
col=mix(vec3(luma),col,uSaturation);
col=pow(max(col,0.0),vec3(1.0/max(uGamma,0.001)));
col=clamp(col,0.0,1.0);
o=vec4(col,1.0);
}
void main(){
vec4 o=vec4(0.0);
mainImage(o,gl_FragCoord.xy);
fragColor=o;
}
`;
// Keep renderer/program alive across re-renders so Effect 2 can update
// uniforms without ever rebuilding the WebGL context.
const ctxMap = new WeakMap();
const Grainient = ({
timeSpeed = 0.25,
colorBalance = 0.0,
warpStrength = 1.0,
warpFrequency = 5.0,
warpSpeed = 2.0,
warpAmplitude = 50.0,
blendAngle = 0.0,
blendSoftness = 0.05,
rotationAmount = 500.0,
noiseScale = 2.0,
grainAmount = 0.1,
grainScale = 2.0,
grainAnimated = false,
contrast = 1.5,
gamma = 1.0,
saturation = 1.0,
centerX = 0.0,
centerY = 0.0,
zoom = 0.9,
color1 = '#FF9FFC',
color2 = '#5227FF',
color3 = '#B497CF',
className = ''
}) => {
const containerRef = useRef(null);
// Effect 1: build WebGL context once, pause when offscreen / tab hidden
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const renderer = new Renderer({
webgl: 2,
alpha: true,
antialias: false,
dpr: Math.min(window.devicePixelRatio || 1, 2)
});
const gl = renderer.gl;
const canvas = gl.canvas;
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.display = 'block';
container.appendChild(canvas);
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex,
fragment,
uniforms: {
iTime: { value: 0 },
iResolution: { value: new Float32Array([1, 1]) },
uTimeSpeed: { value: 0.25 },
uColorBalance: { value: 0.0 },
uWarpStrength: { value: 1.0 },
uWarpFrequency: { value: 5.0 },
uWarpSpeed: { value: 2.0 },
uWarpAmplitude: { value: 50.0 },
uBlendAngle: { value: 0.0 },
uBlendSoftness: { value: 0.05 },
uRotationAmount: { value: 500.0 },
uNoiseScale: { value: 2.0 },
uGrainAmount: { value: 0.1 },
uGrainScale: { value: 2.0 },
uGrainAnimated: { value: 0.0 },
uContrast: { value: 1.5 },
uGamma: { value: 1.0 },
uSaturation: { value: 1.0 },
uCenterOffset: { value: new Float32Array([0, 0]) },
uZoom: { value: 0.9 },
uColor1: { value: new Float32Array([1, 1, 1]) },
uColor2: { value: new Float32Array([1, 1, 1]) },
uColor3: { value: new Float32Array([1, 1, 1]) }
}
});
const mesh = new Mesh(gl, { geometry, program });
ctxMap.set(container, { renderer, program, mesh });
const setSize = () => {
const rect = container.getBoundingClientRect();
const w = Math.max(1, Math.floor(rect.width));
const h = Math.max(1, Math.floor(rect.height));
renderer.setSize(w, h);
const res = program.uniforms.iResolution.value;
res[0] = gl.drawingBufferWidth;
res[1] = gl.drawingBufferHeight;
renderer.render({ scene: mesh });
};
const ro = new ResizeObserver(setSize);
ro.observe(container);
setSize();
let raf = 0;
let isVisible = true;
let isPageVisible = !document.hidden;
const t0 = performance.now();
const loop = t => {
program.uniforms.iTime.value = (t - t0) * 0.001;
renderer.render({ scene: mesh });
raf = requestAnimationFrame(loop);
};
const tryStart = () => {
if (isVisible && isPageVisible && raf === 0) raf = requestAnimationFrame(loop);
};
const tryStop = () => {
if (raf !== 0) { cancelAnimationFrame(raf); raf = 0; }
};
const io = new IntersectionObserver(
([entry]) => { isVisible = entry.isIntersecting; isVisible ? tryStart() : tryStop(); },
{ threshold: 0 }
);
io.observe(container);
const onVisibility = () => {
isPageVisible = !document.hidden;
isPageVisible ? tryStart() : tryStop();
};
document.addEventListener('visibilitychange', onVisibility);
tryStart();
return () => {
tryStop();
ro.disconnect();
io.disconnect();
document.removeEventListener('visibilitychange', onVisibility);
ctxMap.delete(container);
try { container.removeChild(canvas); } catch { /* ignore */ }
};
}, []); // renderer created once
// Effect 2: sync props to uniforms — zero GPU cost, no teardown
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const ctx = ctxMap.get(container);
if (!ctx) return;
const { program } = ctx;
const u = program.uniforms;
u.uTimeSpeed.value = timeSpeed;
u.uColorBalance.value = colorBalance;
u.uWarpStrength.value = warpStrength;
u.uWarpFrequency.value = warpFrequency;
u.uWarpSpeed.value = warpSpeed;
u.uWarpAmplitude.value = warpAmplitude;
u.uBlendAngle.value = blendAngle;
u.uBlendSoftness.value = blendSoftness;
u.uRotationAmount.value = rotationAmount;
u.uNoiseScale.value = noiseScale;
u.uGrainAmount.value = grainAmount;
u.uGrainScale.value = grainScale;
u.uGrainAnimated.value = grainAnimated ? 1.0 : 0.0;
u.uContrast.value = contrast;
u.uGamma.value = gamma;
u.uSaturation.value = saturation;
u.uCenterOffset.value = new Float32Array([centerX, centerY]);
u.uZoom.value = zoom;
u.uColor1.value = new Float32Array(hexToRgb(color1));
u.uColor2.value = new Float32Array(hexToRgb(color2));
u.uColor3.value = new Float32Array(hexToRgb(color3));
}, [
timeSpeed, colorBalance, warpStrength, warpFrequency, warpSpeed,
warpAmplitude, blendAngle, blendSoftness, rotationAmount, noiseScale,
grainAmount, grainScale, grainAnimated, contrast, gamma, saturation,
centerX, centerY, zoom, color1, color2, color3
]);
return <div ref={containerRef} className={`grainient-container ${className}`.trim()} />;
};
export default Grainient;Component CSS
.grainient-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: grid scan code: ## Integrate the <GridScan /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: GridScan
Variant: JavaScript + CSS
Dependencies: three face-api.js
Usage Example
import GridScan from './GridScan';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<GridScan
sensitivity={0.55}
lineThickness={1}
linesColor="#2F293A"
gridScale={0.1}
scanColor="#FF9FFC"
scanOpacity={0.4}
enablePost
bloomIntensity={0.6}
chromaticAberration={0.002}
noiseIntensity={0.01}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| enableWebcam | boolean | false | Enable face tracking via webcam. |
| showPreview | boolean | false | Show webcam preview/debug HUD. |
| modelsPath | string | CDN URL | Path/URL to face-api.js models. |
| sensitivity | number | 0.55 | Overall responsiveness to input. |
| lineThickness | number | 1 | Grid line thickness. |
| linesColor | string | '#2F293A' | Color of the grid lines. |
| gridScale | number | 0.1 | Grid spacing scale (smaller = denser). |
| lineStyle | 'solid' | 'dashed' | 'dotted' |
| lineJitter | number | 0.1 | Animated jitter along the grid lines. |
| enablePost | boolean | true | Enable post-processing effects. |
| bloomIntensity | number | 0 | Bloom strength. |
| bloomThreshold | number | 0 | Bloom luminance threshold. |
| bloomSmoothing | number | 0 | Bloom threshold smoothing. |
| chromaticAberration | number | 0.002 | Chromatic aberration offset (post). |
| noiseIntensity | number | 0.01 | Additive film grain intensity. |
| scanColor | string | '#FF9FFC' | Color of the scan beam/aura. |
| scanOpacity | number | 0.4 | Opacity of the scan effect. |
| scanDirection | 'forward' | 'backward' | 'pingpong' |
| scanSoftness | number | 2 | Softness of scan band edges. |
| scanGlow | number | 0.5 | Relative width/intensity of glow. |
| scanPhaseTaper | number | 0.9 | Fade-in/out window for the phase. |
| scanDuration | number | 2.0 | Duration of a scan cycle (seconds). |
| scanDelay | number | 2.0 | Delay between scan cycles (seconds). |
| enableGyro | boolean | false | Use device orientation for input. |
| scanOnClick | boolean | false | Trigger a scan when clicking. |
| snapBackDelay | number | 250 | Delay (ms) before input recenters. |
| className | string | '' | Additional CSS classes. |
| style | React.CSSProperties | {} | Inline style overrides. |
Full Component Source
import * as faceapi from 'face-api.js';
import { BloomEffect, ChromaticAberrationEffect, EffectComposer, EffectPass, RenderPass } from 'postprocessing';
import { useEffect, useRef, useState } from 'react';
import * as THREE from 'three';
import './GridScan.css';
const vert = `
varying vec2 vUv;
void main(){
vUv = uv;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
`;
const frag = `
precision highp float;
uniform vec3 iResolution;
uniform float iTime;
uniform vec2 uSkew;
uniform float uTilt;
uniform float uYaw;
uniform float uLineThickness;
uniform vec3 uLinesColor;
uniform vec3 uScanColor;
uniform float uGridScale;
uniform float uLineStyle;
uniform float uLineJitter;
uniform float uScanOpacity;
uniform float uScanDirection;
uniform float uNoise;
uniform float uBloomOpacity;
uniform float uScanGlow;
uniform float uScanSoftness;
uniform float uPhaseTaper;
uniform float uScanDuration;
uniform float uScanDelay;
varying vec2 vUv;
uniform float uScanStarts[8];
uniform float uScanCount;
const int MAX_SCANS = 8;
float smoother01(float a, float b, float x){
float t = clamp((x - a) / max(1e-5, (b - a)), 0.0, 1.0);
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
vec2 p = (2.0 * fragCoord - iResolution.xy) / iResolution.y;
vec3 ro = vec3(0.0);
vec3 rd = normalize(vec3(p, 2.0));
float cR = cos(uTilt), sR = sin(uTilt);
rd.xy = mat2(cR, -sR, sR, cR) * rd.xy;
float cY = cos(uYaw), sY = sin(uYaw);
rd.xz = mat2(cY, -sY, sY, cY) * rd.xz;
vec2 skew = clamp(uSkew, vec2(-0.7), vec2(0.7));
rd.xy += skew * rd.z;
vec3 color = vec3(0.0);
float minT = 1e20;
float gridScale = max(1e-5, uGridScale);
float fadeStrength = 2.0;
vec2 gridUV = vec2(0.0);
float hitIsY = 1.0;
for (int i = 0; i < 4; i++)
{
float isY = float(i < 2);
float pos = mix(-0.2, 0.2, float(i)) * isY + mix(-0.5, 0.5, float(i - 2)) * (1.0 - isY);
float num = pos - (isY * ro.y + (1.0 - isY) * ro.x);
float den = isY * rd.y + (1.0 - isY) * rd.x;
float t = num / den;
vec3 h = ro + rd * t;
float depthBoost = smoothstep(0.0, 3.0, h.z);
h.xy += skew * 0.15 * depthBoost;
bool use = t > 0.0 && t < minT;
gridUV = use ? mix(h.zy, h.xz, isY) / gridScale : gridUV;
minT = use ? t : minT;
hitIsY = use ? isY : hitIsY;
}
vec3 hit = ro + rd * minT;
float dist = length(hit - ro);
float jitterAmt = clamp(uLineJitter, 0.0, 1.0);
if (jitterAmt > 0.0) {
vec2 j = vec2(
sin(gridUV.y * 2.7 + iTime * 1.8),
cos(gridUV.x * 2.3 - iTime * 1.6)
) * (0.15 * jitterAmt);
gridUV += j;
}
float fx = fract(gridUV.x);
float fy = fract(gridUV.y);
float ax = min(fx, 1.0 - fx);
float ay = min(fy, 1.0 - fy);
float wx = fwidth(gridUV.x);
float wy = fwidth(gridUV.y);
float halfPx = max(0.0, uLineThickness) * 0.5;
float tx = halfPx * wx;
float ty = halfPx * wy;
float aax = wx;
float aay = wy;
float lineX = 1.0 - smoothstep(tx, tx + aax, ax);
float lineY = 1.0 - smoothstep(ty, ty + aay, ay);
if (uLineStyle > 0.5) {
float dashRepeat = 4.0;
float dashDuty = 0.5;
float vy = fract(gridUV.y * dashRepeat);
float vx = fract(gridUV.x * dashRepeat);
float dashMaskY = step(vy, dashDuty);
float dashMaskX = step(vx, dashDuty);
if (uLineStyle < 1.5) {
lineX *= dashMaskY;
lineY *= dashMaskX;
} else {
float dotRepeat = 6.0;
float dotWidth = 0.18;
float cy = abs(fract(gridUV.y * dotRepeat) - 0.5);
float cx = abs(fract(gridUV.x * dotRepeat) - 0.5);
float dotMaskY = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.y * dotRepeat), cy);
float dotMaskX = 1.0 - smoothstep(dotWidth, dotWidth + fwidth(gridUV.x * dotRepeat), cx);
lineX *= dotMaskY;
lineY *= dotMaskX;
}
}
float primaryMask = max(lineX, lineY);
vec2 gridUV2 = (hitIsY > 0.5 ? hit.xz : hit.zy) / gridScale;
if (jitterAmt > 0.0) {
vec2 j2 = vec2(
cos(gridUV2.y * 2.1 - iTime * 1.4),
sin(gridUV2.x * 2.5 + iTime * 1.7)
) * (0.15 * jitterAmt);
gridUV2 += j2;
}
float fx2 = fract(gridUV2.x);
float fy2 = fract(gridUV2.y);
float ax2 = min(fx2, 1.0 - fx2);
float ay2 = min(fy2, 1.0 - fy2);
float wx2 = fwidth(gridUV2.x);
float wy2 = fwidth(gridUV2.y);
float tx2 = halfPx * wx2;
float ty2 = halfPx * wy2;
float aax2 = wx2;
float aay2 = wy2;
float lineX2 = 1.0 - smoothstep(tx2, tx2 + aax2, ax2);
float lineY2 = 1.0 - smoothstep(ty2, ty2 + aay2, ay2);
if (uLineStyle > 0.5) {
float dashRepeat2 = 4.0;
float dashDuty2 = 0.5;
float vy2m = fract(gridUV2.y * dashRepeat2);
float vx2m = fract(gridUV2.x * dashRepeat2);
float dashMaskY2 = step(vy2m, dashDuty2);
float dashMaskX2 = step(vx2m, dashDuty2);
if (uLineStyle < 1.5) {
lineX2 *= dashMaskY2;
lineY2 *= dashMaskX2;
} else {
float dotRepeat2 = 6.0;
float dotWidth2 = 0.18;
float cy2 = abs(fract(gridUV2.y * dotRepeat2) - 0.5);
float cx2 = abs(fract(gridUV2.x * dotRepeat2) - 0.5);
float dotMaskY2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.y * dotRepeat2), cy2);
float dotMaskX2 = 1.0 - smoothstep(dotWidth2, dotWidth2 + fwidth(gridUV2.x * dotRepeat2), cx2);
lineX2 *= dotMaskY2;
lineY2 *= dotMaskX2;
}
}
float altMask = max(lineX2, lineY2);
float edgeDistX = min(abs(hit.x - (-0.5)), abs(hit.x - 0.5));
float edgeDistY = min(abs(hit.y - (-0.2)), abs(hit.y - 0.2));
float edgeDist = mix(edgeDistY, edgeDistX, hitIsY);
float edgeGate = 1.0 - smoothstep(gridScale * 0.5, gridScale * 2.0, edgeDist);
altMask *= edgeGate;
float lineMask = max(primaryMask, altMask);
float fade = exp(-dist * fadeStrength);
float dur = max(0.05, uScanDuration);
float del = max(0.0, uScanDelay);
float scanZMax = 2.0;
float widthScale = max(0.1, uScanGlow);
float sigma = max(0.001, 0.18 * widthScale * uScanSoftness);
float sigmaA = sigma * 2.0;
float combinedPulse = 0.0;
float combinedAura = 0.0;
float cycle = dur + del;
float tCycle = mod(iTime, cycle);
float scanPhase = clamp((tCycle - del) / dur, 0.0, 1.0);
float phase = scanPhase;
if (uScanDirection > 0.5 && uScanDirection < 1.5) {
phase = 1.0 - phase;
} else if (uScanDirection > 1.5) {
float t2 = mod(max(0.0, iTime - del), 2.0 * dur);
phase = (t2 < dur) ? (t2 / dur) : (1.0 - (t2 - dur) / dur);
}
float scanZ = phase * scanZMax;
float dz = abs(hit.z - scanZ);
float lineBand = exp(-0.5 * (dz * dz) / (sigma * sigma));
float taper = clamp(uPhaseTaper, 0.0, 0.49);
float headW = taper;
float tailW = taper;
float headFade = smoother01(0.0, headW, phase);
float tailFade = 1.0 - smoother01(1.0 - tailW, 1.0, phase);
float phaseWindow = headFade * tailFade;
float pulseBase = lineBand * phaseWindow;
combinedPulse += pulseBase * clamp(uScanOpacity, 0.0, 1.0);
float auraBand = exp(-0.5 * (dz * dz) / (sigmaA * sigmaA));
combinedAura += (auraBand * 0.25) * phaseWindow * clamp(uScanOpacity, 0.0, 1.0);
for (int i = 0; i < MAX_SCANS; i++) {
if (float(i) >= uScanCount) break;
float tActiveI = iTime - uScanStarts[i];
float phaseI = clamp(tActiveI / dur, 0.0, 1.0);
if (uScanDirection > 0.5 && uScanDirection < 1.5) {
phaseI = 1.0 - phaseI;
} else if (uScanDirection > 1.5) {
phaseI = (phaseI < 0.5) ? (phaseI * 2.0) : (1.0 - (phaseI - 0.5) * 2.0);
}
float scanZI = phaseI * scanZMax;
float dzI = abs(hit.z - scanZI);
float lineBandI = exp(-0.5 * (dzI * dzI) / (sigma * sigma));
float headFadeI = smoother01(0.0, headW, phaseI);
float tailFadeI = 1.0 - smoother01(1.0 - tailW, 1.0, phaseI);
float phaseWindowI = headFadeI * tailFadeI;
combinedPulse += lineBandI * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);
float auraBandI = exp(-0.5 * (dzI * dzI) / (sigmaA * sigmaA));
combinedAura += (auraBandI * 0.25) * phaseWindowI * clamp(uScanOpacity, 0.0, 1.0);
}
float lineVis = lineMask;
vec3 gridCol = uLinesColor * lineVis * fade;
vec3 scanCol = uScanColor * combinedPulse;
vec3 scanAura = uScanColor * combinedAura;
color = gridCol + scanCol + scanAura;
float n = fract(sin(dot(gl_FragCoord.xy + vec2(iTime * 123.4), vec2(12.9898,78.233))) * 43758.5453123);
color += (n - 0.5) * uNoise;
color = clamp(color, 0.0, 1.0);
float alpha = clamp(max(lineVis, combinedPulse), 0.0, 1.0);
float gx = 1.0 - smoothstep(tx * 2.0, tx * 2.0 + aax * 2.0, ax);
float gy = 1.0 - smoothstep(ty * 2.0, ty * 2.0 + aay * 2.0, ay);
float halo = max(gx, gy) * fade;
alpha = max(alpha, halo * clamp(uBloomOpacity, 0.0, 1.0));
fragColor = vec4(color, alpha);
}
void main(){
vec4 c;
mainImage(c, vUv * iResolution.xy);
gl_FragColor = c;
}
`;
export const GridScan = ({
enableWebcam = false,
showPreview = false,
modelsPath = 'https://cdn.jsdelivr.net/gh/justadudewhohacks/face-api.js@0.22.2/weights',
sensitivity = 0.55,
lineThickness = 1,
linesColor = '#2F293A',
scanColor = '#FF9FFC',
scanOpacity = 0.4,
gridScale = 0.1,
lineStyle = 'solid',
lineJitter = 0.1,
scanDirection = 'pingpong',
enablePost = true,
bloomIntensity = 0,
bloomThreshold = 0,
bloomSmoothing = 0,
chromaticAberration = 0.002,
noiseIntensity = 0.01,
scanGlow = 0.5,
scanSoftness = 2,
scanPhaseTaper = 0.9,
scanDuration = 2.0,
scanDelay = 2.0,
enableGyro = false,
scanOnClick = false,
snapBackDelay = 250,
className,
style
}) => {
const containerRef = useRef(null);
const videoRef = useRef(null);
const rendererRef = useRef(null);
const materialRef = useRef(null);
const composerRef = useRef(null);
const bloomRef = useRef(null);
const chromaRef = useRef(null);
const rafRef = useRef(null);
const [modelsReady, setModelsReady] = useState(false);
const [uiFaceActive, setUiFaceActive] = useState(false);
const lookTarget = useRef(new THREE.Vector2(0, 0));
const tiltTarget = useRef(0);
const yawTarget = useRef(0);
const lookCurrent = useRef(new THREE.Vector2(0, 0));
const lookVel = useRef(new THREE.Vector2(0, 0));
const tiltCurrent = useRef(0);
const tiltVel = useRef(0);
const yawCurrent = useRef(0);
const yawVel = useRef(0);
const MAX_SCANS = 8;
const scanStartsRef = useRef([]);
const pushScan = t => {
const arr = scanStartsRef.current.slice();
if (arr.length >= MAX_SCANS) arr.shift();
arr.push(t);
scanStartsRef.current = arr;
if (materialRef.current) {
const u = materialRef.current.uniforms;
const buf = new Array(MAX_SCANS).fill(0);
for (let i = 0; i < arr.length && i < MAX_SCANS; i++) buf[i] = arr[i];
u.uScanStarts.value = buf;
u.uScanCount.value = arr.length;
}
};
const bufX = useRef([]);
const bufY = useRef([]);
const bufT = useRef([]);
const bufYaw = useRef([]);
const s = THREE.MathUtils.clamp(sensitivity, 0, 1);
const skewScale = THREE.MathUtils.lerp(0.06, 0.2, s);
const tiltScale = THREE.MathUtils.lerp(0.12, 0.3, s);
const yawScale = THREE.MathUtils.lerp(0.1, 0.28, s);
const depthResponse = THREE.MathUtils.lerp(0.25, 0.45, s);
const smoothTime = THREE.MathUtils.lerp(0.45, 0.12, s);
const maxSpeed = Infinity;
const yBoost = THREE.MathUtils.lerp(1.2, 1.6, s);
useEffect(() => {
const el = containerRef.current;
if (!el) return;
let leaveTimer = null;
const onMove = e => {
if (uiFaceActive) return;
if (leaveTimer) {
clearTimeout(leaveTimer);
leaveTimer = null;
}
const rect = el.getBoundingClientRect();
const nx = ((e.clientX - rect.left) / rect.width) * 2 - 1;
const ny = -(((e.clientY - rect.top) / rect.height) * 2 - 1);
lookTarget.current.set(nx, ny);
};
const onClick = async () => {
const nowSec = performance.now() / 1000;
if (scanOnClick) pushScan(nowSec);
if (
enableGyro &&
typeof window !== 'undefined' &&
window.DeviceOrientationEvent &&
DeviceOrientationEvent.requestPermission
) {
try {
await DeviceOrientationEvent.requestPermission();
} catch {
// noop
}
}
};
const onEnter = () => {
if (leaveTimer) {
clearTimeout(leaveTimer);
leaveTimer = null;
}
};
const onLeave = () => {
if (uiFaceActive) return;
if (leaveTimer) clearTimeout(leaveTimer);
leaveTimer = window.setTimeout(
() => {
lookTarget.current.set(0, 0);
tiltTarget.current = 0;
yawTarget.current = 0;
},
Math.max(0, snapBackDelay || 0)
);
};
el.addEventListener('mousemove', onMove);
el.addEventListener('mouseenter', onEnter);
if (scanOnClick) el.addEventListener('click', onClick);
el.addEventListener('mouseleave', onLeave);
return () => {
el.removeEventListener('mousemove', onMove);
el.removeEventListener('mouseenter', onEnter);
el.removeEventListener('mouseleave', onLeave);
if (scanOnClick) el.removeEventListener('click', onClick);
if (leaveTimer) clearTimeout(leaveTimer);
};
}, [uiFaceActive, snapBackDelay, scanOnClick, enableGyro]);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
rendererRef.current = renderer;
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.NoToneMapping;
renderer.autoClear = false;
renderer.setClearColor(0x000000, 0);
container.appendChild(renderer.domElement);
const uniforms = {
iResolution: {
value: new THREE.Vector3(container.clientWidth, container.clientHeight, renderer.getPixelRatio())
},
iTime: { value: 0 },
uSkew: { value: new THREE.Vector2(0, 0) },
uTilt: { value: 0 },
uYaw: { value: 0 },
uLineThickness: { value: lineThickness },
uLinesColor: { value: srgbColor(linesColor) },
uScanColor: { value: srgbColor(scanColor) },
uGridScale: { value: gridScale },
uLineStyle: { value: lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0 },
uLineJitter: { value: Math.max(0, Math.min(1, lineJitter || 0)) },
uScanOpacity: { value: scanOpacity },
uNoise: { value: noiseIntensity },
uBloomOpacity: { value: bloomIntensity },
uScanGlow: { value: scanGlow },
uScanSoftness: { value: scanSoftness },
uPhaseTaper: { value: scanPhaseTaper },
uScanDuration: { value: scanDuration },
uScanDelay: { value: scanDelay },
uScanDirection: { value: scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0 },
uScanStarts: { value: new Array(MAX_SCANS).fill(0) },
uScanCount: { value: 0 }
};
const material = new THREE.ShaderMaterial({
uniforms,
vertexShader: vert,
fragmentShader: frag,
transparent: true,
depthWrite: false,
depthTest: false
});
materialRef.current = material;
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
scene.add(quad);
let composer = null;
if (enablePost) {
composer = new EffectComposer(renderer);
composerRef.current = composer;
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
const bloom = new BloomEffect({
intensity: 1.0,
luminanceThreshold: bloomThreshold,
luminanceSmoothing: bloomSmoothing
});
bloom.blendMode.opacity.value = Math.max(0, bloomIntensity);
bloomRef.current = bloom;
const chroma = new ChromaticAberrationEffect({
offset: new THREE.Vector2(chromaticAberration, chromaticAberration),
radialModulation: true,
modulationOffset: 0.0
});
chromaRef.current = chroma;
const effectPass = new EffectPass(camera, bloom, chroma);
effectPass.renderToScreen = true;
composer.addPass(effectPass);
}
const onResize = () => {
renderer.setSize(container.clientWidth, container.clientHeight);
material.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight, renderer.getPixelRatio());
if (composerRef.current) composerRef.current.setSize(container.clientWidth, container.clientHeight);
};
window.addEventListener('resize', onResize);
let last = performance.now();
const tick = () => {
const now = performance.now();
const dt = Math.max(0, Math.min(0.1, (now - last) / 1000));
last = now;
lookCurrent.current.copy(
smoothDampVec2(lookCurrent.current, lookTarget.current, lookVel.current, smoothTime, maxSpeed, dt)
);
const tiltSm = smoothDampFloat(
tiltCurrent.current,
tiltTarget.current,
{ v: tiltVel.current },
smoothTime,
maxSpeed,
dt
);
tiltCurrent.current = tiltSm.value;
tiltVel.current = tiltSm.v;
const yawSm = smoothDampFloat(
yawCurrent.current,
yawTarget.current,
{ v: yawVel.current },
smoothTime,
maxSpeed,
dt
);
yawCurrent.current = yawSm.value;
yawVel.current = yawSm.v;
const skew = new THREE.Vector2(lookCurrent.current.x * skewScale, -lookCurrent.current.y * yBoost * skewScale);
material.uniforms.uSkew.value.set(skew.x, skew.y);
material.uniforms.uTilt.value = tiltCurrent.current * tiltScale;
material.uniforms.uYaw.value = THREE.MathUtils.clamp(yawCurrent.current * yawScale, -0.6, 0.6);
material.uniforms.iTime.value = now / 1000;
renderer.clear(true, true, true);
if (composerRef.current) {
composerRef.current.render(dt);
} else {
renderer.render(scene, camera);
}
rafRef.current = requestAnimationFrame(tick);
};
rafRef.current = requestAnimationFrame(tick);
return () => {
if (rafRef.current) cancelAnimationFrame(rafRef.current);
window.removeEventListener('resize', onResize);
material.dispose();
quad.geometry.dispose();
if (composerRef.current) {
composerRef.current.dispose();
composerRef.current = null;
}
renderer.dispose();
renderer.forceContextLoss();
container.removeChild(renderer.domElement);
};
}, [
sensitivity,
lineThickness,
linesColor,
scanColor,
scanOpacity,
gridScale,
lineStyle,
lineJitter,
scanDirection,
enablePost,
noiseIntensity,
bloomIntensity,
scanGlow,
scanSoftness,
scanPhaseTaper,
scanDuration,
scanDelay,
bloomThreshold,
bloomSmoothing,
chromaticAberration,
smoothTime,
maxSpeed,
skewScale,
yBoost,
tiltScale,
yawScale
]);
useEffect(() => {
const m = materialRef.current;
if (m) {
const u = m.uniforms;
u.uLineThickness.value = lineThickness;
u.uLinesColor.value.copy(srgbColor(linesColor));
u.uScanColor.value.copy(srgbColor(scanColor));
u.uGridScale.value = gridScale;
u.uLineStyle.value = lineStyle === 'dashed' ? 1 : lineStyle === 'dotted' ? 2 : 0;
u.uLineJitter.value = Math.max(0, Math.min(1, lineJitter || 0));
u.uBloomOpacity.value = Math.max(0, bloomIntensity);
u.uNoise.value = Math.max(0, noiseIntensity);
u.uScanGlow.value = scanGlow;
u.uScanOpacity.value = Math.max(0, Math.min(1, scanOpacity));
u.uScanDirection.value = scanDirection === 'backward' ? 1 : scanDirection === 'pingpong' ? 2 : 0;
u.uScanSoftness.value = scanSoftness;
u.uPhaseTaper.value = scanPhaseTaper;
u.uScanDuration.value = Math.max(0.05, scanDuration);
u.uScanDelay.value = Math.max(0.0, scanDelay);
}
if (bloomRef.current) {
bloomRef.current.blendMode.opacity.value = Math.max(0, bloomIntensity);
bloomRef.current.luminanceMaterial.threshold = bloomThreshold;
bloomRef.current.luminanceMaterial.smoothing = bloomSmoothing;
}
if (chromaRef.current) {
chromaRef.current.offset.set(chromaticAberration, chromaticAberration);
}
}, [
lineThickness,
linesColor,
scanColor,
gridScale,
lineStyle,
lineJitter,
bloomIntensity,
bloomThreshold,
bloomSmoothing,
chromaticAberration,
noiseIntensity,
scanGlow,
scanOpacity,
scanDirection,
scanSoftness,
scanPhaseTaper,
scanDuration,
scanDelay
]);
useEffect(() => {
if (!enableGyro) return;
const handler = e => {
if (uiFaceActive) return;
const gamma = e.gamma ?? 0;
const beta = e.beta ?? 0;
const nx = THREE.MathUtils.clamp(gamma / 45, -1, 1);
const ny = THREE.MathUtils.clamp(-beta / 30, -1, 1);
lookTarget.current.set(nx, ny);
tiltTarget.current = THREE.MathUtils.degToRad(gamma) * 0.4;
};
window.addEventListener('deviceorientation', handler);
return () => {
window.removeEventListener('deviceorientation', handler);
};
}, [enableGyro, uiFaceActive]);
useEffect(() => {
let canceled = false;
const load = async () => {
try {
await Promise.all([
faceapi.nets.tinyFaceDetector.loadFromUri(modelsPath),
faceapi.nets.faceLandmark68TinyNet.loadFromUri(modelsPath)
]);
if (!canceled) setModelsReady(true);
} catch {
if (!canceled) setModelsReady(false);
}
};
load();
return () => {
canceled = true;
};
}, [modelsPath]);
useEffect(() => {
let stop = false;
let lastDetect = 0;
const video = videoRef.current;
const start = async () => {
if (!enableWebcam || !modelsReady) return;
if (!video) return;
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } },
audio: false
});
video.srcObject = stream;
await video.play();
} catch {
return;
}
const opts = new faceapi.TinyFaceDetectorOptions({ inputSize: 320, scoreThreshold: 0.5 });
const detect = async ts => {
if (stop) return;
if (ts - lastDetect >= 33) {
lastDetect = ts;
try {
const res = await faceapi.detectSingleFace(video, opts).withFaceLandmarks(true);
if (res && res.detection) {
const det = res.detection;
const box = det.box;
const vw = video.videoWidth || 1;
const vh = video.videoHeight || 1;
const cx = box.x + box.width * 0.5;
const cy = box.y + box.height * 0.5;
const nx = (cx / vw) * 2 - 1;
const ny = (cy / vh) * 2 - 1;
medianPush(bufX.current, nx, 5);
medianPush(bufY.current, ny, 5);
const nxm = median(bufX.current);
const nym = median(bufY.current);
const look = new THREE.Vector2(Math.tanh(nxm), Math.tanh(nym));
const faceSize = Math.min(1, Math.hypot(box.width / vw, box.height / vh));
const depthScale = 1 + depthResponse * (faceSize - 0.25);
lookTarget.current.copy(look.multiplyScalar(depthScale));
const leftEye = res.landmarks.getLeftEye();
const rightEye = res.landmarks.getRightEye();
const lc = centroid(leftEye);
const rc = centroid(rightEye);
const tilt = Math.atan2(rc.y - lc.y, rc.x - lc.x);
medianPush(bufT.current, tilt, 5);
tiltTarget.current = median(bufT.current);
const nose = res.landmarks.getNose();
const tip = nose[nose.length - 1] || nose[Math.floor(nose.length / 2)];
const jaw = res.landmarks.getJawOutline();
const leftCheek = jaw[3] || jaw[2];
const rightCheek = jaw[13] || jaw[14];
const dL = dist2(tip, leftCheek);
const dR = dist2(tip, rightCheek);
const eyeDist = Math.hypot(rc.x - lc.x, rc.y - lc.y) + 1e-6;
let yawSignal = THREE.MathUtils.clamp((dR - dL) / (eyeDist * 1.6), -1, 1);
yawSignal = Math.tanh(yawSignal);
medianPush(bufYaw.current, yawSignal, 5);
yawTarget.current = median(bufYaw.current);
setUiFaceActive(true);
} else {
setUiFaceActive(false);
}
} catch {
setUiFaceActive(false);
}
}
if ('requestVideoFrameCallback' in HTMLVideoElement.prototype) {
video.requestVideoFrameCallback(() => detect(performance.now()));
} else {
requestAnimationFrame(detect);
}
};
requestAnimationFrame(detect);
};
start();
return () => {
stop = true;
if (video) {
const stream = video.srcObject;
if (stream) stream.getTracks().forEach(t => t.stop());
video.pause();
video.srcObject = null;
}
};
}, [enableWebcam, modelsReady, depthResponse]);
return (
<div ref={containerRef} className={`gridscan${className ? ` ${className}` : ''}`} style={style}>
{showPreview && (
<div className="gridscan__preview">
<video ref={videoRef} muted playsInline autoPlay className="gridscan__video" />
<div className="gridscan__badge">
{enableWebcam
? modelsReady
? uiFaceActive
? 'Face: tracking'
: 'Face: searching'
: 'Loading models'
: 'Webcam disabled'}
</div>
</div>
)}
</div>
);
};
function srgbColor(hex) {
const c = new THREE.Color(hex);
return c.convertSRGBToLinear();
}
function smoothDampVec2(current, target, currentVelocity, smoothTime, maxSpeed, deltaTime) {
const out = current.clone();
smoothTime = Math.max(0.0001, smoothTime);
const omega = 2 / smoothTime;
const x = omega * deltaTime;
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
let change = current.clone().sub(target);
const originalTo = target.clone();
const maxChange = maxSpeed * smoothTime;
if (change.length() > maxChange) change.setLength(maxChange);
target = current.clone().sub(change);
const temp = currentVelocity.clone().addScaledVector(change, omega).multiplyScalar(deltaTime);
currentVelocity.sub(temp.clone().multiplyScalar(omega));
currentVelocity.multiplyScalar(exp);
out.copy(target.clone().add(change.add(temp).multiplyScalar(exp)));
const origMinusCurrent = originalTo.clone().sub(current);
const outMinusOrig = out.clone().sub(originalTo);
if (origMinusCurrent.dot(outMinusOrig) > 0) {
out.copy(originalTo);
currentVelocity.set(0, 0);
}
return out;
}
function smoothDampFloat(current, target, velRef, smoothTime, maxSpeed, deltaTime) {
smoothTime = Math.max(0.0001, smoothTime);
const omega = 2 / smoothTime;
const x = omega * deltaTime;
const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x);
let change = current - target;
const originalTo = target;
const maxChange = maxSpeed * smoothTime;
change = Math.sign(change) * Math.min(Math.abs(change), maxChange);
target = current - change;
const temp = (velRef.v + omega * change) * deltaTime;
velRef.v = (velRef.v - omega * temp) * exp;
let out = target + (change + temp) * exp;
const origMinusCurrent = originalTo - current;
const outMinusOrig = out - originalTo;
if (origMinusCurrent * outMinusOrig > 0) {
out = originalTo;
velRef.v = 0;
}
return { value: out, v: velRef.v };
}
function medianPush(buf, v, maxLen) {
buf.push(v);
if (buf.length > maxLen) buf.shift();
}
function median(buf) {
if (buf.length === 0) return 0;
const a = [...buf].sort((x, y) => x - y);
const mid = Math.floor(a.length / 2);
return a.length % 2 ? a[mid] : (a[mid - 1] + a[mid]) * 0.5;
}
function centroid(points) {
let x = 0,
y = 0;
const n = points.length || 1;
for (const p of points) {
x += p.x;
y += p.y;
}
return { x: x / n, y: y / n };
}
function dist2(a, b) {
return Math.hypot(a.x - b.x, a.y - b.y);
}
Component CSS
.gridscan {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.gridscan__preview {
position: absolute;
right: 12px;
bottom: 12px;
width: 220px;
height: 132px;
border-radius: 8px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.25);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
background: #000;
color: #fff;
font:
12px/1.2 system-ui,
-apple-system,
Segoe UI,
Roboto,
sans-serif;
pointer-events: none;
}
.gridscan__video {
width: 100%;
height: 100%;
object-fit: cover;
transform: scaleX(-1);
}
.gridscan__badge {
position: absolute;
left: 8px;
top: 8px;
padding: 2px 6px;
background: rgba(0, 0, 0, 0.5);
border-radius: 6px;
backdrop-filter: blur(4px);
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: beams code: ## Integrate the <Beams /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Beams
Variant: JavaScript + CSS
Dependencies: three @react-three/fiber @react-three/drei
Usage Example
import Beams from './Beams';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Beams
beamWidth={2}
beamHeight={15}
beamNumber={12}
lightColor="#ffffff"
speed={2}
noiseIntensity={1.75}
scale={0.2}
rotation={0}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| beamWidth | number | 2 | Width of each beam. |
| beamHeight | number | 15 | Height of each beam. |
| beamNumber | number | 12 | Number of beams to display. |
| lightColor | string | '#ffffff' | Color of the directional light. |
| speed | number | 2 | Speed of the animation. |
| noiseIntensity | number | 1.75 | Intensity of the noise effect overlay. |
| scale | number | 0.2 | Scale of the noise pattern. |
| rotation | number | 0 | Rotation of the entire beams system in degrees. |
Full Component Source
/* eslint-disable react/no-unknown-property */
import { forwardRef, useImperativeHandle, useEffect, useRef, useMemo } from 'react';
import * as THREE from 'three';
import { Canvas, useFrame } from '@react-three/fiber';
import { PerspectiveCamera } from '@react-three/drei';
import { degToRad } from 'three/src/math/MathUtils.js';
import './Beams.css';
function extendMaterial(BaseMaterial, cfg) {
const physical = THREE.ShaderLib.physical;
const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;
const baseDefines = physical.defines ?? {};
const uniforms = THREE.UniformsUtils.clone(baseUniforms);
const defaults = new BaseMaterial(cfg.material || {});
if (defaults.color) uniforms.diffuse.value = defaults.color;
if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;
if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;
if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;
if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;
Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {
uniforms[key] = u !== null && typeof u === 'object' && 'value' in u ? u : { value: u };
});
let vert = `${cfg.header}\n${cfg.vertexHeader ?? ''}\n${baseVert}`;
let frag = `${cfg.header}\n${cfg.fragmentHeader ?? ''}\n${baseFrag}`;
for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {
vert = vert.replace(inc, `${inc}\n${code}`);
}
for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {
frag = frag.replace(inc, `${inc}\n${code}`);
}
const mat = new THREE.ShaderMaterial({
defines: { ...baseDefines },
uniforms,
vertexShader: vert,
fragmentShader: frag,
lights: true,
fog: !!cfg.material?.fog
});
return mat;
}
const CanvasWrapper = ({ children }) => (
<Canvas dpr={[1, 2]} frameloop="always" className="beams-container">
{children}
</Canvas>
);
const hexToNormalizedRGB = hex => {
const clean = hex.replace('#', '');
const r = parseInt(clean.substring(0, 2), 16);
const g = parseInt(clean.substring(2, 4), 16);
const b = parseInt(clean.substring(4, 6), 16);
return [r / 255, g / 255, b / 255];
};
const noise = `
float random (in vec2 st) {
return fract(sin(dot(st.xy,
vec2(12.9898,78.233)))*
43758.5453123);
}
float noise (in vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) +
(c - a)* u.y * (1.0 - u.x) +
(d - b) * u.x * u.y;
}
vec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}
vec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}
vec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}
float cnoise(vec3 P){
vec3 Pi0 = floor(P);
vec3 Pi1 = Pi0 + vec3(1.0);
Pi0 = mod(Pi0, 289.0);
Pi1 = mod(Pi1, 289.0);
vec3 Pf0 = fract(P);
vec3 Pf1 = Pf0 - vec3(1.0);
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
vec4 iy = vec4(Pi0.yy, Pi1.yy);
vec4 iz0 = Pi0.zzzz;
vec4 iz1 = Pi1.zzzz;
vec4 ixy = permute(permute(ix) + iy);
vec4 ixy0 = permute(ixy + iz0);
vec4 ixy1 = permute(ixy + iz1);
vec4 gx0 = ixy0 / 7.0;
vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;
gx0 = fract(gx0);
vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
vec4 sz0 = step(gz0, vec4(0.0));
gx0 -= sz0 * (step(0.0, gx0) - 0.5);
gy0 -= sz0 * (step(0.0, gy0) - 0.5);
vec4 gx1 = ixy1 / 7.0;
vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;
gx1 = fract(gx1);
vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
vec4 sz1 = step(gz1, vec4(0.0));
gx1 -= sz1 * (step(0.0, gx1) - 0.5);
gy1 -= sz1 * (step(0.0, gy1) - 0.5);
vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);
vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));
g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;
vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));
g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;
float n000 = dot(g000, Pf0);
float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));
float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));
float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));
float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));
float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));
float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));
float n111 = dot(g111, Pf1);
vec3 fade_xyz = fade(Pf0);
vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);
vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);
float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);
return 2.2 * n_xyz;
}
`;
const Beams = ({
beamWidth = 2,
beamHeight = 15,
beamNumber = 12,
lightColor = '#ffffff',
speed = 2,
noiseIntensity = 1.75,
scale = 0.2,
rotation = 0
}) => {
const meshRef = useRef(null);
const beamMaterial = useMemo(
() =>
extendMaterial(THREE.MeshStandardMaterial, {
header: `
varying vec3 vEye;
varying float vNoise;
varying vec2 vUv;
varying vec3 vPosition;
uniform float time;
uniform float uSpeed;
uniform float uNoiseIntensity;
uniform float uScale;
${noise}`,
vertexHeader: `
float getPos(vec3 pos) {
vec3 noisePos =
vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;
return cnoise(noisePos);
}
vec3 getCurrentPos(vec3 pos) {
vec3 newpos = pos;
newpos.z += getPos(pos);
return newpos;
}
vec3 getNormal(vec3 pos) {
vec3 curpos = getCurrentPos(pos);
vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));
vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));
vec3 tangentX = normalize(nextposX - curpos);
vec3 tangentZ = normalize(nextposZ - curpos);
return normalize(cross(tangentZ, tangentX));
}`,
fragmentHeader: '',
vertex: {
'#include <begin_vertex>': `transformed.z += getPos(transformed.xyz);`,
'#include <beginnormal_vertex>': `objectNormal = getNormal(position.xyz);`
},
fragment: {
'#include <dithering_fragment>': `
float randomNoise = noise(gl_FragCoord.xy);
gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`
},
material: { fog: true },
uniforms: {
diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),
time: { shared: true, mixed: true, linked: true, value: 0 },
roughness: 0.3,
metalness: 0.3,
uSpeed: { shared: true, mixed: true, linked: true, value: speed },
envMapIntensity: 10,
uNoiseIntensity: noiseIntensity,
uScale: scale
}
}),
[speed, noiseIntensity, scale]
);
return (
<CanvasWrapper>
<group rotation={[0, 0, degToRad(rotation)]}>
<PlaneNoise ref={meshRef} material={beamMaterial} count={beamNumber} width={beamWidth} height={beamHeight} />
<DirLight color={lightColor} position={[0, 3, 10]} />
</group>
<ambientLight intensity={1} />
<color attach="background" args={['#000000']} />
<PerspectiveCamera makeDefault position={[0, 0, 20]} fov={30} />
</CanvasWrapper>
);
};
function createStackedPlanesBufferGeometry(n, width, height, spacing, heightSegments) {
const geometry = new THREE.BufferGeometry();
const numVertices = n * (heightSegments + 1) * 2;
const numFaces = n * heightSegments * 2;
const positions = new Float32Array(numVertices * 3);
const indices = new Uint32Array(numFaces * 3);
const uvs = new Float32Array(numVertices * 2);
let vertexOffset = 0;
let indexOffset = 0;
let uvOffset = 0;
const totalWidth = n * width + (n - 1) * spacing;
const xOffsetBase = -totalWidth / 2;
for (let i = 0; i < n; i++) {
const xOffset = xOffsetBase + i * (width + spacing);
const uvXOffset = Math.random() * 300;
const uvYOffset = Math.random() * 300;
for (let j = 0; j <= heightSegments; j++) {
const y = height * (j / heightSegments - 0.5);
const v0 = [xOffset, y, 0];
const v1 = [xOffset + width, y, 0];
positions.set([...v0, ...v1], vertexOffset * 3);
const uvY = j / heightSegments;
uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);
if (j < heightSegments) {
const a = vertexOffset,
b = vertexOffset + 1,
c = vertexOffset + 2,
d = vertexOffset + 3;
indices.set([a, b, c, c, b, d], indexOffset);
indexOffset += 6;
}
vertexOffset += 2;
uvOffset += 4;
}
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
geometry.computeVertexNormals();
return geometry;
}
const MergedPlanes = forwardRef(({ material, width, count, height }, ref) => {
const mesh = useRef(null);
useImperativeHandle(ref, () => mesh.current);
const geometry = useMemo(
() => createStackedPlanesBufferGeometry(count, width, height, 0, 100),
[count, width, height]
);
useFrame((_, delta) => {
mesh.current.material.uniforms.time.value += 0.1 * delta;
});
return <mesh ref={mesh} geometry={geometry} material={material} />;
});
MergedPlanes.displayName = 'MergedPlanes';
const PlaneNoise = forwardRef((props, ref) => (
<MergedPlanes ref={ref} material={props.material} width={props.width} count={props.count} height={props.height} />
));
PlaneNoise.displayName = 'PlaneNoise';
const DirLight = ({ position, color }) => {
const dir = useRef(null);
useEffect(() => {
if (!dir.current) return;
const cam = dir.current.shadow.camera;
if (!cam) return;
cam.top = 24;
cam.bottom = -24;
cam.left = -24;
cam.right = 24;
cam.far = 64;
dir.current.shadow.bias = -0.004;
}, []);
return <directionalLight ref={dir} color={color} intensity={1} position={position} />;
};
export default Beams;
Component CSS
.beams-container {
position: relative;
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: pixel snow code: ## Integrate the <PixelSnow /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: PixelSnow
Variant: JavaScript + CSS
Dependencies: three
Usage Example
import PixelSnow from './PixelSnow';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<PixelSnow
color="#ffffff"
flakeSize={0.01}
minFlakeSize={1.25}
pixelResolution={200}
speed={1.25}
density={0.3}
direction={125}
brightness={1}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| color | string | "#ffffff" | Color of the snowflakes (hex or CSS color) |
| flakeSize | number | 0.01 | Size of snowflakes in scene units |
| minFlakeSize | number | 1.25 | Minimum flake size in pixels on screen |
| pixelResolution | number | 200 | Pixel resolution - lower values create larger pixels for a more retro look |
| speed | number | 1.25 | Animation speed multiplier |
| depthFade | number | 8 | Depth fade intensity - higher values make distant flakes fade faster |
| farPlane | number | 20 | Far plane distance for rendering - higher values show more distant flakes |
| brightness | number | 1 | Overall brightness multiplier |
| gamma | number | 0.4545 | Gamma correction value for final color output |
| density | number | 0.3 | Probability of snowflakes appearing (0-1) - lower values = fewer flakes |
| variant | "square" | "round" | "snowflake" |
| direction | number | 125 | Wind direction angle in degrees (0-360) |
| className | string | "" | Additional CSS class name |
| style | object | {} | Additional inline styles |
Full Component Source
import { useCallback, useEffect, useMemo, useRef } from 'react';
import {
Color,
Mesh,
OrthographicCamera,
PlaneGeometry,
Scene,
ShaderMaterial,
Vector2,
Vector3,
WebGLRenderer
} from 'three';
import './PixelSnow.css';
const vertexShader = `
void main() {
gl_Position = vec4(position, 1.0);
}
`;
const fragmentShader = `
precision mediump float;
uniform float uTime;
uniform vec2 uResolution;
uniform float uFlakeSize;
uniform float uMinFlakeSize;
uniform float uPixelResolution;
uniform float uSpeed;
uniform float uDepthFade;
uniform float uFarPlane;
uniform vec3 uColor;
uniform float uBrightness;
uniform float uGamma;
uniform float uDensity;
uniform float uVariant;
uniform float uDirection;
// Precomputed constants
#define PI 3.14159265
#define PI_OVER_6 0.5235988
#define PI_OVER_3 1.0471976
#define INV_SQRT3 0.57735027
#define M1 1597334677U
#define M2 3812015801U
#define M3 3299493293U
#define F0 2.3283064e-10
// Optimized hash - inline multiplication
#define hash(n) (n * (n ^ (n >> 15)))
#define coord3(p) (uvec3(p).x * M1 ^ uvec3(p).y * M2 ^ uvec3(p).z * M3)
// Precomputed camera basis vectors (normalized vec3(1,1,1), vec3(1,0,-1))
const vec3 camK = vec3(0.57735027, 0.57735027, 0.57735027);
const vec3 camI = vec3(0.70710678, 0.0, -0.70710678);
const vec3 camJ = vec3(-0.40824829, 0.81649658, -0.40824829);
// Precomputed branch direction
const vec2 b1d = vec2(0.574, 0.819);
vec3 hash3(uint n) {
uvec3 hashed = hash(n) * uvec3(1U, 511U, 262143U);
return vec3(hashed) * F0;
}
float snowflakeDist(vec2 p) {
float r = length(p);
float a = atan(p.y, p.x);
a = abs(mod(a + PI_OVER_6, PI_OVER_3) - PI_OVER_6);
vec2 q = r * vec2(cos(a), sin(a));
float dMain = max(abs(q.y), max(-q.x, q.x - 1.0));
float b1t = clamp(dot(q - vec2(0.4, 0.0), b1d), 0.0, 0.4);
float dB1 = length(q - vec2(0.4, 0.0) - b1t * b1d);
float b2t = clamp(dot(q - vec2(0.7, 0.0), b1d), 0.0, 0.25);
float dB2 = length(q - vec2(0.7, 0.0) - b2t * b1d);
return min(dMain, min(dB1, dB2)) * 10.0;
}
void main() {
// Precompute reciprocals to avoid division
float invPixelRes = 1.0 / uPixelResolution;
float pixelSize = max(1.0, floor(0.5 + uResolution.x * invPixelRes));
float invPixelSize = 1.0 / pixelSize;
vec2 fragCoord = floor(gl_FragCoord.xy * invPixelSize);
vec2 res = uResolution * invPixelSize;
float invResX = 1.0 / res.x;
vec3 ray = normalize(vec3((fragCoord - res * 0.5) * invResX, 1.0));
ray = ray.x * camI + ray.y * camJ + ray.z * camK;
// Precompute time-based values
float timeSpeed = uTime * uSpeed;
float windX = cos(uDirection) * 0.4;
float windY = sin(uDirection) * 0.4;
vec3 camPos = (windX * camI + windY * camJ + 0.1 * camK) * timeSpeed;
vec3 pos = camPos;
// Precompute ray reciprocal for strides
vec3 absRay = max(abs(ray), vec3(0.001));
vec3 strides = 1.0 / absRay;
vec3 raySign = step(ray, vec3(0.0));
vec3 phase = fract(pos) * strides;
phase = mix(strides - phase, phase, raySign);
// Precompute for intersection test
float rayDotCamK = dot(ray, camK);
float invRayDotCamK = 1.0 / rayDotCamK;
float invDepthFade = 1.0 / uDepthFade;
float halfInvResX = 0.5 * invResX;
vec3 timeAnim = timeSpeed * 0.1 * vec3(7.0, 8.0, 5.0);
float t = 0.0;
for (int i = 0; i < 128; i++) {
if (t >= uFarPlane) break;
vec3 fpos = floor(pos);
uint cellCoord = coord3(fpos);
float cellHash = hash3(cellCoord).x;
if (cellHash < uDensity) {
vec3 h = hash3(cellCoord);
// Optimized flake position calculation
vec3 sinArg1 = fpos.yzx * 0.073;
vec3 sinArg2 = fpos.zxy * 0.27;
vec3 flakePos = 0.5 - 0.5 * cos(4.0 * sin(sinArg1) + 4.0 * sin(sinArg2) + 2.0 * h + timeAnim);
flakePos = flakePos * 0.8 + 0.1 + fpos;
float toIntersection = dot(flakePos - pos, camK) * invRayDotCamK;
if (toIntersection > 0.0) {
vec3 testPos = pos + ray * toIntersection - flakePos;
float testX = dot(testPos, camI);
float testY = dot(testPos, camJ);
vec2 testUV = abs(vec2(testX, testY));
float depth = dot(flakePos - camPos, camK);
float flakeSize = max(uFlakeSize, uMinFlakeSize * depth * halfInvResX);
// Avoid branching with step functions where possible
float dist;
if (uVariant < 0.5) {
dist = max(testUV.x, testUV.y);
} else if (uVariant < 1.5) {
dist = length(testUV);
} else {
float invFlakeSize = 1.0 / flakeSize;
dist = snowflakeDist(vec2(testX, testY) * invFlakeSize) * flakeSize;
}
if (dist < flakeSize) {
float flakeSizeRatio = uFlakeSize / flakeSize;
float intensity = exp2(-(t + toIntersection) * invDepthFade) *
min(1.0, flakeSizeRatio * flakeSizeRatio) * uBrightness;
gl_FragColor = vec4(uColor * pow(vec3(intensity), vec3(uGamma)), 1.0);
return;
}
}
}
float nextStep = min(min(phase.x, phase.y), phase.z);
vec3 sel = step(phase, vec3(nextStep));
phase = phase - nextStep + strides * sel;
t += nextStep;
pos = mix(pos + ray * nextStep, floor(pos + ray * nextStep + 0.5), sel);
}
gl_FragColor = vec4(0.0);
}
`;
export default function PixelSnow({
color = '#ffffff',
flakeSize = 0.01,
minFlakeSize = 1.25,
pixelResolution = 200,
speed = 1.25,
depthFade = 8,
farPlane = 20,
brightness = 1,
gamma = 0.4545,
density = 0.3,
variant = 'square',
direction = 125,
className = '',
style = {}
}) {
const containerRef = useRef(null);
const animationRef = useRef(0);
const isVisibleRef = useRef(true);
const rendererRef = useRef(null);
const materialRef = useRef(null);
const resizeTimeoutRef = useRef(null);
// Memoize shader variant value
const variantValue = useMemo(() => {
return variant === 'round' ? 1.0 : variant === 'snowflake' ? 2.0 : 0.0;
}, [variant]);
// Memoize color conversion
const colorVector = useMemo(() => {
const threeColor = new Color(color);
return new Vector3(threeColor.r, threeColor.g, threeColor.b);
}, [color]);
// Debounced resize handler
const handleResize = useCallback(() => {
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
resizeTimeoutRef.current = window.setTimeout(() => {
const container = containerRef.current;
const renderer = rendererRef.current;
const material = materialRef.current;
if (!container || !renderer || !material) return;
const w = container.offsetWidth;
const h = container.offsetHeight;
renderer.setSize(w, h);
material.uniforms.uResolution.value.set(w, h);
}, 100);
}, []);
// Visibility observer
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const observer = new IntersectionObserver(
([entry]) => {
isVisibleRef.current = entry.isIntersecting;
},
{ threshold: 0 }
);
observer.observe(container);
return () => observer.disconnect();
}, []);
// Main Three.js setup - only runs once
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const scene = new Scene();
const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
const renderer = new WebGLRenderer({
antialias: false,
alpha: true,
premultipliedAlpha: false,
powerPreference: 'high-performance',
stencil: false,
depth: false
});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(container.offsetWidth, container.offsetHeight);
renderer.setClearColor(0x000000, 0);
container.appendChild(renderer.domElement);
rendererRef.current = renderer;
const material = new ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
uTime: { value: 0 },
uResolution: { value: new Vector2(container.offsetWidth, container.offsetHeight) },
uFlakeSize: { value: flakeSize },
uMinFlakeSize: { value: minFlakeSize },
uPixelResolution: { value: pixelResolution },
uSpeed: { value: speed },
uDepthFade: { value: depthFade },
uFarPlane: { value: farPlane },
uColor: { value: colorVector.clone() },
uBrightness: { value: brightness },
uGamma: { value: gamma },
uDensity: { value: density },
uVariant: { value: variantValue },
uDirection: { value: (direction * Math.PI) / 180 }
},
transparent: true
});
materialRef.current = material;
const geometry = new PlaneGeometry(2, 2);
scene.add(new Mesh(geometry, material));
window.addEventListener('resize', handleResize);
const startTime = performance.now();
const animate = () => {
animationRef.current = requestAnimationFrame(animate);
// Only render if visible
if (isVisibleRef.current) {
material.uniforms.uTime.value = (performance.now() - startTime) * 0.001;
renderer.render(scene, camera);
}
};
animate();
return () => {
cancelAnimationFrame(animationRef.current);
window.removeEventListener('resize', handleResize);
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
if (container.contains(renderer.domElement)) {
container.removeChild(renderer.domElement);
}
renderer.dispose();
renderer.forceContextLoss();
geometry.dispose();
material.dispose();
rendererRef.current = null;
materialRef.current = null;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [handleResize]); // Only recreate scene when handleResize changes
// Update material uniforms when props change
useEffect(() => {
const material = materialRef.current;
if (!material) return;
material.uniforms.uFlakeSize.value = flakeSize;
material.uniforms.uMinFlakeSize.value = minFlakeSize;
material.uniforms.uPixelResolution.value = pixelResolution;
material.uniforms.uSpeed.value = speed;
material.uniforms.uDepthFade.value = depthFade;
material.uniforms.uFarPlane.value = farPlane;
material.uniforms.uBrightness.value = brightness;
material.uniforms.uGamma.value = gamma;
material.uniforms.uDensity.value = density;
material.uniforms.uVariant.value = variantValue;
material.uniforms.uDirection.value = (direction * Math.PI) / 180;
material.uniforms.uColor.value.copy(colorVector);
}, [
flakeSize,
minFlakeSize,
pixelResolution,
speed,
depthFade,
farPlane,
brightness,
gamma,
density,
variantValue,
direction,
colorVector
]);
return <div ref={containerRef} className={`pixel-snow-container ${className}`} style={style} />;
}
Component CSS
.pixel-snow-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
contain: layout style paint;
}
.pixel-snow-container canvas {
display: block;
width: 100%;
height: 100%;
transform: translateZ(0);
will-change: transform;
backface-visibility: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: lightning code: ## Integrate the <Lightning /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Lightning
Variant: JavaScript + CSS
Usage Example
import Lightning from './Lightning';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Lightning
hue={220}
xOffset={0}
speed={1}
intensity={1}
size={1}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| hue | number | 230 | Hue of the lightning in degrees (0 to 360). |
| xOffset | number | 0 | Horizontal offset of the lightning in normalized units. |
| speed | number | 1 | Animation speed multiplier for the lightning. |
| intensity | number | 1 | Brightness multiplier for the lightning. |
| size | number | 1 | Scale factor for the bolt size. |
Full Component Source
import { useRef, useEffect } from 'react';
import './Lightning.css';
const Lightning = ({ hue = 230, xOffset = 0, speed = 1, intensity = 1, size = 1 }) => {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const resizeCanvas = () => {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
};
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
const gl = canvas.getContext('webgl', { alpha: true, premultipliedAlpha: false });
if (!gl) {
console.error('WebGL not supported');
return;
}
const vertexShaderSource = `
attribute vec2 aPosition;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`;
const fragmentShaderSource = `
precision mediump float;
uniform vec2 iResolution;
uniform float iTime;
uniform float uHue;
uniform float uXOffset;
uniform float uSpeed;
uniform float uIntensity;
uniform float uSize;
#define OCTAVE_COUNT 10
vec3 hsv2rgb(vec3 c) {
vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0,4.0,2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
return c.z * mix(vec3(1.0), rgb, c.y);
}
float hash11(float p) {
p = fract(p * .1031);
p *= p + 33.33;
p *= p + p;
return fract(p);
}
float hash12(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * .1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
mat2 rotate2d(float theta) {
float c = cos(theta);
float s = sin(theta);
return mat2(c, -s, s, c);
}
float noise(vec2 p) {
vec2 ip = floor(p);
vec2 fp = fract(p);
float a = hash12(ip);
float b = hash12(ip + vec2(1.0, 0.0));
float c = hash12(ip + vec2(0.0, 1.0));
float d = hash12(ip + vec2(1.0, 1.0));
vec2 t = smoothstep(0.0, 1.0, fp);
return mix(mix(a, b, t.x), mix(c, d, t.x), t.y);
}
float fbm(vec2 p) {
float value = 0.0;
float amplitude = 0.5;
for (int i = 0; i < OCTAVE_COUNT; ++i) {
value += amplitude * noise(p);
p *= rotate2d(0.45);
p *= 2.0;
amplitude *= 0.5;
}
return value;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
vec2 uv = fragCoord / iResolution.xy;
uv = 2.0 * uv - 1.0;
uv.x *= iResolution.x / iResolution.y;
uv.x += uXOffset;
uv += 2.0 * fbm(uv * uSize + 0.8 * iTime * uSpeed) - 1.0;
float dist = abs(uv.x);
vec3 baseColor = hsv2rgb(vec3(uHue / 360.0, 0.7, 0.8));
vec3 col = baseColor * pow(mix(0.0, 0.07, hash11(iTime * uSpeed)) / dist, 1.0) * uIntensity;
col = pow(col, vec3(1.0));
float a = clamp(max(col.r, max(col.g, col.b)), 0.0, 1.0);
fragColor = vec4(col, a);
}
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
`;
const compileShader = (source, type) => {
const shader = gl.createShader(type);
if (!shader) return null;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Shader compile error:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
};
const vertexShader = compileShader(vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = compileShader(fragmentShaderSource, gl.FRAGMENT_SHADER);
if (!vertexShader || !fragmentShader) return;
const program = gl.createProgram();
if (!program) return;
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Program linking error:', gl.getProgramInfoLog(program));
return;
}
gl.useProgram(program);
const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const aPosition = gl.getAttribLocation(program, 'aPosition');
gl.enableVertexAttribArray(aPosition);
gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
const iResolutionLocation = gl.getUniformLocation(program, 'iResolution');
const iTimeLocation = gl.getUniformLocation(program, 'iTime');
const uHueLocation = gl.getUniformLocation(program, 'uHue');
const uXOffsetLocation = gl.getUniformLocation(program, 'uXOffset');
const uSpeedLocation = gl.getUniformLocation(program, 'uSpeed');
const uIntensityLocation = gl.getUniformLocation(program, 'uIntensity');
const uSizeLocation = gl.getUniformLocation(program, 'uSize');
const startTime = performance.now();
const render = () => {
resizeCanvas();
gl.viewport(0, 0, canvas.width, canvas.height);
gl.uniform2f(iResolutionLocation, canvas.width, canvas.height);
const currentTime = performance.now();
gl.uniform1f(iTimeLocation, (currentTime - startTime) / 1000.0);
gl.uniform1f(uHueLocation, hue);
gl.uniform1f(uXOffsetLocation, xOffset);
gl.uniform1f(uSpeedLocation, speed);
gl.uniform1f(uIntensityLocation, intensity);
gl.uniform1f(uSizeLocation, size);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(render);
};
requestAnimationFrame(render);
return () => {
window.removeEventListener('resize', resizeCanvas);
};
}, [hue, xOffset, speed, intensity, size]);
return <canvas ref={canvasRef} className="lightning-container" />;
};
export default Lightning;
Component CSS
.lightning-container {
width: 100%;
height: 100%;
position: relative;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: prismatic burst code: ## Integrate the <PrismaticBurst /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: PrismaticBurst
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import PrismaticBurst from './PrismaticBurst';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<PrismaticBurst
animationType="rotate3d"
intensity={2}
speed={0.5}
distort={1.0}
paused={false}
offset={{ x: 0, y: 0 }}
hoverDampness={0.25}
rayCount={24}
mixBlendMode="lighten"
colors={['#ff007a', '#4d3dff', '#ffffff']}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| intensity | number | 2 | Overall brightness multiplier applied after accumulation. |
| speed | number | 0.5 | Global time multiplier controlling ray motion & distortion. |
| animationType | "rotate" | "rotate3d" | "hover" |
| colors | string[] | [] | Optional array of hex colors used as a gradient (otherwise spectral) |
| distort | number | 0 | Amount of bend/distortion applied to marching space (adds organic wobble) |
| paused | boolean | false | Freeze time progression when true (animation stops) |
| offset | { x?: number | string; y?: number | string } |
| hoverDampness | number | 0 | Smoothing factor (0-1) for pointer tracking when animationType='hover' |
| rayCount | number | undefined | If > 0 applies an angular comb filter to produce discrete ray spokes |
| mixBlendMode | CSSProperties['mixBlendMode'] | 'none' | "lighten" |
Full Component Source
import { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle, Texture } from 'ogl';
import './PrismaticBurst.css';
const vertexShader = `#version 300 es
in vec2 position;
in vec2 uv;
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragmentShader = `#version 300 es
precision highp float;
precision highp int;
out vec4 fragColor;
uniform vec2 uResolution;
uniform float uTime;
uniform float uIntensity;
uniform float uSpeed;
uniform int uAnimType;
uniform vec2 uMouse;
uniform int uColorCount;
uniform float uDistort;
uniform vec2 uOffset;
uniform sampler2D uGradient;
uniform float uNoiseAmount;
uniform int uRayCount;
float hash21(vec2 p){
p = floor(p);
float f = 52.9829189 * fract(dot(p, vec2(0.065, 0.005)));
return fract(f);
}
mat2 rot30(){ return mat2(0.8, -0.5, 0.5, 0.8); }
float layeredNoise(vec2 fragPx){
vec2 p = mod(fragPx + vec2(uTime * 30.0, -uTime * 21.0), 1024.0);
vec2 q = rot30() * p;
float n = 0.0;
n += 0.40 * hash21(q);
n += 0.25 * hash21(q * 2.0 + 17.0);
n += 0.20 * hash21(q * 4.0 + 47.0);
n += 0.10 * hash21(q * 8.0 + 113.0);
n += 0.05 * hash21(q * 16.0 + 191.0);
return n;
}
vec3 rayDir(vec2 frag, vec2 res, vec2 offset, float dist){
float focal = res.y * max(dist, 1e-3);
return normalize(vec3(2.0 * (frag - offset) - res, focal));
}
float edgeFade(vec2 frag, vec2 res, vec2 offset){
vec2 toC = frag - 0.5 * res - offset;
float r = length(toC) / (0.5 * min(res.x, res.y));
float x = clamp(r, 0.0, 1.0);
float q = x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
float s = q * 0.5;
s = pow(s, 1.5);
float tail = 1.0 - pow(1.0 - s, 2.0);
s = mix(s, tail, 0.2);
float dn = (layeredNoise(frag * 0.15) - 0.5) * 0.0015 * s;
return clamp(s + dn, 0.0, 1.0);
}
mat3 rotX(float a){ float c = cos(a), s = sin(a); return mat3(1.0,0.0,0.0, 0.0,c,-s, 0.0,s,c); }
mat3 rotY(float a){ float c = cos(a), s = sin(a); return mat3(c,0.0,s, 0.0,1.0,0.0, -s,0.0,c); }
mat3 rotZ(float a){ float c = cos(a), s = sin(a); return mat3(c,-s,0.0, s,c,0.0, 0.0,0.0,1.0); }
vec3 sampleGradient(float t){
t = clamp(t, 0.0, 1.0);
return texture(uGradient, vec2(t, 0.5)).rgb;
}
vec2 rot2(vec2 v, float a){
float s = sin(a), c = cos(a);
return mat2(c, -s, s, c) * v;
}
float bendAngle(vec3 q, float t){
float a = 0.8 * sin(q.x * 0.55 + t * 0.6)
+ 0.7 * sin(q.y * 0.50 - t * 0.5)
+ 0.6 * sin(q.z * 0.60 + t * 0.7);
return a;
}
void main(){
vec2 frag = gl_FragCoord.xy;
float t = uTime * uSpeed;
float jitterAmp = 0.1 * clamp(uNoiseAmount, 0.0, 1.0);
vec3 dir = rayDir(frag, uResolution, uOffset, 1.0);
float marchT = 0.0;
vec3 col = vec3(0.0);
float n = layeredNoise(frag);
vec4 c = cos(t * 0.2 + vec4(0.0, 33.0, 11.0, 0.0));
mat2 M2 = mat2(c.x, c.y, c.z, c.w);
float amp = clamp(uDistort, 0.0, 50.0) * 0.15;
mat3 rot3dMat = mat3(1.0);
if(uAnimType == 1){
vec3 ang = vec3(t * 0.31, t * 0.21, t * 0.17);
rot3dMat = rotZ(ang.z) * rotY(ang.y) * rotX(ang.x);
}
mat3 hoverMat = mat3(1.0);
if(uAnimType == 2){
vec2 m = uMouse * 2.0 - 1.0;
vec3 ang = vec3(m.y * 0.6, m.x * 0.6, 0.0);
hoverMat = rotY(ang.y) * rotX(ang.x);
}
for (int i = 0; i < 44; ++i) {
vec3 P = marchT * dir;
P.z -= 2.0;
float rad = length(P);
vec3 Pl = P * (10.0 / max(rad, 1e-6));
if(uAnimType == 0){
Pl.xz *= M2;
} else if(uAnimType == 1){
Pl = rot3dMat * Pl;
} else {
Pl = hoverMat * Pl;
}
float stepLen = min(rad - 0.3, n * jitterAmp) + 0.1;
float grow = smoothstep(0.35, 3.0, marchT);
float a1 = amp * grow * bendAngle(Pl * 0.6, t);
float a2 = 0.5 * amp * grow * bendAngle(Pl.zyx * 0.5 + 3.1, t * 0.9);
vec3 Pb = Pl;
Pb.xz = rot2(Pb.xz, a1);
Pb.xy = rot2(Pb.xy, a2);
float rayPattern = smoothstep(
0.5, 0.7,
sin(Pb.x + cos(Pb.y) * cos(Pb.z)) *
sin(Pb.z + sin(Pb.y) * cos(Pb.x + t))
);
if (uRayCount > 0) {
float ang = atan(Pb.y, Pb.x);
float comb = 0.5 + 0.5 * cos(float(uRayCount) * ang);
comb = pow(comb, 3.0);
rayPattern *= smoothstep(0.15, 0.95, comb);
}
vec3 spectralDefault = 1.0 + vec3(
cos(marchT * 3.0 + 0.0),
cos(marchT * 3.0 + 1.0),
cos(marchT * 3.0 + 2.0)
);
float saw = fract(marchT * 0.25);
float tRay = saw * saw * (3.0 - 2.0 * saw);
vec3 userGradient = 2.0 * sampleGradient(tRay);
vec3 spectral = (uColorCount > 0) ? userGradient : spectralDefault;
vec3 base = (0.05 / (0.4 + stepLen))
* smoothstep(5.0, 0.0, rad)
* spectral;
col += base * rayPattern;
marchT += stepLen;
}
col *= edgeFade(frag, uResolution, uOffset);
col *= uIntensity;
fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}`;
const hexToRgb01 = hex => {
let h = hex.trim();
if (h.startsWith('#')) h = h.slice(1);
if (h.length === 3) {
const r = h[0],
g = h[1],
b = h[2];
h = r + r + g + g + b + b;
}
const intVal = parseInt(h, 16);
if (isNaN(intVal) || (h.length !== 6 && h.length !== 8)) return [1, 1, 1];
const r = ((intVal >> 16) & 255) / 255;
const g = ((intVal >> 8) & 255) / 255;
const b = (intVal & 255) / 255;
return [r, g, b];
};
const toPx = v => {
if (v == null) return 0;
if (typeof v === 'number') return v;
const s = String(v).trim();
const num = parseFloat(s.replace('px', ''));
return isNaN(num) ? 0 : num;
};
const PrismaticBurst = ({
intensity = 2,
speed = 0.5,
animationType = 'rotate3d',
colors,
distort = 0,
paused = false,
offset = { x: 0, y: 0 },
hoverDampness = 0,
rayCount,
mixBlendMode = 'lighten'
}) => {
const containerRef = useRef(null);
const programRef = useRef(null);
const rendererRef = useRef(null);
const mouseTargetRef = useRef([0.5, 0.5]);
const mouseSmoothRef = useRef([0.5, 0.5]);
const pausedRef = useRef(paused);
const gradTexRef = useRef(null);
const hoverDampRef = useRef(hoverDampness);
const isVisibleRef = useRef(true);
const meshRef = useRef(null);
const triRef = useRef(null);
useEffect(() => {
pausedRef.current = paused;
}, [paused]);
useEffect(() => {
hoverDampRef.current = hoverDampness;
}, [hoverDampness]);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const renderer = new Renderer({
dpr,
alpha: false,
antialias: false
});
rendererRef.current = renderer;
const gl = renderer.gl;
gl.canvas.style.position = 'absolute';
gl.canvas.style.inset = '0';
gl.canvas.style.width = '100%';
gl.canvas.style.height = '100%';
gl.canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';
container.appendChild(gl.canvas);
const white = new Uint8Array([255, 255, 255, 255]);
const gradientTex = new Texture(gl, {
image: white,
width: 1,
height: 1,
generateMipmaps: false,
flipY: false
});
gradientTex.minFilter = gl.LINEAR;
gradientTex.magFilter = gl.LINEAR;
gradientTex.wrapS = gl.CLAMP_TO_EDGE;
gradientTex.wrapT = gl.CLAMP_TO_EDGE;
gradTexRef.current = gradientTex;
const program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
uResolution: { value: [1, 1] },
uTime: { value: 0 },
uIntensity: { value: 1 },
uSpeed: { value: 1 },
uAnimType: { value: 0 },
uMouse: { value: [0.5, 0.5] },
uColorCount: { value: 0 },
uDistort: { value: 0 },
uOffset: { value: [0, 0] },
uGradient: { value: gradientTex },
uNoiseAmount: { value: 0.8 },
uRayCount: { value: 0 }
}
});
programRef.current = program;
const triangle = new Triangle(gl);
const mesh = new Mesh(gl, { geometry: triangle, program });
triRef.current = triangle;
meshRef.current = mesh;
const resize = () => {
const w = container.clientWidth || 1;
const h = container.clientHeight || 1;
renderer.setSize(w, h);
program.uniforms.uResolution.value = [gl.drawingBufferWidth, gl.drawingBufferHeight];
};
let ro = null;
if ('ResizeObserver' in window) {
ro = new ResizeObserver(resize);
ro.observe(container);
} else {
window.addEventListener('resize', resize);
}
resize();
const onPointer = e => {
const rect = container.getBoundingClientRect();
const x = (e.clientX - rect.left) / Math.max(rect.width, 1);
const y = (e.clientY - rect.top) / Math.max(rect.height, 1);
mouseTargetRef.current = [Math.min(Math.max(x, 0), 1), Math.min(Math.max(y, 0), 1)];
};
container.addEventListener('pointermove', onPointer, { passive: true });
let io = null;
if ('IntersectionObserver' in window) {
io = new IntersectionObserver(
entries => {
if (entries[0]) {
isVisibleRef.current = entries[0].isIntersecting;
}
},
{ root: null, threshold: 0.01 }
);
io.observe(container);
}
const onVis = () => {};
document.addEventListener('visibilitychange', onVis);
let raf = 0;
let last = performance.now();
let accumTime = 0;
const update = now => {
const dt = Math.max(0, now - last) * 0.001;
last = now;
const visible = isVisibleRef.current && !document.hidden;
if (!pausedRef.current) accumTime += dt;
if (!visible) {
raf = requestAnimationFrame(update);
return;
}
const tau = 0.02 + Math.max(0, Math.min(1, hoverDampRef.current)) * 0.5;
const alpha = 1 - Math.exp(-dt / tau);
const tgt = mouseTargetRef.current;
const sm = mouseSmoothRef.current;
sm[0] += (tgt[0] - sm[0]) * alpha;
sm[1] += (tgt[1] - sm[1]) * alpha;
program.uniforms.uMouse.value = sm;
program.uniforms.uTime.value = accumTime;
renderer.render({ scene: meshRef.current });
raf = requestAnimationFrame(update);
};
raf = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(raf);
container.removeEventListener('pointermove', onPointer);
ro?.disconnect();
if (!ro) window.removeEventListener('resize', resize);
io?.disconnect();
document.removeEventListener('visibilitychange', onVis);
try {
container.removeChild(gl.canvas);
} catch {
console.warn('Canvas already removed');
}
try {
meshRef.current?.remove?.();
} catch (e) {
/* ignore dispose errors */
}
try {
triRef.current?.remove?.();
} catch (e) {
/* ignore dispose errors */
}
try {
programRef.current?.remove?.();
} catch (e) {
/* ignore dispose errors */
}
try {
const glCtx = rendererRef.current?.gl;
if (glCtx && gradTexRef.current?.texture) {
glCtx.deleteTexture(gradTexRef.current.texture);
}
} catch (e) {
/* ignore texture delete errors */
}
programRef.current = null;
rendererRef.current = null;
gradTexRef.current = null;
meshRef.current = null;
triRef.current = null;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
const canvas = rendererRef.current?.gl?.canvas;
if (canvas) {
canvas.style.mixBlendMode = mixBlendMode && mixBlendMode !== 'none' ? mixBlendMode : '';
}
}, [mixBlendMode]);
useEffect(() => {
const program = programRef.current;
const renderer = rendererRef.current;
const gradTex = gradTexRef.current;
if (!program || !renderer || !gradTex) return;
program.uniforms.uIntensity.value = intensity ?? 1;
program.uniforms.uSpeed.value = speed ?? 1;
const animTypeMap = {
rotate: 0,
rotate3d: 1,
hover: 2
};
program.uniforms.uAnimType.value = animTypeMap[animationType ?? 'rotate'];
program.uniforms.uDistort.value = typeof distort === 'number' ? distort : 0;
const ox = toPx(offset?.x);
const oy = toPx(offset?.y);
program.uniforms.uOffset.value = [ox, oy];
program.uniforms.uRayCount.value = Math.max(0, Math.floor(rayCount ?? 0));
let count = 0;
if (Array.isArray(colors) && colors.length > 0) {
const gl = renderer.gl;
const capped = colors.slice(0, 64);
count = capped.length;
const data = new Uint8Array(count * 4);
for (let i = 0; i < count; i++) {
const [r, g, b] = hexToRgb01(capped[i]);
data[i * 4 + 0] = Math.round(r * 255);
data[i * 4 + 1] = Math.round(g * 255);
data[i * 4 + 2] = Math.round(b * 255);
data[i * 4 + 3] = 255;
}
gradTex.image = data;
gradTex.width = count;
gradTex.height = 1;
gradTex.minFilter = gl.LINEAR;
gradTex.magFilter = gl.LINEAR;
gradTex.wrapS = gl.CLAMP_TO_EDGE;
gradTex.wrapT = gl.CLAMP_TO_EDGE;
gradTex.flipY = false;
gradTex.generateMipmaps = false;
gradTex.format = gl.RGBA;
gradTex.type = gl.UNSIGNED_BYTE;
gradTex.needsUpdate = true;
} else {
count = 0;
}
program.uniforms.uColorCount.value = count;
}, [intensity, speed, animationType, colors, distort, offset, rayCount]);
return <div className="prismatic-burst-container" ref={containerRef} />;
};
export default PrismaticBurst;
Component CSS
.prismatic-burst-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: galaxy code: ## Integrate the <Galaxy /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Galaxy
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Galaxy from './Galaxy';
// Basic usage
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Galaxy />
</div>
// With custom prop values
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Galaxy
mouseRepulsion={true}
mouseInteraction={true}
density={1.5}
glowIntensity={0.5}
saturation={0.8}
hueShift={240}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| focal | [number, number] | [0.5, 0.5] | Sets the focal point of the galaxy effect as [x, y] coordinates from 0 to 1 |
| rotation | [number, number] | [1.0, 0.0] | Controls the rotation matrix of the galaxy as [x, y] rotation values |
| starSpeed | number | 0.5 | Controls the speed of star movement and animation |
| density | number | 1 | Controls the density of stars in the galaxy |
| hueShift | number | 140 | Shifts the hue of all stars by the specified degrees (0-360) |
| disableAnimation | boolean | false | When true, stops all time-based animations |
| speed | number | 1.0 | Global speed multiplier for all animations |
| mouseInteraction | boolean | true | Enables or disables mouse interaction with the galaxy |
| glowIntensity | number | 0.3 | Controls the intensity of the star glow effect |
| saturation | number | 0.0 | Controls color saturation of stars (0 = grayscale, 1 = full color) |
| mouseRepulsion | boolean | true | When true, stars are repelled by the mouse cursor |
| twinkleIntensity | number | 0.3 | Controls how much stars twinkle (0 = no twinkle, 1 = maximum twinkle) |
| rotationSpeed | number | 0.1 | Speed of automatic galaxy rotation |
| repulsionStrength | number | 2 | Strength of mouse repulsion effect when mouseRepulsion is enabled |
| autoCenterRepulsion | number | 0 | Creates repulsion from center of canvas. Overrides mouse repulsion when > 0 |
| transparent | boolean | true | Makes the black background transparent, showing only stars |
Full Component Source
import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';
import { useEffect, useRef } from 'react';
import './Galaxy.css';
const vertexShader = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
const fragmentShader = `
precision highp float;
uniform float uTime;
uniform vec3 uResolution;
uniform vec2 uFocal;
uniform vec2 uRotation;
uniform float uStarSpeed;
uniform float uDensity;
uniform float uHueShift;
uniform float uSpeed;
uniform vec2 uMouse;
uniform float uGlowIntensity;
uniform float uSaturation;
uniform bool uMouseRepulsion;
uniform float uTwinkleIntensity;
uniform float uRotationSpeed;
uniform float uRepulsionStrength;
uniform float uMouseActiveFactor;
uniform float uAutoCenterRepulsion;
uniform bool uTransparent;
varying vec2 vUv;
#define NUM_LAYER 4.0
#define STAR_COLOR_CUTOFF 0.2
#define MAT45 mat2(0.7071, -0.7071, 0.7071, 0.7071)
#define PERIOD 3.0
float Hash21(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
float tri(float x) {
return abs(fract(x) * 2.0 - 1.0);
}
float tris(float x) {
float t = fract(x);
return 1.0 - smoothstep(0.0, 1.0, abs(2.0 * t - 1.0));
}
float trisn(float x) {
float t = fract(x);
return 2.0 * (1.0 - smoothstep(0.0, 1.0, abs(2.0 * t - 1.0))) - 1.0;
}
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
float Star(vec2 uv, float flare) {
float d = length(uv);
float m = (0.05 * uGlowIntensity) / d;
float rays = smoothstep(0.0, 1.0, 1.0 - abs(uv.x * uv.y * 1000.0));
m += rays * flare * uGlowIntensity;
uv *= MAT45;
rays = smoothstep(0.0, 1.0, 1.0 - abs(uv.x * uv.y * 1000.0));
m += rays * 0.3 * flare * uGlowIntensity;
m *= smoothstep(1.0, 0.2, d);
return m;
}
vec3 StarLayer(vec2 uv) {
vec3 col = vec3(0.0);
vec2 gv = fract(uv) - 0.5;
vec2 id = floor(uv);
for (int y = -1; y <= 1; y++) {
for (int x = -1; x <= 1; x++) {
vec2 offset = vec2(float(x), float(y));
vec2 si = id + vec2(float(x), float(y));
float seed = Hash21(si);
float size = fract(seed * 345.32);
float glossLocal = tri(uStarSpeed / (PERIOD * seed + 1.0));
float flareSize = smoothstep(0.9, 1.0, size) * glossLocal;
float red = smoothstep(STAR_COLOR_CUTOFF, 1.0, Hash21(si + 1.0)) + STAR_COLOR_CUTOFF;
float blu = smoothstep(STAR_COLOR_CUTOFF, 1.0, Hash21(si + 3.0)) + STAR_COLOR_CUTOFF;
float grn = min(red, blu) * seed;
vec3 base = vec3(red, grn, blu);
float hue = atan(base.g - base.r, base.b - base.r) / (2.0 * 3.14159) + 0.5;
hue = fract(hue + uHueShift / 360.0);
float sat = length(base - vec3(dot(base, vec3(0.299, 0.587, 0.114)))) * uSaturation;
float val = max(max(base.r, base.g), base.b);
base = hsv2rgb(vec3(hue, sat, val));
vec2 pad = vec2(tris(seed * 34.0 + uTime * uSpeed / 10.0), tris(seed * 38.0 + uTime * uSpeed / 30.0)) - 0.5;
float star = Star(gv - offset - pad, flareSize);
vec3 color = base;
float twinkle = trisn(uTime * uSpeed + seed * 6.2831) * 0.5 + 1.0;
twinkle = mix(1.0, twinkle, uTwinkleIntensity);
star *= twinkle;
col += star * size * color;
}
}
return col;
}
void main() {
vec2 focalPx = uFocal * uResolution.xy;
vec2 uv = (vUv * uResolution.xy - focalPx) / uResolution.y;
vec2 mouseNorm = uMouse - vec2(0.5);
if (uAutoCenterRepulsion > 0.0) {
vec2 centerUV = vec2(0.0, 0.0);
float centerDist = length(uv - centerUV);
vec2 repulsion = normalize(uv - centerUV) * (uAutoCenterRepulsion / (centerDist + 0.1));
uv += repulsion * 0.05;
} else if (uMouseRepulsion) {
vec2 mousePosUV = (uMouse * uResolution.xy - focalPx) / uResolution.y;
float mouseDist = length(uv - mousePosUV);
vec2 repulsion = normalize(uv - mousePosUV) * (uRepulsionStrength / (mouseDist + 0.1));
uv += repulsion * 0.05 * uMouseActiveFactor;
} else {
vec2 mouseOffset = mouseNorm * 0.1 * uMouseActiveFactor;
uv += mouseOffset;
}
float autoRotAngle = uTime * uRotationSpeed;
mat2 autoRot = mat2(cos(autoRotAngle), -sin(autoRotAngle), sin(autoRotAngle), cos(autoRotAngle));
uv = autoRot * uv;
uv = mat2(uRotation.x, -uRotation.y, uRotation.y, uRotation.x) * uv;
vec3 col = vec3(0.0);
for (float i = 0.0; i < 1.0; i += 1.0 / NUM_LAYER) {
float depth = fract(i + uStarSpeed * uSpeed);
float scale = mix(20.0 * uDensity, 0.5 * uDensity, depth);
float fade = depth * smoothstep(1.0, 0.9, depth);
col += StarLayer(uv * scale + i * 453.32) * fade;
}
if (uTransparent) {
float alpha = length(col);
alpha = smoothstep(0.0, 0.3, alpha);
alpha = min(alpha, 1.0);
gl_FragColor = vec4(col, alpha);
} else {
gl_FragColor = vec4(col, 1.0);
}
}
`;
export default function Galaxy({
focal = [0.5, 0.5],
rotation = [1.0, 0.0],
starSpeed = 0.5,
density = 1,
hueShift = 140,
disableAnimation = false,
speed = 1.0,
mouseInteraction = true,
glowIntensity = 0.3,
saturation = 0.0,
mouseRepulsion = true,
repulsionStrength = 2,
twinkleIntensity = 0.3,
rotationSpeed = 0.1,
autoCenterRepulsion = 0,
transparent = true,
...rest
}) {
const ctnDom = useRef(null);
const targetMousePos = useRef({ x: 0.5, y: 0.5 });
const smoothMousePos = useRef({ x: 0.5, y: 0.5 });
const targetMouseActive = useRef(0.0);
const smoothMouseActive = useRef(0.0);
useEffect(() => {
if (!ctnDom.current) return;
const ctn = ctnDom.current;
const renderer = new Renderer({
alpha: transparent,
premultipliedAlpha: false
});
const gl = renderer.gl;
if (transparent) {
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.clearColor(0, 0, 0, 0);
} else {
gl.clearColor(0, 0, 0, 1);
}
let program;
function resize() {
const scale = 1;
renderer.setSize(ctn.offsetWidth * scale, ctn.offsetHeight * scale);
if (program) {
program.uniforms.uResolution.value = new Color(
gl.canvas.width,
gl.canvas.height,
gl.canvas.width / gl.canvas.height
);
}
}
window.addEventListener('resize', resize, false);
resize();
const geometry = new Triangle(gl);
program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
uTime: { value: 0 },
uResolution: {
value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)
},
uFocal: { value: new Float32Array(focal) },
uRotation: { value: new Float32Array(rotation) },
uStarSpeed: { value: starSpeed },
uDensity: { value: density },
uHueShift: { value: hueShift },
uSpeed: { value: speed },
uMouse: {
value: new Float32Array([smoothMousePos.current.x, smoothMousePos.current.y])
},
uGlowIntensity: { value: glowIntensity },
uSaturation: { value: saturation },
uMouseRepulsion: { value: mouseRepulsion },
uTwinkleIntensity: { value: twinkleIntensity },
uRotationSpeed: { value: rotationSpeed },
uRepulsionStrength: { value: repulsionStrength },
uMouseActiveFactor: { value: 0.0 },
uAutoCenterRepulsion: { value: autoCenterRepulsion },
uTransparent: { value: transparent }
}
});
const mesh = new Mesh(gl, { geometry, program });
let animateId;
function update(t) {
animateId = requestAnimationFrame(update);
if (!disableAnimation) {
program.uniforms.uTime.value = t * 0.001;
program.uniforms.uStarSpeed.value = (t * 0.001 * starSpeed) / 10.0;
}
const lerpFactor = 0.05;
smoothMousePos.current.x += (targetMousePos.current.x - smoothMousePos.current.x) * lerpFactor;
smoothMousePos.current.y += (targetMousePos.current.y - smoothMousePos.current.y) * lerpFactor;
smoothMouseActive.current += (targetMouseActive.current - smoothMouseActive.current) * lerpFactor;
program.uniforms.uMouse.value[0] = smoothMousePos.current.x;
program.uniforms.uMouse.value[1] = smoothMousePos.current.y;
program.uniforms.uMouseActiveFactor.value = smoothMouseActive.current;
renderer.render({ scene: mesh });
}
animateId = requestAnimationFrame(update);
ctn.appendChild(gl.canvas);
function handleMouseMove(e) {
const rect = ctn.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = 1.0 - (e.clientY - rect.top) / rect.height;
targetMousePos.current = { x, y };
targetMouseActive.current = 1.0;
}
function handleMouseLeave() {
targetMouseActive.current = 0.0;
}
if (mouseInteraction) {
ctn.addEventListener('mousemove', handleMouseMove);
ctn.addEventListener('mouseleave', handleMouseLeave);
}
return () => {
cancelAnimationFrame(animateId);
window.removeEventListener('resize', resize);
if (mouseInteraction) {
ctn.removeEventListener('mousemove', handleMouseMove);
ctn.removeEventListener('mouseleave', handleMouseLeave);
}
ctn.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, [
focal,
rotation,
starSpeed,
density,
hueShift,
disableAnimation,
speed,
mouseInteraction,
glowIntensity,
saturation,
mouseRepulsion,
twinkleIntensity,
rotationSpeed,
repulsionStrength,
autoCenterRepulsion,
transparent
]);
return <div ref={ctnDom} className="galaxy-container" {...rest} />;
}
Component CSS
.galaxy-container {
width: 100%;
height: 100%;
position: relative;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: dither code: ## Integrate the <Dither /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Dither
Variant: JavaScript + CSS
Dependencies: three postprocessing @react-three/fiber @react-three/postprocessing
Usage Example
import Dither from './Dither';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Dither
waveColor={[0.5, 0.5, 0.5]}
disableAnimation={false}
enableMouseInteraction={true}
mouseRadius={0.3}
colorNum={4}
waveAmplitude={0.3}
waveFrequency={3}
waveSpeed={0.05}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| waveSpeed | number | 0.05 | Speed of the wave animation. |
| waveFrequency | number | 3 | Frequency of the wave pattern. |
| waveAmplitude | number | 0.3 | Amplitude of the wave pattern. |
| waveColor | [number, number, number] | [0.5, 0.5, 0.5] | Color of the wave, defined as an RGB array. |
| colorNum | number | 4 | Number of colors to use in the dithering effect. |
| pixelSize | number | 2 | Size of the pixels for the dithering effect. |
| disableAnimation | boolean | false | Disable the wave animation when true. |
| enableMouseInteraction | boolean | true | Enables mouse interaction to influence the wave effect. |
| mouseRadius | number | 1 | Radius for the mouse interaction effect. |
Full Component Source
/* eslint-disable react/no-unknown-property */
import { useRef, useEffect, forwardRef } from 'react';
import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { EffectComposer, wrapEffect } from '@react-three/postprocessing';
import { Effect } from 'postprocessing';
import * as THREE from 'three';
import './Dither.css';
const waveVertexShader = `
precision highp float;
varying vec2 vUv;
void main() {
vUv = uv;
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
vec4 viewPosition = viewMatrix * modelPosition;
gl_Position = projectionMatrix * viewPosition;
}
`;
const waveFragmentShader = `
precision highp float;
uniform vec2 resolution;
uniform float time;
uniform float waveSpeed;
uniform float waveFrequency;
uniform float waveAmplitude;
uniform vec3 waveColor;
uniform vec2 mousePos;
uniform int enableMouseInteraction;
uniform float mouseRadius;
vec4 mod289(vec4 x) { return x - floor(x * (1.0/289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289(((x * 34.0) + 1.0) * x); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
vec2 fade(vec2 t) { return t*t*t*(t*(t*6.0-15.0)+10.0); }
float cnoise(vec2 P) {
vec4 Pi = floor(P.xyxy) + vec4(0.0,0.0,1.0,1.0);
vec4 Pf = fract(P.xyxy) - vec4(0.0,0.0,1.0,1.0);
Pi = mod289(Pi);
vec4 ix = Pi.xzxz;
vec4 iy = Pi.yyww;
vec4 fx = Pf.xzxz;
vec4 fy = Pf.yyww;
vec4 i = permute(permute(ix) + iy);
vec4 gx = fract(i * (1.0/41.0)) * 2.0 - 1.0;
vec4 gy = abs(gx) - 0.5;
vec4 tx = floor(gx + 0.5);
gx = gx - tx;
vec2 g00 = vec2(gx.x, gy.x);
vec2 g10 = vec2(gx.y, gy.y);
vec2 g01 = vec2(gx.z, gy.z);
vec2 g11 = vec2(gx.w, gy.w);
vec4 norm = taylorInvSqrt(vec4(dot(g00,g00), dot(g01,g01), dot(g10,g10), dot(g11,g11)));
g00 *= norm.x; g01 *= norm.y; g10 *= norm.z; g11 *= norm.w;
float n00 = dot(g00, vec2(fx.x, fy.x));
float n10 = dot(g10, vec2(fx.y, fy.y));
float n01 = dot(g01, vec2(fx.z, fy.z));
float n11 = dot(g11, vec2(fx.w, fy.w));
vec2 fade_xy = fade(Pf.xy);
vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
return 2.3 * mix(n_x.x, n_x.y, fade_xy.y);
}
const int OCTAVES = 4;
float fbm(vec2 p) {
float value = 0.0;
float amp = 1.0;
float freq = waveFrequency;
for (int i = 0; i < OCTAVES; i++) {
value += amp * abs(cnoise(p));
p *= freq;
amp *= waveAmplitude;
}
return value;
}
float pattern(vec2 p) {
vec2 p2 = p - time * waveSpeed;
return fbm(p + fbm(p2));
}
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
uv -= 0.5;
uv.x *= resolution.x / resolution.y;
float f = pattern(uv);
if (enableMouseInteraction == 1) {
vec2 mouseNDC = (mousePos / resolution - 0.5) * vec2(1.0, -1.0);
mouseNDC.x *= resolution.x / resolution.y;
float dist = length(uv - mouseNDC);
float effect = 1.0 - smoothstep(0.0, mouseRadius, dist);
f -= 0.5 * effect;
}
vec3 col = mix(vec3(0.0), waveColor, f);
gl_FragColor = vec4(col, 1.0);
}
`;
const ditherFragmentShader = `
precision highp float;
uniform float colorNum;
uniform float pixelSize;
const float bayerMatrix8x8[64] = float[64](
0.0/64.0, 48.0/64.0, 12.0/64.0, 60.0/64.0, 3.0/64.0, 51.0/64.0, 15.0/64.0, 63.0/64.0,
32.0/64.0,16.0/64.0, 44.0/64.0, 28.0/64.0, 35.0/64.0,19.0/64.0, 47.0/64.0, 31.0/64.0,
8.0/64.0, 56.0/64.0, 4.0/64.0, 52.0/64.0, 11.0/64.0,59.0/64.0, 7.0/64.0, 55.0/64.0,
40.0/64.0,24.0/64.0, 36.0/64.0, 20.0/64.0, 43.0/64.0,27.0/64.0, 39.0/64.0, 23.0/64.0,
2.0/64.0, 50.0/64.0, 14.0/64.0, 62.0/64.0, 1.0/64.0,49.0/64.0, 13.0/64.0, 61.0/64.0,
34.0/64.0,18.0/64.0, 46.0/64.0, 30.0/64.0, 33.0/64.0,17.0/64.0, 45.0/64.0, 29.0/64.0,
10.0/64.0,58.0/64.0, 6.0/64.0, 54.0/64.0, 9.0/64.0,57.0/64.0, 5.0/64.0, 53.0/64.0,
42.0/64.0,26.0/64.0, 38.0/64.0, 22.0/64.0, 41.0/64.0,25.0/64.0, 37.0/64.0, 21.0/64.0
);
vec3 dither(vec2 uv, vec3 color) {
vec2 scaledCoord = floor(uv * resolution / pixelSize);
int x = int(mod(scaledCoord.x, 8.0));
int y = int(mod(scaledCoord.y, 8.0));
float threshold = bayerMatrix8x8[y * 8 + x] - 0.25;
float step = 1.0 / (colorNum - 1.0);
color += threshold * step;
float bias = 0.2;
color = clamp(color - bias, 0.0, 1.0);
return floor(color * (colorNum - 1.0) + 0.5) / (colorNum - 1.0);
}
void mainImage(in vec4 inputColor, in vec2 uv, out vec4 outputColor) {
vec2 normalizedPixelSize = pixelSize / resolution;
vec2 uvPixel = normalizedPixelSize * floor(uv / normalizedPixelSize);
vec4 color = texture2D(inputBuffer, uvPixel);
color.rgb = dither(uv, color.rgb);
outputColor = color;
}
`;
class RetroEffectImpl extends Effect {
constructor() {
const uniforms = new Map([
['colorNum', new THREE.Uniform(4.0)],
['pixelSize', new THREE.Uniform(2.0)]
]);
super('RetroEffect', ditherFragmentShader, { uniforms });
this.uniforms = uniforms;
}
set colorNum(v) {
this.uniforms.get('colorNum').value = v;
}
get colorNum() {
return this.uniforms.get('colorNum').value;
}
set pixelSize(v) {
this.uniforms.get('pixelSize').value = v;
}
get pixelSize() {
return this.uniforms.get('pixelSize').value;
}
}
const WrappedRetro = wrapEffect(RetroEffectImpl);
const RetroEffect = forwardRef((props, ref) => {
const { colorNum, pixelSize } = props;
return <WrappedRetro ref={ref} colorNum={colorNum} pixelSize={pixelSize} />;
});
RetroEffect.displayName = 'RetroEffect';
function DitheredWaves({
waveSpeed,
waveFrequency,
waveAmplitude,
waveColor,
colorNum,
pixelSize,
disableAnimation,
enableMouseInteraction,
mouseRadius
}) {
const mesh = useRef(null);
const mouseRef = useRef(new THREE.Vector2());
const { viewport, size, gl } = useThree();
const waveUniformsRef = useRef({
time: new THREE.Uniform(0),
resolution: new THREE.Uniform(new THREE.Vector2(0, 0)),
waveSpeed: new THREE.Uniform(waveSpeed),
waveFrequency: new THREE.Uniform(waveFrequency),
waveAmplitude: new THREE.Uniform(waveAmplitude),
waveColor: new THREE.Uniform(new THREE.Color(...waveColor)),
mousePos: new THREE.Uniform(new THREE.Vector2(0, 0)),
enableMouseInteraction: new THREE.Uniform(enableMouseInteraction ? 1 : 0),
mouseRadius: new THREE.Uniform(mouseRadius)
});
useEffect(() => {
const dpr = gl.getPixelRatio();
const w = Math.floor(size.width * dpr),
h = Math.floor(size.height * dpr);
const res = waveUniformsRef.current.resolution.value;
if (res.x !== w || res.y !== h) {
res.set(w, h);
}
}, [size, gl]);
const prevColor = useRef([...waveColor]);
useFrame(({ clock }) => {
const u = waveUniformsRef.current;
if (!disableAnimation) {
u.time.value = clock.getElapsedTime();
}
if (u.waveSpeed.value !== waveSpeed) u.waveSpeed.value = waveSpeed;
if (u.waveFrequency.value !== waveFrequency) u.waveFrequency.value = waveFrequency;
if (u.waveAmplitude.value !== waveAmplitude) u.waveAmplitude.value = waveAmplitude;
if (!prevColor.current.every((v, i) => v === waveColor[i])) {
u.waveColor.value.set(...waveColor);
prevColor.current = [...waveColor];
}
u.enableMouseInteraction.value = enableMouseInteraction ? 1 : 0;
u.mouseRadius.value = mouseRadius;
if (enableMouseInteraction) {
u.mousePos.value.copy(mouseRef.current);
}
});
const handlePointerMove = e => {
if (!enableMouseInteraction) return;
const rect = gl.domElement.getBoundingClientRect();
const dpr = gl.getPixelRatio();
mouseRef.current.set((e.clientX - rect.left) * dpr, (e.clientY - rect.top) * dpr);
};
return (
<>
<mesh ref={mesh} scale={[viewport.width, viewport.height, 1]}>
<planeGeometry args={[1, 1]} />
<shaderMaterial
vertexShader={waveVertexShader}
fragmentShader={waveFragmentShader}
uniforms={waveUniformsRef.current}
/>
</mesh>
<EffectComposer>
<RetroEffect colorNum={colorNum} pixelSize={pixelSize} />
</EffectComposer>
<mesh
onPointerMove={handlePointerMove}
position={[0, 0, 0.01]}
scale={[viewport.width, viewport.height, 1]}
visible={false}
>
<planeGeometry args={[1, 1]} />
<meshBasicMaterial transparent opacity={0} />
</mesh>
</>
);
}
export default function Dither({
waveSpeed = 0.05,
waveFrequency = 3,
waveAmplitude = 0.3,
waveColor = [0.5, 0.5, 0.5],
colorNum = 4,
pixelSize = 2,
disableAnimation = false,
enableMouseInteraction = true,
mouseRadius = 1
}) {
return (
<Canvas
className="dither-container"
camera={{ position: [0, 0, 6] }}
dpr={1}
gl={{ antialias: true, preserveDrawingBuffer: true }}
>
<DitheredWaves
waveSpeed={waveSpeed}
waveFrequency={waveFrequency}
waveAmplitude={waveAmplitude}
waveColor={waveColor}
colorNum={colorNum}
pixelSize={pixelSize}
disableAnimation={disableAnimation}
enableMouseInteraction={enableMouseInteraction}
mouseRadius={mouseRadius}
/>
</Canvas>
);
}
Component CSS
.dither-container {
width: 100%;
height: 100%;
position: relative;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: faulty terminal code: ## Integrate the <FaultyTerminal /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: FaultyTerminal
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import FaultyTerminal from './FaultyTerminal';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<FaultyTerminal
scale={1.5}
gridMul={[2, 1]}
digitSize={1.2}
timeScale={1}
pause={false}
scanlineIntensity={1}
glitchAmount={1}
flickerAmount={1}
noiseAmp={1}
chromaticAberration={0}
dither={0}
curvature={0}
tint="#ffffff"
mouseReact={true}
mouseStrength={0.5}
pageLoadAnimation={false}
brightness={1}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| scale | number | 1.5 | Controls the zoom/scale of the pattern. |
| gridMul | Vec2 | [2, 1] | Grid multiplier for glyph density [x, y]. |
| digitSize | number | 1.2 | Size of individual glyphs. |
| timeScale | number | 1 | Animation speed multiplier. |
| pause | boolean | false | Pause/resume animation. |
| scanlineIntensity | number | 1 | Strength of scanline effects. |
| glitchAmount | number | 1 | Glitch displacement intensity. |
| flickerAmount | number | 1 | Flicker effect strength. |
| noiseAmp | number | 1 | Noise pattern amplitude. |
| chromaticAberration | number | 0 | RGB channel separation in pixels. |
| dither | number | boolean | 0 |
| curvature | number | 0 | Barrel distortion amount. |
| tint | string | '#ffffff' | Color tint (hex). |
| mouseReact | boolean | true | Enable/disable mouse interaction. |
| mouseStrength | number | 0.5 | Mouse interaction intensity. |
| pageLoadAnimation | boolean | false | Enable fade-in animation on load. |
| brightness | number | 1 | Overall opacity/brightness control. |
| className | string | '' | Additional CSS classes. |
| style | React.CSSProperties | {} | Inline styles. |
Full Component Source
import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';
import { useEffect, useRef, useMemo, useCallback } from 'react';
import './FaultyTerminal.css';
const vertexShader = `
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragmentShader = `
precision mediump float;
varying vec2 vUv;
uniform float iTime;
uniform vec3 iResolution;
uniform float uScale;
uniform vec2 uGridMul;
uniform float uDigitSize;
uniform float uScanlineIntensity;
uniform float uGlitchAmount;
uniform float uFlickerAmount;
uniform float uNoiseAmp;
uniform float uChromaticAberration;
uniform float uDither;
uniform float uCurvature;
uniform vec3 uTint;
uniform vec2 uMouse;
uniform float uMouseStrength;
uniform float uUseMouse;
uniform float uPageLoadProgress;
uniform float uUsePageLoadAnimation;
uniform float uBrightness;
float time;
float hash21(vec2 p){
p = fract(p * 234.56);
p += dot(p, p + 34.56);
return fract(p.x * p.y);
}
float noise(vec2 p)
{
return sin(p.x * 10.0) * sin(p.y * (3.0 + sin(time * 0.090909))) + 0.2;
}
mat2 rotate(float angle)
{
float c = cos(angle);
float s = sin(angle);
return mat2(c, -s, s, c);
}
float fbm(vec2 p)
{
p *= 1.1;
float f = 0.0;
float amp = 0.5 * uNoiseAmp;
mat2 modify0 = rotate(time * 0.02);
f += amp * noise(p);
p = modify0 * p * 2.0;
amp *= 0.454545;
mat2 modify1 = rotate(time * 0.02);
f += amp * noise(p);
p = modify1 * p * 2.0;
amp *= 0.454545;
mat2 modify2 = rotate(time * 0.08);
f += amp * noise(p);
return f;
}
float pattern(vec2 p, out vec2 q, out vec2 r) {
vec2 offset1 = vec2(1.0);
vec2 offset0 = vec2(0.0);
mat2 rot01 = rotate(0.1 * time);
mat2 rot1 = rotate(0.1);
q = vec2(fbm(p + offset1), fbm(rot01 * p + offset1));
r = vec2(fbm(rot1 * q + offset0), fbm(q + offset0));
return fbm(p + r);
}
float digit(vec2 p){
vec2 grid = uGridMul * 15.0;
vec2 s = floor(p * grid) / grid;
p = p * grid;
vec2 q, r;
float intensity = pattern(s * 0.1, q, r) * 1.3 - 0.03;
if(uUseMouse > 0.5){
vec2 mouseWorld = uMouse * uScale;
float distToMouse = distance(s, mouseWorld);
float mouseInfluence = exp(-distToMouse * 8.0) * uMouseStrength * 10.0;
intensity += mouseInfluence;
float ripple = sin(distToMouse * 20.0 - iTime * 5.0) * 0.1 * mouseInfluence;
intensity += ripple;
}
if(uUsePageLoadAnimation > 0.5){
float cellRandom = fract(sin(dot(s, vec2(12.9898, 78.233))) * 43758.5453);
float cellDelay = cellRandom * 0.8;
float cellProgress = clamp((uPageLoadProgress - cellDelay) / 0.2, 0.0, 1.0);
float fadeAlpha = smoothstep(0.0, 1.0, cellProgress);
intensity *= fadeAlpha;
}
p = fract(p);
p *= uDigitSize;
float px5 = p.x * 5.0;
float py5 = (1.0 - p.y) * 5.0;
float x = fract(px5);
float y = fract(py5);
float i = floor(py5) - 2.0;
float j = floor(px5) - 2.0;
float n = i * i + j * j;
float f = n * 0.0625;
float isOn = step(0.1, intensity - f);
float brightness = isOn * (0.2 + y * 0.8) * (0.75 + x * 0.25);
return step(0.0, p.x) * step(p.x, 1.0) * step(0.0, p.y) * step(p.y, 1.0) * brightness;
}
float onOff(float a, float b, float c)
{
return step(c, sin(iTime + a * cos(iTime * b))) * uFlickerAmount;
}
float displace(vec2 look)
{
float y = look.y - mod(iTime * 0.25, 1.0);
float window = 1.0 / (1.0 + 50.0 * y * y);
return sin(look.y * 20.0 + iTime) * 0.0125 * onOff(4.0, 2.0, 0.8) * (1.0 + cos(iTime * 60.0)) * window;
}
vec3 getColor(vec2 p){
float bar = step(mod(p.y + time * 20.0, 1.0), 0.2) * 0.4 + 1.0;
bar *= uScanlineIntensity;
float displacement = displace(p);
p.x += displacement;
if (uGlitchAmount != 1.0) {
float extra = displacement * (uGlitchAmount - 1.0);
p.x += extra;
}
float middle = digit(p);
const float off = 0.002;
float sum = digit(p + vec2(-off, -off)) + digit(p + vec2(0.0, -off)) + digit(p + vec2(off, -off)) +
digit(p + vec2(-off, 0.0)) + digit(p + vec2(0.0, 0.0)) + digit(p + vec2(off, 0.0)) +
digit(p + vec2(-off, off)) + digit(p + vec2(0.0, off)) + digit(p + vec2(off, off));
vec3 baseColor = vec3(0.9) * middle + sum * 0.1 * vec3(1.0) * bar;
return baseColor;
}
vec2 barrel(vec2 uv){
vec2 c = uv * 2.0 - 1.0;
float r2 = dot(c, c);
c *= 1.0 + uCurvature * r2;
return c * 0.5 + 0.5;
}
void main() {
time = iTime * 0.333333;
vec2 uv = vUv;
if(uCurvature != 0.0){
uv = barrel(uv);
}
vec2 p = uv * uScale;
vec3 col = getColor(p);
if(uChromaticAberration != 0.0){
vec2 ca = vec2(uChromaticAberration) / iResolution.xy;
col.r = getColor(p + ca).r;
col.b = getColor(p - ca).b;
}
col *= uTint;
col *= uBrightness;
if(uDither > 0.0){
float rnd = hash21(gl_FragCoord.xy);
col += (rnd - 0.5) * (uDither * 0.003922);
}
gl_FragColor = vec4(col, 1.0);
}
`;
function hexToRgb(hex) {
let h = hex.replace('#', '').trim();
if (h.length === 3)
h = h
.split('')
.map(c => c + c)
.join('');
const num = parseInt(h, 16);
return [((num >> 16) & 255) / 255, ((num >> 8) & 255) / 255, (num & 255) / 255];
}
export default function FaultyTerminal({
scale = 1,
gridMul = [2, 1],
digitSize = 1.5,
timeScale = 0.3,
pause = false,
scanlineIntensity = 0.3,
glitchAmount = 1,
flickerAmount = 1,
noiseAmp = 0,
chromaticAberration = 0,
dither = 0,
curvature = 0.2,
tint = '#ffffff',
mouseReact = true,
mouseStrength = 0.2,
dpr = Math.min(window.devicePixelRatio || 1, 2),
pageLoadAnimation = true,
brightness = 1,
className,
style,
...rest
}) {
const containerRef = useRef(null);
const programRef = useRef(null);
const rendererRef = useRef(null);
const mouseRef = useRef({ x: 0.5, y: 0.5 });
const smoothMouseRef = useRef({ x: 0.5, y: 0.5 });
const frozenTimeRef = useRef(0);
const rafRef = useRef(0);
const loadAnimationStartRef = useRef(0);
const timeOffsetRef = useRef(Math.random() * 100);
const tintVec = useMemo(() => hexToRgb(tint), [tint]);
const ditherValue = useMemo(() => (typeof dither === 'boolean' ? (dither ? 1 : 0) : dither), [dither]);
const handleMouseMove = useCallback(e => {
const ctn = containerRef.current;
if (!ctn) return;
const rect = ctn.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = 1 - (e.clientY - rect.top) / rect.height;
mouseRef.current = { x, y };
}, []);
useEffect(() => {
const ctn = containerRef.current;
if (!ctn) return;
const renderer = new Renderer({ dpr });
rendererRef.current = renderer;
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 1);
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
iTime: { value: 0 },
iResolution: {
value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)
},
uScale: { value: scale },
uGridMul: { value: new Float32Array(gridMul) },
uDigitSize: { value: digitSize },
uScanlineIntensity: { value: scanlineIntensity },
uGlitchAmount: { value: glitchAmount },
uFlickerAmount: { value: flickerAmount },
uNoiseAmp: { value: noiseAmp },
uChromaticAberration: { value: chromaticAberration },
uDither: { value: ditherValue },
uCurvature: { value: curvature },
uTint: { value: new Color(tintVec[0], tintVec[1], tintVec[2]) },
uMouse: {
value: new Float32Array([smoothMouseRef.current.x, smoothMouseRef.current.y])
},
uMouseStrength: { value: mouseStrength },
uUseMouse: { value: mouseReact ? 1 : 0 },
uPageLoadProgress: { value: pageLoadAnimation ? 0 : 1 },
uUsePageLoadAnimation: { value: pageLoadAnimation ? 1 : 0 },
uBrightness: { value: brightness }
}
});
programRef.current = program;
const mesh = new Mesh(gl, { geometry, program });
function resize() {
if (!ctn || !renderer) return;
renderer.setSize(ctn.offsetWidth, ctn.offsetHeight);
program.uniforms.iResolution.value = new Color(
gl.canvas.width,
gl.canvas.height,
gl.canvas.width / gl.canvas.height
);
}
const resizeObserver = new ResizeObserver(() => resize());
resizeObserver.observe(ctn);
resize();
const update = t => {
rafRef.current = requestAnimationFrame(update);
if (pageLoadAnimation && loadAnimationStartRef.current === 0) {
loadAnimationStartRef.current = t;
}
if (!pause) {
const elapsed = (t * 0.001 + timeOffsetRef.current) * timeScale;
program.uniforms.iTime.value = elapsed;
frozenTimeRef.current = elapsed;
} else {
program.uniforms.iTime.value = frozenTimeRef.current;
}
if (pageLoadAnimation && loadAnimationStartRef.current > 0) {
const animationDuration = 2000;
const animationElapsed = t - loadAnimationStartRef.current;
const progress = Math.min(animationElapsed / animationDuration, 1);
program.uniforms.uPageLoadProgress.value = progress;
}
if (mouseReact) {
const dampingFactor = 0.08;
const smoothMouse = smoothMouseRef.current;
const mouse = mouseRef.current;
smoothMouse.x += (mouse.x - smoothMouse.x) * dampingFactor;
smoothMouse.y += (mouse.y - smoothMouse.y) * dampingFactor;
const mouseUniform = program.uniforms.uMouse.value;
mouseUniform[0] = smoothMouse.x;
mouseUniform[1] = smoothMouse.y;
}
renderer.render({ scene: mesh });
};
rafRef.current = requestAnimationFrame(update);
ctn.appendChild(gl.canvas);
if (mouseReact) ctn.addEventListener('mousemove', handleMouseMove);
return () => {
cancelAnimationFrame(rafRef.current);
resizeObserver.disconnect();
if (mouseReact) ctn.removeEventListener('mousemove', handleMouseMove);
if (gl.canvas.parentElement === ctn) ctn.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
loadAnimationStartRef.current = 0;
timeOffsetRef.current = Math.random() * 100;
};
}, [
dpr,
pause,
timeScale,
scale,
gridMul,
digitSize,
scanlineIntensity,
glitchAmount,
flickerAmount,
noiseAmp,
chromaticAberration,
ditherValue,
curvature,
tintVec,
mouseReact,
mouseStrength,
pageLoadAnimation,
brightness,
handleMouseMove
]);
return <div ref={containerRef} className={`faulty-terminal-container ${className}`} style={style} {...rest} />;
}
Component CSS
.faulty-terminal-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: ripple grid code:## Integrate the <RippleGrid /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: RippleGrid
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import RippleGrid from './RippleGrid';
<div style={{position: 'relative', height: '500px', overflow: 'hidden'}}>
<RippleGrid
enableRainbow={false}
gridColor="#ffffff"
rippleIntensity={0.05}
gridSize={10}
gridThickness={15}
mouseInteraction={true}
mouseInteractionRadius={1.2}
opacity={0.8}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| enableRainbow | boolean | false | Enables rainbow color cycling animation for the grid. |
| gridColor | string | '#ffffff' | Color of the grid when rainbow mode is disabled. |
| rippleIntensity | number | 0.05 | Controls the intensity of the ripple effect from the center. |
| gridSize | number | 10.0 | Controls the density/size of the grid pattern. |
| gridThickness | number | 15.0 | Controls the thickness of the grid lines. |
| fadeDistance | number | 1.5 | Controls how far the fade effect extends from the center. |
| vignetteStrength | number | 2.0 | Controls the intensity of the vignette (edge darkening) effect. |
| glowIntensity | number | 0.1 | Adds a glow effect to the grid lines. |
| opacity | number | 1.0 | Overall opacity of the entire effect. |
| gridRotation | number | 0 | Rotate the entire grid pattern by degrees. |
| mouseInteraction | boolean | false | Enable mouse/touch interaction to create ripples. |
| mouseInteractionRadius | number | 0.8 | Controls the radius of the mouse interaction effect. |
Full Component Source
import { useRef, useEffect } from 'react';
import { Renderer, Program, Triangle, Mesh } from 'ogl';
import './RippleGrid.css';
const RippleGrid = ({
enableRainbow = false,
gridColor = '#ffffff',
rippleIntensity = 0.05,
gridSize = 10.0,
gridThickness = 15.0,
fadeDistance = 1.5,
vignetteStrength = 2.0,
glowIntensity = 0.1,
opacity = 1.0,
gridRotation = 0,
mouseInteraction = true,
mouseInteractionRadius = 1
}) => {
const containerRef = useRef(null);
const mousePositionRef = useRef({ x: 0.5, y: 0.5 });
const targetMouseRef = useRef({ x: 0.5, y: 0.5 });
const mouseInfluenceRef = useRef(0);
const uniformsRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const hexToRgb = hex => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255]
: [1, 1, 1];
};
const renderer = new Renderer({
dpr: Math.min(window.devicePixelRatio, 2),
alpha: true
});
const gl = renderer.gl;
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.canvas.style.width = '100%';
gl.canvas.style.height = '100%';
containerRef.current.appendChild(gl.canvas);
const vert = `
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = position * 0.5 + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}`;
const frag = `precision highp float;
uniform float iTime;
uniform vec2 iResolution;
uniform bool enableRainbow;
uniform vec3 gridColor;
uniform float rippleIntensity;
uniform float gridSize;
uniform float gridThickness;
uniform float fadeDistance;
uniform float vignetteStrength;
uniform float glowIntensity;
uniform float opacity;
uniform float gridRotation;
uniform bool mouseInteraction;
uniform vec2 mousePosition;
uniform float mouseInfluence;
uniform float mouseInteractionRadius;
varying vec2 vUv;
float pi = 3.141592;
mat2 rotate(float angle) {
float s = sin(angle);
float c = cos(angle);
return mat2(c, -s, s, c);
}
void main() {
vec2 uv = vUv * 2.0 - 1.0;
uv.x *= iResolution.x / iResolution.y;
if (gridRotation != 0.0) {
uv = rotate(gridRotation * pi / 180.0) * uv;
}
float dist = length(uv);
float func = sin(pi * (iTime - dist));
vec2 rippleUv = uv + uv * func * rippleIntensity;
if (mouseInteraction && mouseInfluence > 0.0) {
vec2 mouseUv = (mousePosition * 2.0 - 1.0);
mouseUv.x *= iResolution.x / iResolution.y;
float mouseDist = length(uv - mouseUv);
float influence = mouseInfluence * exp(-mouseDist * mouseDist / (mouseInteractionRadius * mouseInteractionRadius));
float mouseWave = sin(pi * (iTime * 2.0 - mouseDist * 3.0)) * influence;
rippleUv += normalize(uv - mouseUv) * mouseWave * rippleIntensity * 0.3;
}
vec2 a = sin(gridSize * 0.5 * pi * rippleUv - pi / 2.0);
vec2 b = abs(a);
float aaWidth = 0.5;
vec2 smoothB = vec2(
smoothstep(0.0, aaWidth, b.x),
smoothstep(0.0, aaWidth, b.y)
);
vec3 color = vec3(0.0);
color += exp(-gridThickness * smoothB.x * (0.8 + 0.5 * sin(pi * iTime)));
color += exp(-gridThickness * smoothB.y);
color += 0.5 * exp(-(gridThickness / 4.0) * sin(smoothB.x));
color += 0.5 * exp(-(gridThickness / 3.0) * smoothB.y);
if (glowIntensity > 0.0) {
color += glowIntensity * exp(-gridThickness * 0.5 * smoothB.x);
color += glowIntensity * exp(-gridThickness * 0.5 * smoothB.y);
}
float ddd = exp(-2.0 * clamp(pow(dist, fadeDistance), 0.0, 1.0));
vec2 vignetteCoords = vUv - 0.5;
float vignetteDistance = length(vignetteCoords);
float vignette = 1.0 - pow(vignetteDistance * 2.0, vignetteStrength);
vignette = clamp(vignette, 0.0, 1.0);
vec3 t;
if (enableRainbow) {
t = vec3(
uv.x * 0.5 + 0.5 * sin(iTime),
uv.y * 0.5 + 0.5 * cos(iTime),
pow(cos(iTime), 4.0)
) + 0.5;
} else {
t = gridColor;
}
float finalFade = ddd * vignette;
float alpha = length(color) * finalFade * opacity;
gl_FragColor = vec4(color * t * finalFade * opacity, alpha);
}`;
const uniforms = {
iTime: { value: 0 },
iResolution: { value: [1, 1] },
enableRainbow: { value: enableRainbow },
gridColor: { value: hexToRgb(gridColor) },
rippleIntensity: { value: rippleIntensity },
gridSize: { value: gridSize },
gridThickness: { value: gridThickness },
fadeDistance: { value: fadeDistance },
vignetteStrength: { value: vignetteStrength },
glowIntensity: { value: glowIntensity },
opacity: { value: opacity },
gridRotation: { value: gridRotation },
mouseInteraction: { value: mouseInteraction },
mousePosition: { value: [0.5, 0.5] },
mouseInfluence: { value: 0 },
mouseInteractionRadius: { value: mouseInteractionRadius }
};
uniformsRef.current = uniforms;
const geometry = new Triangle(gl);
const program = new Program(gl, { vertex: vert, fragment: frag, uniforms });
const mesh = new Mesh(gl, { geometry, program });
const resize = () => {
const { clientWidth: w, clientHeight: h } = containerRef.current;
renderer.setSize(w, h);
uniforms.iResolution.value = [w, h];
};
const handleMouseMove = e => {
if (!mouseInteraction || !containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = 1.0 - (e.clientY - rect.top) / rect.height; // Flip Y coordinate
targetMouseRef.current = { x, y };
};
const handleMouseEnter = () => {
if (!mouseInteraction) return;
mouseInfluenceRef.current = 1.0;
};
const handleMouseLeave = () => {
if (!mouseInteraction) return;
mouseInfluenceRef.current = 0.0;
};
window.addEventListener('resize', resize);
if (mouseInteraction) {
containerRef.current.addEventListener('mousemove', handleMouseMove);
containerRef.current.addEventListener('mouseenter', handleMouseEnter);
containerRef.current.addEventListener('mouseleave', handleMouseLeave);
}
resize();
const render = t => {
uniforms.iTime.value = t * 0.001;
const lerpFactor = 0.1;
mousePositionRef.current.x += (targetMouseRef.current.x - mousePositionRef.current.x) * lerpFactor;
mousePositionRef.current.y += (targetMouseRef.current.y - mousePositionRef.current.y) * lerpFactor;
const currentInfluence = uniforms.mouseInfluence.value;
const targetInfluence = mouseInfluenceRef.current;
uniforms.mouseInfluence.value += (targetInfluence - currentInfluence) * 0.05;
uniforms.mousePosition.value = [mousePositionRef.current.x, mousePositionRef.current.y];
renderer.render({ scene: mesh });
requestAnimationFrame(render);
};
requestAnimationFrame(render);
const container = containerRef.current;
return () => {
window.removeEventListener('resize', resize);
if (mouseInteraction && container) {
container.removeEventListener('mousemove', handleMouseMove);
container.removeEventListener('mouseenter', handleMouseEnter);
container.removeEventListener('mouseleave', handleMouseLeave);
}
renderer.gl.getExtension('WEBGL_lose_context')?.loseContext();
container?.removeChild(gl.canvas);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (!uniformsRef.current) return;
const hexToRgb = hex => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255]
: [1, 1, 1];
};
uniformsRef.current.enableRainbow.value = enableRainbow;
uniformsRef.current.gridColor.value = hexToRgb(gridColor);
uniformsRef.current.rippleIntensity.value = rippleIntensity;
uniformsRef.current.gridSize.value = gridSize;
uniformsRef.current.gridThickness.value = gridThickness;
uniformsRef.current.fadeDistance.value = fadeDistance;
uniformsRef.current.vignetteStrength.value = vignetteStrength;
uniformsRef.current.glowIntensity.value = glowIntensity;
uniformsRef.current.opacity.value = opacity;
uniformsRef.current.gridRotation.value = gridRotation;
uniformsRef.current.mouseInteraction.value = mouseInteraction;
uniformsRef.current.mouseInteractionRadius.value = mouseInteractionRadius;
}, [
enableRainbow,
gridColor,
rippleIntensity,
gridSize,
gridThickness,
fadeDistance,
vignetteStrength,
glowIntensity,
opacity,
gridRotation,
mouseInteraction,
mouseInteractionRadius
]);
return <div ref={containerRef} className="ripple-grid-container" />;
};
export default RippleGrid;
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: dot field code: ## Integrate the <DotField /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: DotField
Variant: JavaScript + CSS
Usage Example
import DotField from './DotField';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<DotField
dotRadius={1.5}
dotSpacing={14}
bulgeStrength={67}
glowRadius={160}
sparkle={false}
waveAmplitude={0}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| dotRadius | number | 1.5 | Radius of each individual dot in the grid. |
| dotSpacing | number | 14 | Spacing between dots in the grid. |
| cursorRadius | number | 500 | Radius of the cursor interaction area. |
| cursorForce | number | 0.1 | Force applied to dots when not in bulge mode. |
| bulgeOnly | boolean | true | When true, dots bulge away from cursor. When false, dots are pushed with physics. |
| bulgeStrength | number | 67 | Strength of the bulge effect around the cursor. |
| glowRadius | number | 160 | Radius of the SVG glow effect that follows the cursor. |
| sparkle | boolean | false | When enabled, ~3% of dots randomly sparkle at a larger size. |
| waveAmplitude | number | 0 | Amplitude of the wave displacement animation applied to dots. |
| gradientFrom | string | 'rgba(168, 85, 247, 0.35)' | Start color of the diagonal gradient applied to dots. |
| gradientTo | string | 'rgba(180, 151, 207, 0.25)' | End color of the diagonal gradient applied to dots. |
| glowColor | string | '#120F17' | Color of the radial glow effect that follows the cursor. |
Full Component Source
import { useEffect, useRef, memo } from 'react';
import './DotField.css';
const TWO_PI = Math.PI * 2;
const DotField = memo(({
dotRadius = 1.5,
dotSpacing = 14,
cursorRadius = 500,
cursorForce = 0.1,
bulgeOnly = true,
bulgeStrength = 67,
glowRadius = 160,
sparkle = false,
waveAmplitude = 0,
gradientFrom = 'rgba(168, 85, 247, 0.35)',
gradientTo = 'rgba(180, 151, 207, 0.25)',
glowColor = '#120F17',
...rest
}) => {
const canvasRef = useRef(null);
const svgRef = useRef(null);
const glowRef = useRef(null);
const dotsRef = useRef([]);
const mouseRef = useRef({ x: -9999, y: -9999, prevX: -9999, prevY: -9999, speed: 0 });
const rafRef = useRef(null);
const sizeRef = useRef({ w: 0, h: 0, offsetX: 0, offsetY: 0 });
const glowOpacity = useRef(0);
const engagement = useRef(0);
const propsRef = useRef({});
propsRef.current = { dotRadius, dotSpacing, cursorRadius, cursorForce, bulgeOnly, bulgeStrength, sparkle, waveAmplitude, gradientFrom, gradientTo };
const rebuildRef = useRef(null);
const glowIdRef = useRef(`dot-field-glow-${Math.random().toString(36).slice(2, 9)}`);
useEffect(() => {
const canvas = canvasRef.current;
const glowEl = glowRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d', { alpha: true });
const dpr = Math.min(window.devicePixelRatio || 1, 2);
let resizeTimer;
function resize() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(doResize, 100);
}
function doResize() {
const rect = canvas.parentElement.getBoundingClientRect();
const w = rect.width;
const h = rect.height;
canvas.width = w * dpr;
canvas.height = h * dpr;
canvas.style.width = `${w}px`;
canvas.style.height = `${h}px`;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
sizeRef.current = {
w,
h,
offsetX: rect.left + window.scrollX,
offsetY: rect.top + window.scrollY,
};
buildDots(w, h);
}
function buildDots(w, h) {
const p = propsRef.current;
const step = p.dotRadius + p.dotSpacing;
const cols = Math.floor(w / step);
const rows = Math.floor(h / step);
const padX = (w % step) / 2;
const padY = (h % step) / 2;
const dots = new Array(rows * cols);
let idx = 0;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const ax = padX + col * step + step / 2;
const ay = padY + row * step + step / 2;
dots[idx++] = { ax, ay, sx: ax, sy: ay, vx: 0, vy: 0, x: ax, y: ay };
}
}
dotsRef.current = dots;
}
function onMouseMove(e) {
const s = sizeRef.current;
mouseRef.current.x = e.pageX - s.offsetX;
mouseRef.current.y = e.pageY - s.offsetY;
}
function updateMouseSpeed() {
const m = mouseRef.current;
const dx = m.prevX - m.x;
const dy = m.prevY - m.y;
const dist = Math.sqrt(dx * dx + dy * dy);
m.speed += (dist - m.speed) * 0.5;
if (m.speed < 0.001) m.speed = 0;
m.prevX = m.x;
m.prevY = m.y;
}
const speedInterval = setInterval(updateMouseSpeed, 20);
let frameCount = 0;
function tick() {
frameCount++;
const dots = dotsRef.current;
const m = mouseRef.current;
const { w, h } = sizeRef.current;
const p = propsRef.current;
const len = dots.length;
const t = frameCount * 0.02;
const targetEngagement = Math.min(m.speed / 5, 1);
engagement.current += (targetEngagement - engagement.current) * 0.06;
if (engagement.current < 0.001) engagement.current = 0;
const eng = engagement.current;
glowOpacity.current += (eng - glowOpacity.current) * 0.08;
if (glowEl) {
glowEl.setAttribute('cx', m.x);
glowEl.setAttribute('cy', m.y);
glowEl.style.opacity = glowOpacity.current;
}
ctx.clearRect(0, 0, w, h);
const grad = ctx.createLinearGradient(0, 0, w, h);
grad.addColorStop(0, p.gradientFrom);
grad.addColorStop(1, p.gradientTo);
ctx.fillStyle = grad;
const cr = p.cursorRadius;
const crSq = cr * cr;
const rad = p.dotRadius / 2;
const isBulge = p.bulgeOnly;
ctx.beginPath();
for (let i = 0; i < len; i++) {
const d = dots[i];
const dx = m.x - d.ax;
const dy = m.y - d.ay;
const distSq = dx * dx + dy * dy;
if (distSq < crSq && eng > 0.01) {
const dist = Math.sqrt(distSq);
if (isBulge) {
const t = 1 - dist / cr;
const push = t * t * p.bulgeStrength * eng;
const angle = Math.atan2(dy, dx);
d.sx += (d.ax - Math.cos(angle) * push - d.sx) * 0.15;
d.sy += (d.ay - Math.sin(angle) * push - d.sy) * 0.15;
} else {
const angle = Math.atan2(dy, dx);
const move = (500 / dist) * (m.speed * p.cursorForce);
d.vx += Math.cos(angle) * -move;
d.vy += Math.sin(angle) * -move;
}
} else if (isBulge) {
d.sx += (d.ax - d.sx) * 0.1;
d.sy += (d.ay - d.sy) * 0.1;
}
if (!isBulge) {
d.vx *= 0.9;
d.vy *= 0.9;
d.x = d.ax + d.vx;
d.y = d.ay + d.vy;
d.sx += (d.x - d.sx) * 0.1;
d.sy += (d.y - d.sy) * 0.1;
}
let drawX = d.sx;
let drawY = d.sy;
if (p.waveAmplitude > 0) {
drawY += Math.sin(d.ax * 0.03 + t) * p.waveAmplitude;
drawX += Math.cos(d.ay * 0.03 + t * 0.7) * p.waveAmplitude * 0.5;
}
if (p.sparkle) {
const hash = ((i * 2654435761) ^ (frameCount >> 3)) >>> 0;
if ((hash % 100) < 3) {
ctx.moveTo(drawX + rad * 1.8, drawY);
ctx.arc(drawX, drawY, rad * 1.8, 0, TWO_PI);
} else {
ctx.moveTo(drawX + rad, drawY);
ctx.arc(drawX, drawY, rad, 0, TWO_PI);
}
} else {
ctx.moveTo(drawX + rad, drawY);
ctx.arc(drawX, drawY, rad, 0, TWO_PI);
}
}
ctx.fill();
rafRef.current = requestAnimationFrame(tick);
}
doResize();
window.addEventListener('resize', resize);
window.addEventListener('mousemove', onMouseMove, { passive: true });
rafRef.current = requestAnimationFrame(tick);
rebuildRef.current = () => {
const { w, h } = sizeRef.current;
if (w > 0 && h > 0) buildDots(w, h);
};
return () => {
cancelAnimationFrame(rafRef.current);
clearInterval(speedInterval);
clearTimeout(resizeTimer);
window.removeEventListener('resize', resize);
window.removeEventListener('mousemove', onMouseMove);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
rebuildRef.current?.();
}, [dotRadius, dotSpacing]);
return (
<div className="dot-field-container" {...rest}>
<canvas
ref={canvasRef}
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
}}
/>
<svg
ref={svgRef}
style={{
position: 'absolute',
inset: 0,
width: '100%',
height: '100%',
pointerEvents: 'none',
}}
>
<defs>
<radialGradient id={glowIdRef.current}>
<stop offset="0%" stopColor={glowColor} />
<stop offset="100%" stopColor="transparent" />
</radialGradient>
</defs>
<circle
ref={glowRef}
cx="-9999"
cy="-9999"
r={glowRadius}
fill={`url(#${glowIdRef.current})`}
style={{ opacity: 0, willChange: 'opacity' }}
/>
</svg>
</div>
);
});
DotField.displayName = 'DotField';
export default DotField;
Component CSS
.dot-field-container {
position: relative;
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: dot grid code: ## Integrate the <DotGrid /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: DotGrid
Variant: JavaScript + CSS
Dependencies: gsap
Usage Example
import DotGrid from './DotGrid';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<DotGrid
dotSize={10}
gap={15}
baseColor="#5227FF"
activeColor="#5227FF"
proximity={120}
shockRadius={250}
shockStrength={5}
resistance={750}
returnDuration={1.5}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| dotSize | number | 16 | Size of each dot in pixels. |
| gap | number | 32 | Gap between each dot in pixels. |
| baseColor | string | '#5227FF' | Base color of the dots. |
| activeColor | string | '#5227FF' | Color of dots when hovered or activated. |
| proximity | number | 150 | Radius around the mouse pointer within which dots react. |
| speedTrigger | number | 100 | Mouse speed threshold to trigger inertia effect. |
| shockRadius | number | 250 | Radius of the shockwave effect on click. |
| shockStrength | number | 5 | Strength of the shockwave effect on click. |
| maxSpeed | number | 5000 | Maximum speed for inertia calculation. |
| resistance | number | 750 | Resistance for the inertia effect. |
| returnDuration | number | 1.5 | Duration for dots to return to their original position after inertia. |
| className | string | '' | Additional CSS classes for the component. |
| style | React.CSSProperties | {} | Inline styles for the component. |
Full Component Source
'use client';
import { useRef, useEffect, useCallback, useMemo } from 'react';
import { gsap } from 'gsap';
import { InertiaPlugin } from 'gsap/InertiaPlugin';
import './DotGrid.css';
gsap.registerPlugin(InertiaPlugin);
const throttle = (func, limit) => {
let lastCall = 0;
return function (...args) {
const now = performance.now();
if (now - lastCall >= limit) {
lastCall = now;
func.apply(this, args);
}
};
};
function hexToRgb(hex) {
const m = hex.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
if (!m) return { r: 0, g: 0, b: 0 };
return {
r: parseInt(m[1], 16),
g: parseInt(m[2], 16),
b: parseInt(m[3], 16)
};
}
const DotGrid = ({
dotSize = 16,
gap = 32,
baseColor = '#5227FF',
activeColor = '#5227FF',
proximity = 150,
speedTrigger = 100,
shockRadius = 250,
shockStrength = 5,
maxSpeed = 5000,
resistance = 750,
returnDuration = 1.5,
className = '',
style
}) => {
const wrapperRef = useRef(null);
const canvasRef = useRef(null);
const dotsRef = useRef([]);
const pointerRef = useRef({
x: 0,
y: 0,
vx: 0,
vy: 0,
speed: 0,
lastTime: 0,
lastX: 0,
lastY: 0
});
const baseRgb = useMemo(() => hexToRgb(baseColor), [baseColor]);
const activeRgb = useMemo(() => hexToRgb(activeColor), [activeColor]);
const circlePath = useMemo(() => {
if (typeof window === 'undefined' || !window.Path2D) return null;
const p = new window.Path2D();
p.arc(0, 0, dotSize / 2, 0, Math.PI * 2);
return p;
}, [dotSize]);
const buildGrid = useCallback(() => {
const wrap = wrapperRef.current;
const canvas = canvasRef.current;
if (!wrap || !canvas) return;
const { width, height } = wrap.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const ctx = canvas.getContext('2d');
if (ctx) ctx.scale(dpr, dpr);
const cols = Math.floor((width + gap) / (dotSize + gap));
const rows = Math.floor((height + gap) / (dotSize + gap));
const cell = dotSize + gap;
const gridW = cell * cols - gap;
const gridH = cell * rows - gap;
const extraX = width - gridW;
const extraY = height - gridH;
const startX = extraX / 2 + dotSize / 2;
const startY = extraY / 2 + dotSize / 2;
const dots = [];
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const cx = startX + x * cell;
const cy = startY + y * cell;
dots.push({ cx, cy, xOffset: 0, yOffset: 0, _inertiaApplied: false });
}
}
dotsRef.current = dots;
}, [dotSize, gap]);
useEffect(() => {
if (!circlePath) return;
let rafId;
const proxSq = proximity * proximity;
const draw = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const { x: px, y: py } = pointerRef.current;
for (const dot of dotsRef.current) {
const ox = dot.cx + dot.xOffset;
const oy = dot.cy + dot.yOffset;
const dx = dot.cx - px;
const dy = dot.cy - py;
const dsq = dx * dx + dy * dy;
let style = baseColor;
if (dsq <= proxSq) {
const dist = Math.sqrt(dsq);
const t = 1 - dist / proximity;
const r = Math.round(baseRgb.r + (activeRgb.r - baseRgb.r) * t);
const g = Math.round(baseRgb.g + (activeRgb.g - baseRgb.g) * t);
const b = Math.round(baseRgb.b + (activeRgb.b - baseRgb.b) * t);
style = `rgb(${r},${g},${b})`;
}
ctx.save();
ctx.translate(ox, oy);
ctx.fillStyle = style;
ctx.fill(circlePath);
ctx.restore();
}
rafId = requestAnimationFrame(draw);
};
draw();
return () => cancelAnimationFrame(rafId);
}, [proximity, baseColor, activeRgb, baseRgb, circlePath]);
useEffect(() => {
buildGrid();
let ro = null;
if ('ResizeObserver' in window) {
ro = new ResizeObserver(buildGrid);
wrapperRef.current && ro.observe(wrapperRef.current);
} else {
window.addEventListener('resize', buildGrid);
}
return () => {
if (ro) ro.disconnect();
else window.removeEventListener('resize', buildGrid);
};
}, [buildGrid]);
useEffect(() => {
const onMove = e => {
const now = performance.now();
const pr = pointerRef.current;
const dt = pr.lastTime ? now - pr.lastTime : 16;
const dx = e.clientX - pr.lastX;
const dy = e.clientY - pr.lastY;
let vx = (dx / dt) * 1000;
let vy = (dy / dt) * 1000;
let speed = Math.hypot(vx, vy);
if (speed > maxSpeed) {
const scale = maxSpeed / speed;
vx *= scale;
vy *= scale;
speed = maxSpeed;
}
pr.lastTime = now;
pr.lastX = e.clientX;
pr.lastY = e.clientY;
pr.vx = vx;
pr.vy = vy;
pr.speed = speed;
const rect = canvasRef.current.getBoundingClientRect();
pr.x = e.clientX - rect.left;
pr.y = e.clientY - rect.top;
for (const dot of dotsRef.current) {
const dist = Math.hypot(dot.cx - pr.x, dot.cy - pr.y);
if (speed > speedTrigger && dist < proximity && !dot._inertiaApplied) {
dot._inertiaApplied = true;
gsap.killTweensOf(dot);
const pushX = dot.cx - pr.x + vx * 0.005;
const pushY = dot.cy - pr.y + vy * 0.005;
gsap.to(dot, {
inertia: { xOffset: pushX, yOffset: pushY, resistance },
onComplete: () => {
gsap.to(dot, {
xOffset: 0,
yOffset: 0,
duration: returnDuration,
ease: 'elastic.out(1,0.75)'
});
dot._inertiaApplied = false;
}
});
}
}
};
const onClick = e => {
const rect = canvasRef.current.getBoundingClientRect();
const cx = e.clientX - rect.left;
const cy = e.clientY - rect.top;
for (const dot of dotsRef.current) {
const dist = Math.hypot(dot.cx - cx, dot.cy - cy);
if (dist < shockRadius && !dot._inertiaApplied) {
dot._inertiaApplied = true;
gsap.killTweensOf(dot);
const falloff = Math.max(0, 1 - dist / shockRadius);
const pushX = (dot.cx - cx) * shockStrength * falloff;
const pushY = (dot.cy - cy) * shockStrength * falloff;
gsap.to(dot, {
inertia: { xOffset: pushX, yOffset: pushY, resistance },
onComplete: () => {
gsap.to(dot, {
xOffset: 0,
yOffset: 0,
duration: returnDuration,
ease: 'elastic.out(1,0.75)'
});
dot._inertiaApplied = false;
}
});
}
}
};
const throttledMove = throttle(onMove, 50);
window.addEventListener('mousemove', throttledMove, { passive: true });
window.addEventListener('click', onClick);
return () => {
window.removeEventListener('mousemove', throttledMove);
window.removeEventListener('click', onClick);
};
}, [maxSpeed, speedTrigger, proximity, resistance, returnDuration, shockRadius, shockStrength]);
return (
<section className={`dot-grid ${className}`} style={style}>
<div ref={wrapperRef} className="dot-grid__wrap">
<canvas ref={canvasRef} className="dot-grid__canvas" />
</div>
</section>
);
};
export default DotGrid;
Component CSS
.dot-grid {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
position: relative;
}
.dot-grid__wrap {
width: 100%;
height: 100%;
position: relative;
}
.dot-grid__canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: threads code: ## Integrate the <Threads /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Threads
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Threads from './Threads';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Threads
amplitude={1}
distance={0}
enableMouseInteraction={true}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| color | [number, number, number] | [1, 1, 1] | Customizes the color of the lines (RGB). |
| amplitude | number | 1 | Adjusts the intensity of the wave effect on the lines. |
| distance | number | 0 | Controls the spacing between the lines. A value of 0 means no offset. |
| enableMouseInteraction | boolean | false | Enables smooth mouse hover effects that modulate the line's movement and amplitude. |
Full Component Source
import { useEffect, useRef } from 'react';
import { Renderer, Program, Mesh, Triangle, Color } from 'ogl';
import './Threads.css';
const vertexShader = `
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragmentShader = `
precision highp float;
uniform float iTime;
uniform vec3 iResolution;
uniform vec3 uColor;
uniform float uAmplitude;
uniform float uDistance;
uniform vec2 uMouse;
#define PI 3.1415926538
const int u_line_count = 40;
const float u_line_width = 7.0;
const float u_line_blur = 10.0;
float Perlin2D(vec2 P) {
vec2 Pi = floor(P);
vec4 Pf_Pfmin1 = P.xyxy - vec4(Pi, Pi + 1.0);
vec4 Pt = vec4(Pi.xy, Pi.xy + 1.0);
Pt = Pt - floor(Pt * (1.0 / 71.0)) * 71.0;
Pt += vec2(26.0, 161.0).xyxy;
Pt *= Pt;
Pt = Pt.xzxz * Pt.yyww;
vec4 hash_x = fract(Pt * (1.0 / 951.135664));
vec4 hash_y = fract(Pt * (1.0 / 642.949883));
vec4 grad_x = hash_x - 0.49999;
vec4 grad_y = hash_y - 0.49999;
vec4 grad_results = inversesqrt(grad_x * grad_x + grad_y * grad_y)
* (grad_x * Pf_Pfmin1.xzxz + grad_y * Pf_Pfmin1.yyww);
grad_results *= 1.4142135623730950;
vec2 blend = Pf_Pfmin1.xy * Pf_Pfmin1.xy * Pf_Pfmin1.xy
* (Pf_Pfmin1.xy * (Pf_Pfmin1.xy * 6.0 - 15.0) + 10.0);
vec4 blend2 = vec4(blend, vec2(1.0 - blend));
return dot(grad_results, blend2.zxzx * blend2.wwyy);
}
float pixel(float count, vec2 resolution) {
return (1.0 / max(resolution.x, resolution.y)) * count;
}
float lineFn(vec2 st, float width, float perc, float offset, vec2 mouse, float time, float amplitude, float distance) {
float split_offset = (perc * 0.4);
float split_point = 0.1 + split_offset;
float amplitude_normal = smoothstep(split_point, 0.7, st.x);
float amplitude_strength = 0.5;
float finalAmplitude = amplitude_normal * amplitude_strength
* amplitude * (1.0 + (mouse.y - 0.5) * 0.2);
float time_scaled = time / 10.0 + (mouse.x - 0.5) * 1.0;
float blur = smoothstep(split_point, split_point + 0.05, st.x) * perc;
float xnoise = mix(
Perlin2D(vec2(time_scaled, st.x + perc) * 2.5),
Perlin2D(vec2(time_scaled, st.x + time_scaled) * 3.5) / 1.5,
st.x * 0.3
);
float y = 0.5 + (perc - 0.5) * distance + xnoise / 2.0 * finalAmplitude;
float line_start = smoothstep(
y + (width / 2.0) + (u_line_blur * pixel(1.0, iResolution.xy) * blur),
y,
st.y
);
float line_end = smoothstep(
y,
y - (width / 2.0) - (u_line_blur * pixel(1.0, iResolution.xy) * blur),
st.y
);
return clamp(
(line_start - line_end) * (1.0 - smoothstep(0.0, 1.0, pow(perc, 0.3))),
0.0,
1.0
);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
float line_strength = 1.0;
for (int i = 0; i < u_line_count; i++) {
float p = float(i) / float(u_line_count);
line_strength *= (1.0 - lineFn(
uv,
u_line_width * pixel(1.0, iResolution.xy) * (1.0 - p),
p,
(PI * 1.0) * p,
uMouse,
iTime,
uAmplitude,
uDistance
));
}
float colorVal = 1.0 - line_strength;
fragColor = vec4(uColor * colorVal, colorVal);
}
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
`;
const Threads = ({ color = [1, 1, 1], amplitude = 1, distance = 0, enableMouseInteraction = false, ...rest }) => {
const containerRef = useRef(null);
const animationFrameId = useRef();
useEffect(() => {
if (!containerRef.current) return;
const container = containerRef.current;
const renderer = new Renderer({ alpha: true });
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
container.appendChild(gl.canvas);
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
iTime: { value: 0 },
iResolution: {
value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)
},
uColor: { value: new Color(...color) },
uAmplitude: { value: amplitude },
uDistance: { value: distance },
uMouse: { value: new Float32Array([0.5, 0.5]) }
}
});
const mesh = new Mesh(gl, { geometry, program });
function resize() {
const { clientWidth, clientHeight } = container;
renderer.setSize(clientWidth, clientHeight);
program.uniforms.iResolution.value.r = clientWidth;
program.uniforms.iResolution.value.g = clientHeight;
program.uniforms.iResolution.value.b = clientWidth / clientHeight;
}
window.addEventListener('resize', resize);
resize();
let currentMouse = [0.5, 0.5];
let targetMouse = [0.5, 0.5];
function handleMouseMove(e) {
const rect = container.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = 1.0 - (e.clientY - rect.top) / rect.height;
targetMouse = [x, y];
}
function handleMouseLeave() {
targetMouse = [0.5, 0.5];
}
if (enableMouseInteraction) {
container.addEventListener('mousemove', handleMouseMove);
container.addEventListener('mouseleave', handleMouseLeave);
}
function update(t) {
if (enableMouseInteraction) {
const smoothing = 0.05;
currentMouse[0] += smoothing * (targetMouse[0] - currentMouse[0]);
currentMouse[1] += smoothing * (targetMouse[1] - currentMouse[1]);
program.uniforms.uMouse.value[0] = currentMouse[0];
program.uniforms.uMouse.value[1] = currentMouse[1];
} else {
program.uniforms.uMouse.value[0] = 0.5;
program.uniforms.uMouse.value[1] = 0.5;
}
program.uniforms.iTime.value = t * 0.001;
renderer.render({ scene: mesh });
animationFrameId.current = requestAnimationFrame(update);
}
animationFrameId.current = requestAnimationFrame(update);
return () => {
if (animationFrameId.current) cancelAnimationFrame(animationFrameId.current);
window.removeEventListener('resize', resize);
if (enableMouseInteraction) {
container.removeEventListener('mousemove', handleMouseMove);
container.removeEventListener('mouseleave', handleMouseLeave);
}
if (container.contains(gl.canvas)) container.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, [color, amplitude, distance, enableMouseInteraction]);
return <div ref={containerRef} className="threads-container" {...rest} />;
};
export default Threads;
Component CSS
.threads-container {
position: relative;
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: hyperspeed code: ## Integrate the <Hyperspeed /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Hyperspeed
Variant: JavaScript + CSS
Dependencies: three postprocessing
Usage Example
import Hyperspeed from './Hyperspeed';
// the component will fill the height/width of its parent container, edit the CSS to change this
// the options below are the default values
// TIP: if you pass custom effectOptions, memoize the object (e.g. useMemo or a constant)
// to avoid unnecessary re-renders and WebGL scene recreations
<Hyperspeed
effectOptions={{"distortion":"turbulentDistortion","length":400,"roadWidth":10,"islandWidth":2,"lanesPerRoad":3,"fov":90,"fovSpeedUp":150,"speedUp":2,"carLightsFade":0.4,"totalSideLightSticks":20,"lightPairsPerRoadWay":40,"shoulderLinesWidthPercentage":0.05,"brokenLinesWidthPercentage":0.1,"brokenLinesLengthPercentage":0.5,"lightStickWidth":[0.12,0.5],"lightStickHeight":[1.3,1.7],"movingAwaySpeed":[60,80],"movingCloserSpeed":[-120,-160],"carLightsLength":[12,80],"carLightsRadius":[0.05,0.14],"carWidthPercentage":[0.3,0.5],"carShiftX":[-0.8,0.8],"carFloorSeparation":[0,5],"colors":{"roadColor":526344,"islandColor":657930,"background":0,"shoulderLines":1250072,"brokenLines":1250072,"leftCars":[14177983,6770850,12732332],"rightCars":[242627,941733,3294549],"sticks":242627}}}
onSpeedUp: () => { },
onSlowDown: () => { },
distortion: 'turbulentDistortion',
length: 400,
roadWidth: 10,
islandWidth: 2,
lanesPerRoad: 4,
fov: 90,
fovSpeedUp: 150,
speedUp: 2,
carLightsFade: 0.4,
totalSideLightSticks: 20,
lightPairsPerRoadWay: 40,
shoulderLinesWidthPercentage: 0.05,
brokenLinesWidthPercentage: 0.1,
brokenLinesLengthPercentage: 0.5,
lightStickWidth: [0.12, 0.5],
lightStickHeight: [1.3, 1.7],
movingAwaySpeed: [60, 80],
movingCloserSpeed: [-120, -160],
carLightsLength: [400 * 0.03, 400 * 0.2],
carLightsRadius: [0.05, 0.14],
carWidthPercentage: [0.3, 0.5],
carShiftX: [-0.8, 0.8],
carFloorSeparation: [0, 5],
colors: {
roadColor: 0x080808,
islandColor: 0x0a0a0a,
background: 0x000000,
shoulderLines: 0xFFFFFF,
brokenLines: 0xFFFFFF,
leftCars: [0xD856BF, 0x6750A2, 0xC247AC],
rightCars: [0x03B3C3, 0x0E5EA5, 0x324555],
sticks: 0x03B3C3,
}
}}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| effectOptions | object | See the "code" tab for default values and presets. | The highly customizable configuration object for the effect, controls things like colors, distortion, line properties, etc. |
Full Component Source
import { BloomEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset } from 'postprocessing';
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import './Hyperspeed.css';
const DEFAULT_EFFECT_OPTIONS = {
onSpeedUp: () => {},
onSlowDown: () => {},
distortion: 'turbulentDistortion',
length: 400,
roadWidth: 10,
islandWidth: 2,
lanesPerRoad: 4,
fov: 90,
fovSpeedUp: 150,
speedUp: 2,
carLightsFade: 0.4,
totalSideLightSticks: 20,
lightPairsPerRoadWay: 40,
shoulderLinesWidthPercentage: 0.05,
brokenLinesWidthPercentage: 0.1,
brokenLinesLengthPercentage: 0.5,
lightStickWidth: [0.12, 0.5],
lightStickHeight: [1.3, 1.7],
movingAwaySpeed: [60, 80],
movingCloserSpeed: [-120, -160],
carLightsLength: [400 * 0.03, 400 * 0.2],
carLightsRadius: [0.05, 0.14],
carWidthPercentage: [0.3, 0.5],
carShiftX: [-0.8, 0.8],
carFloorSeparation: [0, 5],
colors: {
roadColor: 0x080808,
islandColor: 0x0a0a0a,
background: 0x000000,
shoulderLines: 0xffffff,
brokenLines: 0xffffff,
leftCars: [0xd856bf, 0x6750a2, 0xc247ac],
rightCars: [0x03b3c3, 0x0e5ea5, 0x324555],
sticks: 0x03b3c3
}
};
const Hyperspeed = ({ effectOptions = DEFAULT_EFFECT_OPTIONS }) => {
const hyperspeed = useRef(null);
const appRef = useRef(null);
useEffect(() => {
if (appRef.current) {
appRef.current.dispose();
appRef.current = null;
const container = hyperspeed.current;
if (container) {
while (container.firstChild) {
container.removeChild(container.firstChild);
}
}
}
const mountainUniforms = {
uFreq: { value: new THREE.Vector3(3, 6, 10) },
uAmp: { value: new THREE.Vector3(30, 30, 20) }
};
const xyUniforms = {
uFreq: { value: new THREE.Vector2(5, 2) },
uAmp: { value: new THREE.Vector2(25, 15) }
};
const LongRaceUniforms = {
uFreq: { value: new THREE.Vector2(2, 3) },
uAmp: { value: new THREE.Vector2(35, 10) }
};
const turbulentUniforms = {
uFreq: { value: new THREE.Vector4(4, 8, 8, 1) },
uAmp: { value: new THREE.Vector4(25, 5, 10, 10) }
};
const deepUniforms = {
uFreq: { value: new THREE.Vector2(4, 8) },
uAmp: { value: new THREE.Vector2(10, 20) },
uPowY: { value: new THREE.Vector2(20, 2) }
};
let nsin = val => Math.sin(val) * 0.5 + 0.5;
const distortions = {
mountainDistortion: {
uniforms: mountainUniforms,
getDistortion: `
uniform vec3 uAmp;
uniform vec3 uFreq;
#define PI 3.14159265358979
float nsin(float val){
return sin(val) * 0.5 + 0.5;
}
vec3 getDistortion(float progress){
float movementProgressFix = 0.02;
return vec3(
cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,
nsin(progress * PI * uFreq.y + uTime) * uAmp.y - nsin(movementProgressFix * PI * uFreq.y + uTime) * uAmp.y,
nsin(progress * PI * uFreq.z + uTime) * uAmp.z - nsin(movementProgressFix * PI * uFreq.z + uTime) * uAmp.z
);
}
`,
getJS: (progress, time) => {
let movementProgressFix = 0.02;
let uFreq = mountainUniforms.uFreq.value;
let uAmp = mountainUniforms.uAmp.value;
let distortion = new THREE.Vector3(
Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -
Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,
nsin(progress * Math.PI * uFreq.y + time) * uAmp.y -
nsin(movementProgressFix * Math.PI * uFreq.y + time) * uAmp.y,
nsin(progress * Math.PI * uFreq.z + time) * uAmp.z -
nsin(movementProgressFix * Math.PI * uFreq.z + time) * uAmp.z
);
let lookAtAmp = new THREE.Vector3(2, 2, 2);
let lookAtOffset = new THREE.Vector3(0, 0, -5);
return distortion.multiply(lookAtAmp).add(lookAtOffset);
}
},
xyDistortion: {
uniforms: xyUniforms,
getDistortion: `
uniform vec2 uFreq;
uniform vec2 uAmp;
#define PI 3.14159265358979
vec3 getDistortion(float progress){
float movementProgressFix = 0.02;
return vec3(
cos(progress * PI * uFreq.x + uTime) * uAmp.x - cos(movementProgressFix * PI * uFreq.x + uTime) * uAmp.x,
sin(progress * PI * uFreq.y + PI/2. + uTime) * uAmp.y - sin(movementProgressFix * PI * uFreq.y + PI/2. + uTime) * uAmp.y,
0.
);
}
`,
getJS: (progress, time) => {
let movementProgressFix = 0.02;
let uFreq = xyUniforms.uFreq.value;
let uAmp = xyUniforms.uAmp.value;
let distortion = new THREE.Vector3(
Math.cos(progress * Math.PI * uFreq.x + time) * uAmp.x -
Math.cos(movementProgressFix * Math.PI * uFreq.x + time) * uAmp.x,
Math.sin(progress * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y -
Math.sin(movementProgressFix * Math.PI * uFreq.y + time + Math.PI / 2) * uAmp.y,
0
);
let lookAtAmp = new THREE.Vector3(2, 0.4, 1);
let lookAtOffset = new THREE.Vector3(0, 0, -3);
return distortion.multiply(lookAtAmp).add(lookAtOffset);
}
},
LongRaceDistortion: {
uniforms: LongRaceUniforms,
getDistortion: `
uniform vec2 uFreq;
uniform vec2 uAmp;
#define PI 3.14159265358979
vec3 getDistortion(float progress){
float camProgress = 0.0125;
return vec3(
sin(progress * PI * uFreq.x + uTime) * uAmp.x - sin(camProgress * PI * uFreq.x + uTime) * uAmp.x,
sin(progress * PI * uFreq.y + uTime) * uAmp.y - sin(camProgress * PI * uFreq.y + uTime) * uAmp.y,
0.
);
}
`,
getJS: (progress, time) => {
let camProgress = 0.0125;
let uFreq = LongRaceUniforms.uFreq.value;
let uAmp = LongRaceUniforms.uAmp.value;
let distortion = new THREE.Vector3(
Math.sin(progress * Math.PI * uFreq.x + time) * uAmp.x -
Math.sin(camProgress * Math.PI * uFreq.x + time) * uAmp.x,
Math.sin(progress * Math.PI * uFreq.y + time) * uAmp.y -
Math.sin(camProgress * Math.PI * uFreq.y + time) * uAmp.y,
0
);
let lookAtAmp = new THREE.Vector3(1, 1, 0);
let lookAtOffset = new THREE.Vector3(0, 0, -5);
return distortion.multiply(lookAtAmp).add(lookAtOffset);
}
},
turbulentDistortion: {
uniforms: turbulentUniforms,
getDistortion: `
uniform vec4 uFreq;
uniform vec4 uAmp;
float nsin(float val){
return sin(val) * 0.5 + 0.5;
}
#define PI 3.14159265358979
float getDistortionX(float progress){
return (
cos(PI * progress * uFreq.r + uTime) * uAmp.r +
pow(cos(PI * progress * uFreq.g + uTime * (uFreq.g / uFreq.r)), 2. ) * uAmp.g
);
}
float getDistortionY(float progress){
return (
-nsin(PI * progress * uFreq.b + uTime) * uAmp.b +
-pow(nsin(PI * progress * uFreq.a + uTime / (uFreq.b / uFreq.a)), 5.) * uAmp.a
);
}
vec3 getDistortion(float progress){
return vec3(
getDistortionX(progress) - getDistortionX(0.0125),
getDistortionY(progress) - getDistortionY(0.0125),
0.
);
}
`,
getJS: (progress, time) => {
const uFreq = turbulentUniforms.uFreq.value;
const uAmp = turbulentUniforms.uAmp.value;
const getX = p =>
Math.cos(Math.PI * p * uFreq.x + time) * uAmp.x +
Math.pow(Math.cos(Math.PI * p * uFreq.y + time * (uFreq.y / uFreq.x)), 2) * uAmp.y;
const getY = p =>
-nsin(Math.PI * p * uFreq.z + time) * uAmp.z -
Math.pow(nsin(Math.PI * p * uFreq.w + time / (uFreq.z / uFreq.w)), 5) * uAmp.w;
let distortion = new THREE.Vector3(
getX(progress) - getX(progress + 0.007),
getY(progress) - getY(progress + 0.007),
0
);
let lookAtAmp = new THREE.Vector3(-2, -5, 0);
let lookAtOffset = new THREE.Vector3(0, 0, -10);
return distortion.multiply(lookAtAmp).add(lookAtOffset);
}
},
turbulentDistortionStill: {
uniforms: turbulentUniforms,
getDistortion: `
uniform vec4 uFreq;
uniform vec4 uAmp;
float nsin(float val){
return sin(val) * 0.5 + 0.5;
}
#define PI 3.14159265358979
float getDistortionX(float progress){
return (
cos(PI * progress * uFreq.r) * uAmp.r +
pow(cos(PI * progress * uFreq.g * (uFreq.g / uFreq.r)), 2. ) * uAmp.g
);
}
float getDistortionY(float progress){
return (
-nsin(PI * progress * uFreq.b) * uAmp.b +
-pow(nsin(PI * progress * uFreq.a / (uFreq.b / uFreq.a)), 5.) * uAmp.a
);
}
vec3 getDistortion(float progress){
return vec3(
getDistortionX(progress) - getDistortionX(0.02),
getDistortionY(progress) - getDistortionY(0.02),
0.
);
}
`
},
deepDistortionStill: {
uniforms: deepUniforms,
getDistortion: `
uniform vec4 uFreq;
uniform vec4 uAmp;
uniform vec2 uPowY;
float nsin(float val){
return sin(val) * 0.5 + 0.5;
}
#define PI 3.14159265358979
float getDistortionX(float progress){
return (
sin(progress * PI * uFreq.x) * uAmp.x * 2.
);
}
float getDistortionY(float progress){
return (
pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y) * uAmp.y
);
}
vec3 getDistortion(float progress){
return vec3(
getDistortionX(progress) - getDistortionX(0.02),
getDistortionY(progress) - getDistortionY(0.05),
0.
);
}
`
},
deepDistortion: {
uniforms: deepUniforms,
getDistortion: `
uniform vec4 uFreq;
uniform vec4 uAmp;
uniform vec2 uPowY;
float nsin(float val){
return sin(val) * 0.5 + 0.5;
}
#define PI 3.14159265358979
float getDistortionX(float progress){
return (
sin(progress * PI * uFreq.x + uTime) * uAmp.x
);
}
float getDistortionY(float progress){
return (
pow(abs(progress * uPowY.x), uPowY.y) + sin(progress * PI * uFreq.y + uTime) * uAmp.y
);
}
vec3 getDistortion(float progress){
return vec3(
getDistortionX(progress) - getDistortionX(0.02),
getDistortionY(progress) - getDistortionY(0.02),
0.
);
}
`,
getJS: (progress, time) => {
const uFreq = deepUniforms.uFreq.value;
const uAmp = deepUniforms.uAmp.value;
const uPowY = deepUniforms.uPowY.value;
const getX = p => Math.sin(p * Math.PI * uFreq.x + time) * uAmp.x;
const getY = p => Math.pow(p * uPowY.x, uPowY.y) + Math.sin(p * Math.PI * uFreq.y + time) * uAmp.y;
let distortion = new THREE.Vector3(
getX(progress) - getX(progress + 0.01),
getY(progress) - getY(progress + 0.01),
0
);
let lookAtAmp = new THREE.Vector3(-2, -4, 0);
let lookAtOffset = new THREE.Vector3(0, 0, -10);
return distortion.multiply(lookAtAmp).add(lookAtOffset);
}
}
};
class App {
constructor(container, options = {}) {
this.options = options;
if (this.options.distortion == null) {
this.options.distortion = {
uniforms: distortion_uniforms,
getDistortion: distortion_vertex
};
}
this.container = container;
this.hasValidSize = false;
const initW = Math.max(1, container.offsetWidth);
const initH = Math.max(1, container.offsetHeight);
this.renderer = new THREE.WebGLRenderer({
antialias: false,
alpha: true
});
this.renderer.setSize(initW, initH, false);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.composer = new EffectComposer(this.renderer);
container.append(this.renderer.domElement);
this.camera = new THREE.PerspectiveCamera(options.fov, initW / initH, 0.1, 10000);
this.camera.position.z = -5;
this.camera.position.y = 8;
this.camera.position.x = 0;
this.scene = new THREE.Scene();
this.scene.background = null;
let fog = new THREE.Fog(options.colors.background, options.length * 0.2, options.length * 500);
this.scene.fog = fog;
this.fogUniforms = {
fogColor: { value: fog.color },
fogNear: { value: fog.near },
fogFar: { value: fog.far }
};
this.clock = new THREE.Clock();
this.assets = {};
this.disposed = false;
this.road = new Road(this, options);
this.leftCarLights = new CarLights(
this,
options,
options.colors.leftCars,
options.movingAwaySpeed,
new THREE.Vector2(0, 1 - options.carLightsFade)
);
this.rightCarLights = new CarLights(
this,
options,
options.colors.rightCars,
options.movingCloserSpeed,
new THREE.Vector2(1, 0 + options.carLightsFade)
);
this.leftSticks = new LightsSticks(this, options);
this.fovTarget = options.fov;
this.speedUpTarget = 0;
this.speedUp = 0;
this.timeOffset = 0;
this.tick = this.tick.bind(this);
this.init = this.init.bind(this);
this.setSize = this.setSize.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onTouchStart = this.onTouchStart.bind(this);
this.onTouchEnd = this.onTouchEnd.bind(this);
this.onContextMenu = this.onContextMenu.bind(this);
this.onWindowResize = this.onWindowResize.bind(this);
window.addEventListener('resize', this.onWindowResize);
if (container.offsetWidth > 0 && container.offsetHeight > 0) {
this.hasValidSize = true;
}
}
onWindowResize() {
const width = this.container.offsetWidth;
const height = this.container.offsetHeight;
if (width <= 0 || height <= 0) {
this.hasValidSize = false;
return;
}
this.renderer.setSize(width, height);
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
this.composer.setSize(width, height);
this.hasValidSize = true;
}
initPasses() {
this.renderPass = new RenderPass(this.scene, this.camera);
this.bloomPass = new EffectPass(
this.camera,
new BloomEffect({
luminanceThreshold: 0.2,
luminanceSmoothing: 0,
resolutionScale: 1
})
);
const smaaPass = new EffectPass(
this.camera,
new SMAAEffect({
preset: SMAAPreset.MEDIUM,
searchImage: SMAAEffect.searchImageDataURL,
areaImage: SMAAEffect.areaImageDataURL
})
);
this.renderPass.renderToScreen = false;
this.bloomPass.renderToScreen = false;
smaaPass.renderToScreen = true;
this.composer.addPass(this.renderPass);
this.composer.addPass(this.bloomPass);
this.composer.addPass(smaaPass);
}
loadAssets() {
const assets = this.assets;
return new Promise(resolve => {
const manager = new THREE.LoadingManager(resolve);
const searchImage = new Image();
const areaImage = new Image();
assets.smaa = {};
searchImage.addEventListener('load', function () {
assets.smaa.search = this;
manager.itemEnd('smaa-search');
});
areaImage.addEventListener('load', function () {
assets.smaa.area = this;
manager.itemEnd('smaa-area');
});
manager.itemStart('smaa-search');
manager.itemStart('smaa-area');
searchImage.src = SMAAEffect.searchImageDataURL;
areaImage.src = SMAAEffect.areaImageDataURL;
});
}
init() {
this.initPasses();
const options = this.options;
this.road.init();
this.leftCarLights.init();
this.leftCarLights.mesh.position.setX(-options.roadWidth / 2 - options.islandWidth / 2);
this.rightCarLights.init();
this.rightCarLights.mesh.position.setX(options.roadWidth / 2 + options.islandWidth / 2);
this.leftSticks.init();
this.leftSticks.mesh.position.setX(-(options.roadWidth + options.islandWidth / 2));
this.container.addEventListener('mousedown', this.onMouseDown);
this.container.addEventListener('mouseup', this.onMouseUp);
this.container.addEventListener('mouseout', this.onMouseUp);
this.container.addEventListener('touchstart', this.onTouchStart, { passive: true });
this.container.addEventListener('touchend', this.onTouchEnd, { passive: true });
this.container.addEventListener('touchcancel', this.onTouchEnd, { passive: true });
this.container.addEventListener('contextmenu', this.onContextMenu);
this.tick();
}
onMouseDown(ev) {
if (this.options.onSpeedUp) this.options.onSpeedUp(ev);
this.fovTarget = this.options.fovSpeedUp;
this.speedUpTarget = this.options.speedUp;
}
onMouseUp(ev) {
if (this.options.onSlowDown) this.options.onSlowDown(ev);
this.fovTarget = this.options.fov;
this.speedUpTarget = 0;
}
onTouchStart(ev) {
if (this.options.onSpeedUp) this.options.onSpeedUp(ev);
this.fovTarget = this.options.fovSpeedUp;
this.speedUpTarget = this.options.speedUp;
}
onTouchEnd(ev) {
if (this.options.onSlowDown) this.options.onSlowDown(ev);
this.fovTarget = this.options.fov;
this.speedUpTarget = 0;
}
onContextMenu(ev) {
ev.preventDefault();
}
update(delta) {
let lerpPercentage = Math.exp(-(-60 * Math.log2(1 - 0.1)) * delta);
this.speedUp += lerp(this.speedUp, this.speedUpTarget, lerpPercentage, 0.00001);
this.timeOffset += this.speedUp * delta;
let time = this.clock.elapsedTime + this.timeOffset;
this.rightCarLights.update(time);
this.leftCarLights.update(time);
this.leftSticks.update(time);
this.road.update(time);
let updateCamera = false;
let fovChange = lerp(this.camera.fov, this.fovTarget, lerpPercentage);
if (fovChange !== 0) {
this.camera.fov += fovChange * delta * 6;
updateCamera = true;
}
if (this.options.distortion.getJS) {
const distortion = this.options.distortion.getJS(0.025, time);
this.camera.lookAt(
new THREE.Vector3(
this.camera.position.x + distortion.x,
this.camera.position.y + distortion.y,
this.camera.position.z + distortion.z
)
);
updateCamera = true;
}
if (updateCamera) {
this.camera.updateProjectionMatrix();
}
}
render(delta) {
this.composer.render(delta);
}
dispose() {
this.disposed = true;
if (this.scene) {
this.scene.traverse(object => {
const obj = object;
if (!obj.isMesh) return;
if (obj.geometry) obj.geometry.dispose();
if (obj.material) {
if (Array.isArray(obj.material)) {
obj.material.forEach(material => material.dispose());
} else {
obj.material.dispose();
}
}
});
this.scene.clear();
}
if (this.renderer) {
this.renderer.dispose();
this.renderer.forceContextLoss();
if (this.renderer.domElement && this.renderer.domElement.parentNode) {
this.renderer.domElement.parentNode.removeChild(this.renderer.domElement);
}
}
if (this.composer) {
this.composer.dispose();
}
window.removeEventListener('resize', this.onWindowResize);
if (this.container) {
this.container.removeEventListener('mousedown', this.onMouseDown);
this.container.removeEventListener('mouseup', this.onMouseUp);
this.container.removeEventListener('mouseout', this.onMouseUp);
this.container.removeEventListener('touchstart', this.onTouchStart);
this.container.removeEventListener('touchend', this.onTouchEnd);
this.container.removeEventListener('touchcancel', this.onTouchEnd);
this.container.removeEventListener('contextmenu', this.onContextMenu);
}
}
setSize(width, height, updateStyles) {
if (width <= 0 || height <= 0) {
this.hasValidSize = false;
return;
}
this.composer.setSize(width, height, updateStyles);
this.hasValidSize = true;
}
tick() {
if (this.disposed) return;
if (!this.hasValidSize) {
const w = this.container.offsetWidth;
const h = this.container.offsetHeight;
if (w > 0 && h > 0) {
this.renderer.setSize(w, h, false);
this.camera.aspect = w / h;
this.camera.updateProjectionMatrix();
this.composer.setSize(w, h);
this.hasValidSize = true;
} else {
requestAnimationFrame(this.tick);
return;
}
}
if (resizeRendererToDisplaySize(this.renderer, this.setSize)) {
const canvas = this.renderer.domElement;
if (this.hasValidSize) {
this.camera.aspect = canvas.clientWidth / canvas.clientHeight;
this.camera.updateProjectionMatrix();
}
}
if (this.hasValidSize) {
const delta = this.clock.getDelta();
this.render(delta);
this.update(delta);
}
requestAnimationFrame(this.tick);
}
}
const distortion_uniforms = {
uDistortionX: { value: new THREE.Vector2(80, 3) },
uDistortionY: { value: new THREE.Vector2(-40, 2.5) }
};
const distortion_vertex = `
#define PI 3.14159265358979
uniform vec2 uDistortionX;
uniform vec2 uDistortionY;
float nsin(float val){
return sin(val) * 0.5 + 0.5;
}
vec3 getDistortion(float progress){
progress = clamp(progress, 0., 1.);
float xAmp = uDistortionX.r;
float xFreq = uDistortionX.g;
float yAmp = uDistortionY.r;
float yFreq = uDistortionY.g;
return vec3(
xAmp * nsin(progress * PI * xFreq - PI / 2.),
yAmp * nsin(progress * PI * yFreq - PI / 2.),
0.
);
}
`;
const random = base => {
if (Array.isArray(base)) return Math.random() * (base[1] - base[0]) + base[0];
return Math.random() * base;
};
const pickRandom = arr => {
if (Array.isArray(arr)) return arr[Math.floor(Math.random() * arr.length)];
return arr;
};
function lerp(current, target, speed = 0.1, limit = 0.001) {
let change = (target - current) * speed;
if (Math.abs(change) < limit) {
change = target - current;
}
return change;
}
class CarLights {
constructor(webgl, options, colors, speed, fade) {
this.webgl = webgl;
this.options = options;
this.colors = colors;
this.speed = speed;
this.fade = fade;
}
init() {
const options = this.options;
let curve = new THREE.LineCurve3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 0, -1));
let geometry = new THREE.TubeGeometry(curve, 40, 1, 8, false);
let instanced = new THREE.InstancedBufferGeometry().copy(geometry);
instanced.instanceCount = options.lightPairsPerRoadWay * 2;
let laneWidth = options.roadWidth / options.lanesPerRoad;
let aOffset = [];
let aMetrics = [];
let aColor = [];
let colors = this.colors;
if (Array.isArray(colors)) {
colors = colors.map(c => new THREE.Color(c));
} else {
colors = new THREE.Color(colors);
}
for (let i = 0; i < options.lightPairsPerRoadWay; i++) {
let radius = random(options.carLightsRadius);
let length = random(options.carLightsLength);
let speed = random(this.speed);
let carLane = i % options.lanesPerRoad;
let laneX = carLane * laneWidth - options.roadWidth / 2 + laneWidth / 2;
let carWidth = random(options.carWidthPercentage) * laneWidth;
let carShiftX = random(options.carShiftX) * laneWidth;
laneX += carShiftX;
let offsetY = random(options.carFloorSeparation) + radius * 1.3;
let offsetZ = -random(options.length);
aOffset.push(laneX - carWidth / 2);
aOffset.push(offsetY);
aOffset.push(offsetZ);
aOffset.push(laneX + carWidth / 2);
aOffset.push(offsetY);
aOffset.push(offsetZ);
aMetrics.push(radius);
aMetrics.push(length);
aMetrics.push(speed);
aMetrics.push(radius);
aMetrics.push(length);
aMetrics.push(speed);
let color = pickRandom(colors);
aColor.push(color.r);
aColor.push(color.g);
aColor.push(color.b);
aColor.push(color.r);
aColor.push(color.g);
aColor.push(color.b);
}
instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 3, false));
instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 3, false));
instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));
let material = new THREE.ShaderMaterial({
fragmentShader: carLightsFragment,
vertexShader: carLightsVertex,
transparent: true,
uniforms: Object.assign(
{
uTime: { value: 0 },
uTravelLength: { value: options.length },
uFade: { value: this.fade }
},
this.webgl.fogUniforms,
options.distortion.uniforms
)
});
material.onBeforeCompile = shader => {
shader.vertexShader = shader.vertexShader.replace(
'#include <getDistortion_vertex>',
options.distortion.getDistortion
);
};
let mesh = new THREE.Mesh(instanced, material);
mesh.frustumCulled = false;
this.webgl.scene.add(mesh);
this.mesh = mesh;
}
update(time) {
this.mesh.material.uniforms.uTime.value = time;
}
}
const carLightsFragment = `
#define USE_FOG;
${THREE.ShaderChunk['fog_pars_fragment']}
varying vec3 vColor;
varying vec2 vUv;
uniform vec2 uFade;
void main() {
vec3 color = vec3(vColor);
float alpha = smoothstep(uFade.x, uFade.y, vUv.x);
gl_FragColor = vec4(color, alpha);
if (gl_FragColor.a < 0.0001) discard;
${THREE.ShaderChunk['fog_fragment']}
}
`;
const carLightsVertex = `
#define USE_FOG;
${THREE.ShaderChunk['fog_pars_vertex']}
attribute vec3 aOffset;
attribute vec3 aMetrics;
attribute vec3 aColor;
uniform float uTravelLength;
uniform float uTime;
varying vec2 vUv;
varying vec3 vColor;
#include <getDistortion_vertex>
void main() {
vec3 transformed = position.xyz;
float radius = aMetrics.r;
float myLength = aMetrics.g;
float speed = aMetrics.b;
transformed.xy *= radius;
transformed.z *= myLength;
transformed.z += myLength - mod(uTime * speed + aOffset.z, uTravelLength);
transformed.xy += aOffset.xy;
float progress = abs(transformed.z / uTravelLength);
transformed.xyz += getDistortion(progress);
vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);
gl_Position = projectionMatrix * mvPosition;
vUv = uv;
vColor = aColor;
${THREE.ShaderChunk['fog_vertex']}
}
`;
class LightsSticks {
constructor(webgl, options) {
this.webgl = webgl;
this.options = options;
}
init() {
const options = this.options;
const geometry = new THREE.PlaneGeometry(1, 1);
let instanced = new THREE.InstancedBufferGeometry().copy(geometry);
let totalSticks = options.totalSideLightSticks;
instanced.instanceCount = totalSticks;
let stickoffset = options.length / (totalSticks - 1);
const aOffset = [];
const aColor = [];
const aMetrics = [];
let colors = options.colors.sticks;
if (Array.isArray(colors)) {
colors = colors.map(c => new THREE.Color(c));
} else {
colors = new THREE.Color(colors);
}
for (let i = 0; i < totalSticks; i++) {
let width = random(options.lightStickWidth);
let height = random(options.lightStickHeight);
aOffset.push((i - 1) * stickoffset * 2 + stickoffset * Math.random());
let color = pickRandom(colors);
aColor.push(color.r);
aColor.push(color.g);
aColor.push(color.b);
aMetrics.push(width);
aMetrics.push(height);
}
instanced.setAttribute('aOffset', new THREE.InstancedBufferAttribute(new Float32Array(aOffset), 1, false));
instanced.setAttribute('aColor', new THREE.InstancedBufferAttribute(new Float32Array(aColor), 3, false));
instanced.setAttribute('aMetrics', new THREE.InstancedBufferAttribute(new Float32Array(aMetrics), 2, false));
const material = new THREE.ShaderMaterial({
fragmentShader: sideSticksFragment,
vertexShader: sideSticksVertex,
side: THREE.DoubleSide,
uniforms: Object.assign(
{
uTravelLength: { value: options.length },
uTime: { value: 0 }
},
this.webgl.fogUniforms,
options.distortion.uniforms
)
});
material.onBeforeCompile = shader => {
shader.vertexShader = shader.vertexShader.replace(
'#include <getDistortion_vertex>',
options.distortion.getDistortion
);
};
const mesh = new THREE.Mesh(instanced, material);
mesh.frustumCulled = false;
this.webgl.scene.add(mesh);
this.mesh = mesh;
}
update(time) {
this.mesh.material.uniforms.uTime.value = time;
}
}
const sideSticksVertex = `
#define USE_FOG;
${THREE.ShaderChunk['fog_pars_vertex']}
attribute float aOffset;
attribute vec3 aColor;
attribute vec2 aMetrics;
uniform float uTravelLength;
uniform float uTime;
varying vec3 vColor;
mat4 rotationY( in float angle ) {
return mat4( cos(angle), 0, sin(angle), 0,
0, 1.0, 0, 0,
-sin(angle), 0, cos(angle), 0,
0, 0, 0, 1);
}
#include <getDistortion_vertex>
void main(){
vec3 transformed = position.xyz;
float width = aMetrics.x;
float height = aMetrics.y;
transformed.xy *= vec2(width, height);
float time = mod(uTime * 60. * 2. + aOffset, uTravelLength);
transformed = (rotationY(3.14/2.) * vec4(transformed,1.)).xyz;
transformed.z += - uTravelLength + time;
float progress = abs(transformed.z / uTravelLength);
transformed.xyz += getDistortion(progress);
transformed.y += height / 2.;
transformed.x += -width / 2.;
vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);
gl_Position = projectionMatrix * mvPosition;
vColor = aColor;
${THREE.ShaderChunk['fog_vertex']}
}
`;
const sideSticksFragment = `
#define USE_FOG;
${THREE.ShaderChunk['fog_pars_fragment']}
varying vec3 vColor;
void main(){
vec3 color = vec3(vColor);
gl_FragColor = vec4(color,1.);
${THREE.ShaderChunk['fog_fragment']}
}
`;
class Road {
constructor(webgl, options) {
this.webgl = webgl;
this.options = options;
this.uTime = { value: 0 };
}
createPlane(side, width, isRoad) {
const options = this.options;
let segments = 100;
const geometry = new THREE.PlaneGeometry(
isRoad ? options.roadWidth : options.islandWidth,
options.length,
20,
segments
);
let uniforms = {
uTravelLength: { value: options.length },
uColor: { value: new THREE.Color(isRoad ? options.colors.roadColor : options.colors.islandColor) },
uTime: this.uTime
};
if (isRoad) {
uniforms = Object.assign(uniforms, {
uLanes: { value: options.lanesPerRoad },
uBrokenLinesColor: { value: new THREE.Color(options.colors.brokenLines) },
uShoulderLinesColor: { value: new THREE.Color(options.colors.shoulderLines) },
uShoulderLinesWidthPercentage: { value: options.shoulderLinesWidthPercentage },
uBrokenLinesLengthPercentage: { value: options.brokenLinesLengthPercentage },
uBrokenLinesWidthPercentage: { value: options.brokenLinesWidthPercentage }
});
}
const material = new THREE.ShaderMaterial({
fragmentShader: isRoad ? roadFragment : islandFragment,
vertexShader: roadVertex,
side: THREE.DoubleSide,
uniforms: Object.assign(uniforms, this.webgl.fogUniforms, options.distortion.uniforms)
});
material.onBeforeCompile = shader => {
shader.vertexShader = shader.vertexShader.replace(
'#include <getDistortion_vertex>',
options.distortion.getDistortion
);
};
const mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = -Math.PI / 2;
mesh.position.z = -options.length / 2;
mesh.position.x += (this.options.islandWidth / 2 + options.roadWidth / 2) * side;
this.webgl.scene.add(mesh);
return mesh;
}
init() {
this.leftRoadWay = this.createPlane(-1, this.options.roadWidth, true);
this.rightRoadWay = this.createPlane(1, this.options.roadWidth, true);
this.island = this.createPlane(0, this.options.islandWidth, false);
}
update(time) {
this.uTime.value = time;
}
}
const roadBaseFragment = `
#define USE_FOG;
varying vec2 vUv;
uniform vec3 uColor;
uniform float uTime;
#include <roadMarkings_vars>
${THREE.ShaderChunk['fog_pars_fragment']}
void main() {
vec2 uv = vUv;
vec3 color = vec3(uColor);
#include <roadMarkings_fragment>
gl_FragColor = vec4(color, 1.);
${THREE.ShaderChunk['fog_fragment']}
}
`;
const islandFragment = roadBaseFragment
.replace('#include <roadMarkings_fragment>', '')
.replace('#include <roadMarkings_vars>', '');
const roadMarkings_vars = `
uniform float uLanes;
uniform vec3 uBrokenLinesColor;
uniform vec3 uShoulderLinesColor;
uniform float uShoulderLinesWidthPercentage;
uniform float uBrokenLinesWidthPercentage;
uniform float uBrokenLinesLengthPercentage;
highp float random(vec2 co) {
highp float a = 12.9898;
highp float b = 78.233;
highp float c = 43758.5453;
highp float dt = dot(co.xy, vec2(a, b));
highp float sn = mod(dt, 3.14);
return fract(sin(sn) * c);
}
`;
const roadMarkings_fragment = `
uv.y = mod(uv.y + uTime * 0.05, 1.);
float laneWidth = 1.0 / uLanes;
float brokenLineWidth = laneWidth * uBrokenLinesWidthPercentage;
float laneEmptySpace = 1. - uBrokenLinesLengthPercentage;
float brokenLines = step(1.0 - brokenLineWidth, fract(uv.x * 2.0)) * step(laneEmptySpace, fract(uv.y * 10.0));
float sideLines = step(1.0 - brokenLineWidth, fract((uv.x - laneWidth * (uLanes - 1.0)) * 2.0)) + step(brokenLineWidth, uv.x);
brokenLines = mix(brokenLines, sideLines, uv.x);
`;
const roadFragment = roadBaseFragment
.replace('#include <roadMarkings_fragment>', roadMarkings_fragment)
.replace('#include <roadMarkings_vars>', roadMarkings_vars);
const roadVertex = `
#define USE_FOG;
uniform float uTime;
${THREE.ShaderChunk['fog_pars_vertex']}
uniform float uTravelLength;
varying vec2 vUv;
#include <getDistortion_vertex>
void main() {
vec3 transformed = position.xyz;
vec3 distortion = getDistortion((transformed.y + uTravelLength / 2.) / uTravelLength);
transformed.x += distortion.x;
transformed.z += distortion.y;
transformed.y += -1. * distortion.z;
vec4 mvPosition = modelViewMatrix * vec4(transformed, 1.);
gl_Position = projectionMatrix * mvPosition;
vUv = uv;
${THREE.ShaderChunk['fog_vertex']}
}
`;
function resizeRendererToDisplaySize(renderer, setSize) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (width <= 0 || height <= 0) return false;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
setSize(width, height, false);
}
return needResize;
}
const container = hyperspeed.current;
if (!container) return;
const options = {
...DEFAULT_EFFECT_OPTIONS,
...effectOptions,
colors: { ...DEFAULT_EFFECT_OPTIONS.colors, ...effectOptions.colors }
};
options.distortion = distortions[options.distortion];
const myApp = new App(container, options);
appRef.current = myApp;
myApp.loadAssets().then(myApp.init);
return () => {
if (appRef.current) {
appRef.current.dispose();
appRef.current = null;
}
};
}, [effectOptions]);
return <div id="lights" ref={hyperspeed}></div>;
};
export default Hyperspeed;
Component CSS
#lights {
width: 100%;
height: 100%;
overflow: hidden;
position: absolute;
}
canvas {
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: iridescence code: ## Integrate the <Iridescence /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Iridescence
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Iridescence from './Iridescence';
<Iridescence
color={[1, 1, 1]}
mouseReact={false}
amplitude={0.1}
speed={1.0}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| color | Array<number> | [0.3, 0.2, 0.5] | Base color as an array of RGB values (each between 0 and 1). |
| speed | number | 1.0 | Speed multiplier for the animation. |
| amplitude | number | 0.1 | Amplitude for the mouse-driven effect. |
| mouseReact | boolean | false | Enable or disable mouse interaction with the shader. |
Full Component Source
import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';
import { useEffect, useRef } from 'react';
import './Iridescence.css';
const vertexShader = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
const fragmentShader = `
precision highp float;
uniform float uTime;
uniform vec3 uColor;
uniform vec3 uResolution;
uniform vec2 uMouse;
uniform float uAmplitude;
uniform float uSpeed;
varying vec2 vUv;
void main() {
float mr = min(uResolution.x, uResolution.y);
vec2 uv = (vUv.xy * 2.0 - 1.0) * uResolution.xy / mr;
uv += (uMouse - vec2(0.5)) * uAmplitude;
float d = -uTime * 0.5 * uSpeed;
float a = 0.0;
for (float i = 0.0; i < 8.0; ++i) {
a += cos(i - d - a * uv.x);
d += sin(uv.y * i + a);
}
d += uTime * 0.5 * uSpeed;
vec3 col = vec3(cos(uv * vec2(d, a)) * 0.6 + 0.4, cos(a + d) * 0.5 + 0.5);
col = cos(col * cos(vec3(d, a, 2.5)) * 0.5 + 0.5) * uColor;
gl_FragColor = vec4(col, 1.0);
}
`;
export default function Iridescence({ color = [1, 1, 1], speed = 1.0, amplitude = 0.1, mouseReact = true, ...rest }) {
const ctnDom = useRef(null);
const mousePos = useRef({ x: 0.5, y: 0.5 });
useEffect(() => {
if (!ctnDom.current) return;
const ctn = ctnDom.current;
const renderer = new Renderer();
const gl = renderer.gl;
gl.clearColor(1, 1, 1, 1);
let program;
function resize() {
const scale = 1;
renderer.setSize(ctn.offsetWidth * scale, ctn.offsetHeight * scale);
if (program) {
program.uniforms.uResolution.value = new Color(
gl.canvas.width,
gl.canvas.height,
gl.canvas.width / gl.canvas.height
);
}
}
window.addEventListener('resize', resize, false);
resize();
const geometry = new Triangle(gl);
program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
uTime: { value: 0 },
uColor: { value: new Color(...color) },
uResolution: {
value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)
},
uMouse: { value: new Float32Array([mousePos.current.x, mousePos.current.y]) },
uAmplitude: { value: amplitude },
uSpeed: { value: speed }
}
});
const mesh = new Mesh(gl, { geometry, program });
let animateId;
function update(t) {
animateId = requestAnimationFrame(update);
program.uniforms.uTime.value = t * 0.001;
renderer.render({ scene: mesh });
}
animateId = requestAnimationFrame(update);
ctn.appendChild(gl.canvas);
function handleMouseMove(e) {
const rect = ctn.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = 1.0 - (e.clientY - rect.top) / rect.height;
mousePos.current = { x, y };
program.uniforms.uMouse.value[0] = x;
program.uniforms.uMouse.value[1] = y;
}
if (mouseReact) {
ctn.addEventListener('mousemove', handleMouseMove);
}
return () => {
cancelAnimationFrame(animateId);
window.removeEventListener('resize', resize);
if (mouseReact) {
ctn.removeEventListener('mousemove', handleMouseMove);
}
ctn.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, [color, speed, amplitude, mouseReact]);
return <div ref={ctnDom} className="iridescence-container" {...rest} />;
}
Component CSS
.iridescence-container {
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: waves code: ## Integrate the <Waves /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Waves
Variant: JavaScript + CSS
Usage Example
import Waves from './Waves';
<Waves
lineColor="#fff"
backgroundColor="rgba(255, 255, 255, 0.2)"
waveSpeedX={0.02}
waveSpeedY={0.01}
waveAmpX={40}
waveAmpY={20}
friction={0.9}
tension={0.01}
maxCursorMove={120}
xGap={12}
yGap={36}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| lineColor | string | black | Defines the color of the wave lines drawn on the canvas. |
| backgroundColor | string | transparent | Sets the background color of the waves container. |
| waveSpeedX | number | 0.0125 | Horizontal speed factor for the wave animation. |
| waveSpeedY | number | 0.005 | Vertical speed factor for the wave animation. |
| waveAmpX | number | 32 | Horizontal amplitude of each wave. |
| waveAmpY | number | 16 | Vertical amplitude of each wave. |
| xGap | number | 10 | Horizontal gap between individual wave lines. |
| yGap | number | 32 | Vertical gap between points on each wave line. |
| friction | number | 0.925 | Controls how quickly the cursor effect slows down. |
| tension | number | 0.005 | Determines the 'springiness' of the cursor effect on points. |
| maxCursorMove | number | 100 | Limits how far each point can shift due to cursor movement. |
| style | object | {} | Inline styles applied to the container element. |
| className | string | — | Custom class name(s) applied to the container element. |
Full Component Source
import { useRef, useEffect } from 'react';
import './Waves.css';
class Grad {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
dot2(x, y) {
return this.x * x + this.y * y;
}
}
class Noise {
constructor(seed = 0) {
this.grad3 = [
new Grad(1, 1, 0),
new Grad(-1, 1, 0),
new Grad(1, -1, 0),
new Grad(-1, -1, 0),
new Grad(1, 0, 1),
new Grad(-1, 0, 1),
new Grad(1, 0, -1),
new Grad(-1, 0, -1),
new Grad(0, 1, 1),
new Grad(0, -1, 1),
new Grad(0, 1, -1),
new Grad(0, -1, -1)
];
this.p = [
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240,
21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88,
237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83,
111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216,
80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186,
3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,
17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193,
238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128,
195, 78, 66, 215, 61, 156, 180
];
this.perm = new Array(512);
this.gradP = new Array(512);
this.seed(seed);
}
seed(seed) {
if (seed > 0 && seed < 1) seed *= 65536;
seed = Math.floor(seed);
if (seed < 256) seed |= seed << 8;
for (let i = 0; i < 256; i++) {
let v = i & 1 ? this.p[i] ^ (seed & 255) : this.p[i] ^ ((seed >> 8) & 255);
this.perm[i] = this.perm[i + 256] = v;
this.gradP[i] = this.gradP[i + 256] = this.grad3[v % 12];
}
}
fade(t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
lerp(a, b, t) {
return (1 - t) * a + t * b;
}
perlin2(x, y) {
let X = Math.floor(x),
Y = Math.floor(y);
x -= X;
y -= Y;
X &= 255;
Y &= 255;
const n00 = this.gradP[X + this.perm[Y]].dot2(x, y);
const n01 = this.gradP[X + this.perm[Y + 1]].dot2(x, y - 1);
const n10 = this.gradP[X + 1 + this.perm[Y]].dot2(x - 1, y);
const n11 = this.gradP[X + 1 + this.perm[Y + 1]].dot2(x - 1, y - 1);
const u = this.fade(x);
return this.lerp(this.lerp(n00, n10, u), this.lerp(n01, n11, u), this.fade(y));
}
}
const Waves = ({
lineColor = 'black',
backgroundColor = 'transparent',
waveSpeedX = 0.0125,
waveSpeedY = 0.005,
waveAmpX = 32,
waveAmpY = 16,
xGap = 10,
yGap = 32,
friction = 0.925,
tension = 0.005,
maxCursorMove = 100,
style = {},
className = ''
}) => {
const containerRef = useRef(null);
const canvasRef = useRef(null);
const ctxRef = useRef(null);
const boundingRef = useRef({ width: 0, height: 0, left: 0, top: 0 });
const noiseRef = useRef(new Noise(Math.random()));
const linesRef = useRef([]);
const mouseRef = useRef({
x: -10,
y: 0,
lx: 0,
ly: 0,
sx: 0,
sy: 0,
v: 0,
vs: 0,
a: 0,
set: false
});
const configRef = useRef({
lineColor,
waveSpeedX,
waveSpeedY,
waveAmpX,
waveAmpY,
friction,
tension,
maxCursorMove,
xGap,
yGap
});
const frameIdRef = useRef(null);
useEffect(() => {
configRef.current = {
lineColor,
waveSpeedX,
waveSpeedY,
waveAmpX,
waveAmpY,
friction,
tension,
maxCursorMove,
xGap,
yGap
};
}, [lineColor, waveSpeedX, waveSpeedY, waveAmpX, waveAmpY, friction, tension, maxCursorMove, xGap, yGap]);
useEffect(() => {
console.log('Waves mounted');
const canvas = canvasRef.current;
const container = containerRef.current;
ctxRef.current = canvas.getContext('2d');
function setSize() {
boundingRef.current = container.getBoundingClientRect();
canvas.width = boundingRef.current.width;
canvas.height = boundingRef.current.height;
}
function setLines() {
const { width, height } = boundingRef.current;
linesRef.current = [];
const oWidth = width + 200,
oHeight = height + 30;
const { xGap, yGap } = configRef.current;
const totalLines = Math.ceil(oWidth / xGap);
const totalPoints = Math.ceil(oHeight / yGap);
const xStart = (width - xGap * totalLines) / 2;
const yStart = (height - yGap * totalPoints) / 2;
for (let i = 0; i <= totalLines; i++) {
const pts = [];
for (let j = 0; j <= totalPoints; j++) {
pts.push({
x: xStart + xGap * i,
y: yStart + yGap * j,
wave: { x: 0, y: 0 },
cursor: { x: 0, y: 0, vx: 0, vy: 0 }
});
}
linesRef.current.push(pts);
}
}
function movePoints(time) {
const lines = linesRef.current,
mouse = mouseRef.current,
noise = noiseRef.current;
const { waveSpeedX, waveSpeedY, waveAmpX, waveAmpY, friction, tension, maxCursorMove } = configRef.current;
lines.forEach(pts => {
pts.forEach(p => {
const move = noise.perlin2((p.x + time * waveSpeedX) * 0.002, (p.y + time * waveSpeedY) * 0.0015) * 12;
p.wave.x = Math.cos(move) * waveAmpX;
p.wave.y = Math.sin(move) * waveAmpY;
const dx = p.x - mouse.sx,
dy = p.y - mouse.sy;
const dist = Math.hypot(dx, dy),
l = Math.max(175, mouse.vs);
if (dist < l) {
const s = 1 - dist / l;
const f = Math.cos(dist * 0.001) * s;
p.cursor.vx += Math.cos(mouse.a) * f * l * mouse.vs * 0.00065;
p.cursor.vy += Math.sin(mouse.a) * f * l * mouse.vs * 0.00065;
}
p.cursor.vx += (0 - p.cursor.x) * tension;
p.cursor.vy += (0 - p.cursor.y) * tension;
p.cursor.vx *= friction;
p.cursor.vy *= friction;
p.cursor.x += p.cursor.vx * 2;
p.cursor.y += p.cursor.vy * 2;
p.cursor.x = Math.min(maxCursorMove, Math.max(-maxCursorMove, p.cursor.x));
p.cursor.y = Math.min(maxCursorMove, Math.max(-maxCursorMove, p.cursor.y));
});
});
}
function moved(point, withCursor = true) {
const x = point.x + point.wave.x + (withCursor ? point.cursor.x : 0);
const y = point.y + point.wave.y + (withCursor ? point.cursor.y : 0);
return { x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 };
}
function drawLines() {
const { width, height } = boundingRef.current;
const ctx = ctxRef.current;
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
ctx.strokeStyle = configRef.current.lineColor;
linesRef.current.forEach(points => {
let p1 = moved(points[0], false);
ctx.moveTo(p1.x, p1.y);
points.forEach((p, idx) => {
const isLast = idx === points.length - 1;
p1 = moved(p, !isLast);
const p2 = moved(points[idx + 1] || points[points.length - 1], !isLast);
ctx.lineTo(p1.x, p1.y);
if (isLast) ctx.moveTo(p2.x, p2.y);
});
});
ctx.stroke();
}
function tick(t) {
const mouse = mouseRef.current;
mouse.sx += (mouse.x - mouse.sx) * 0.1;
mouse.sy += (mouse.y - mouse.sy) * 0.1;
const dx = mouse.x - mouse.lx,
dy = mouse.y - mouse.ly;
const d = Math.hypot(dx, dy);
mouse.v = d;
mouse.vs += (d - mouse.vs) * 0.1;
mouse.vs = Math.min(100, mouse.vs);
mouse.lx = mouse.x;
mouse.ly = mouse.y;
mouse.a = Math.atan2(dy, dx);
container.style.setProperty('--x', `${mouse.sx}px`);
container.style.setProperty('--y', `${mouse.sy}px`);
movePoints(t);
drawLines();
frameIdRef.current = requestAnimationFrame(tick);
}
function onResize() {
setSize();
setLines();
}
function onMouseMove(e) {
updateMouse(e.clientX, e.clientY);
}
function onTouchMove(e) {
const touch = e.touches[0];
updateMouse(touch.clientX, touch.clientY);
}
function updateMouse(x, y) {
const mouse = mouseRef.current,
b = boundingRef.current;
mouse.x = x - b.left;
mouse.y = y - b.top;
if (!mouse.set) {
mouse.sx = mouse.x;
mouse.sy = mouse.y;
mouse.lx = mouse.x;
mouse.ly = mouse.y;
mouse.set = true;
}
}
setSize();
setLines();
frameIdRef.current = requestAnimationFrame(tick);
window.addEventListener('resize', onResize);
window.addEventListener('mousemove', onMouseMove);
window.addEventListener('touchmove', onTouchMove, { passive: false });
return () => {
window.removeEventListener('resize', onResize);
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('touchmove', onTouchMove);
cancelAnimationFrame(frameIdRef.current);
};
}, []);
return (
<div
ref={containerRef}
className={`waves ${className}`}
style={{
position: 'absolute',
top: 0,
left: 0,
margin: 0,
padding: 0,
width: '100%',
height: '100%',
overflow: 'hidden',
backgroundColor,
...style
}}
>
<canvas ref={canvasRef} className="waves-canvas" />
</div>
);
};
export default Waves;
Component CSS
.waves {
position: absolute;
top: 0;
left: 0;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.waves::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 0.5rem;
height: 0.5rem;
background: #160000;
border-radius: 50%;
transform: translate3d(calc(var(-0.5rem) - 50%), calc(var(50%) - 50%), 0);
will-change: transform;
}
.waves-canvas {
display: block;
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: grid distortion code: ## Integrate the <GridDistortion /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: GridDistortion
Variant: JavaScript + CSS
Dependencies: three
Usage Example
import GridDistortion from './GridDistortion';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<GridDistortion
imageSrc="https://picsum.photos/1920/1080?grayscale"
grid={10}
mouse={0.1}
strength={0.15}
relaxation={0.9}
className="custom-class"
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| imgageSrc | string | — | The image you want to render inside the container. |
| grid | number | 15 | The number of cells present in the distortion grid |
| mouse | number | 0.1 | The size of the distortion effect that follows the cursor. |
| relaxation | number | 0.9 | The speed at which grid cells return to their initial state. |
| strength | number | 0.15 | The overall strength of the distortion effect. |
| className | string | — | Any custom class(es) you want to apply to the container. |
Full Component Source
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
import './GridDistortion.css';
const vertexShader = `
uniform float time;
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vUv = uv;
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}`;
const fragmentShader = `
uniform sampler2D uDataTexture;
uniform sampler2D uTexture;
uniform vec4 resolution;
varying vec2 vUv;
void main() {
vec2 uv = vUv;
vec4 offset = texture2D(uDataTexture, vUv);
gl_FragColor = texture2D(uTexture, uv - 0.02 * offset.rg);
}`;
const GridDistortion = ({ grid = 15, mouse = 0.1, strength = 0.15, relaxation = 0.9, imageSrc, className = '' }) => {
const containerRef = useRef(null);
const sceneRef = useRef(null);
const rendererRef = useRef(null);
const cameraRef = useRef(null);
const planeRef = useRef(null);
const imageAspectRef = useRef(1);
const animationIdRef = useRef(null);
const resizeObserverRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const container = containerRef.current;
const scene = new THREE.Scene();
sceneRef.current = scene;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: 'high-performance'
});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setClearColor(0x000000, 0);
rendererRef.current = renderer;
container.innerHTML = '';
container.appendChild(renderer.domElement);
const camera = new THREE.OrthographicCamera(0, 0, 0, 0, -1000, 1000);
camera.position.z = 2;
cameraRef.current = camera;
const uniforms = {
time: { value: 0 },
resolution: { value: new THREE.Vector4() },
uTexture: { value: null },
uDataTexture: { value: null }
};
const textureLoader = new THREE.TextureLoader();
textureLoader.load(imageSrc, texture => {
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.wrapS = THREE.ClampToEdgeWrapping;
texture.wrapT = THREE.ClampToEdgeWrapping;
imageAspectRef.current = texture.image.width / texture.image.height;
uniforms.uTexture.value = texture;
handleResize();
});
const size = grid;
const data = new Float32Array(4 * size * size);
for (let i = 0; i < size * size; i++) {
data[i * 4] = Math.random() * 255 - 125;
data[i * 4 + 1] = Math.random() * 255 - 125;
}
const dataTexture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);
dataTexture.needsUpdate = true;
uniforms.uDataTexture.value = dataTexture;
const material = new THREE.ShaderMaterial({
side: THREE.DoubleSide,
uniforms,
vertexShader,
fragmentShader,
transparent: true
});
const geometry = new THREE.PlaneGeometry(1, 1, size - 1, size - 1);
const plane = new THREE.Mesh(geometry, material);
planeRef.current = plane;
scene.add(plane);
const handleResize = () => {
if (!container || !renderer || !camera) return;
const rect = container.getBoundingClientRect();
const width = rect.width;
const height = rect.height;
if (width === 0 || height === 0) return;
const containerAspect = width / height;
renderer.setSize(width, height);
if (plane) {
plane.scale.set(containerAspect, 1, 1);
}
const frustumHeight = 1;
const frustumWidth = frustumHeight * containerAspect;
camera.left = -frustumWidth / 2;
camera.right = frustumWidth / 2;
camera.top = frustumHeight / 2;
camera.bottom = -frustumHeight / 2;
camera.updateProjectionMatrix();
uniforms.resolution.value.set(width, height, 1, 1);
};
if (window.ResizeObserver) {
const resizeObserver = new ResizeObserver(() => {
handleResize();
});
resizeObserver.observe(container);
resizeObserverRef.current = resizeObserver;
} else {
window.addEventListener('resize', handleResize);
}
const mouseState = {
x: 0,
y: 0,
prevX: 0,
prevY: 0,
vX: 0,
vY: 0
};
const handleMouseMove = e => {
const rect = container.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = 1 - (e.clientY - rect.top) / rect.height;
mouseState.vX = x - mouseState.prevX;
mouseState.vY = y - mouseState.prevY;
Object.assign(mouseState, { x, y, prevX: x, prevY: y });
};
const handleMouseLeave = () => {
if (dataTexture) {
dataTexture.needsUpdate = true;
}
Object.assign(mouseState, {
x: 0,
y: 0,
prevX: 0,
prevY: 0,
vX: 0,
vY: 0
});
};
container.addEventListener('mousemove', handleMouseMove);
container.addEventListener('mouseleave', handleMouseLeave);
handleResize();
const animate = () => {
animationIdRef.current = requestAnimationFrame(animate);
if (!renderer || !scene || !camera) return;
uniforms.time.value += 0.05;
const data = dataTexture.image.data;
for (let i = 0; i < size * size; i++) {
data[i * 4] *= relaxation;
data[i * 4 + 1] *= relaxation;
}
const gridMouseX = size * mouseState.x;
const gridMouseY = size * mouseState.y;
const maxDist = size * mouse;
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
const distSq = Math.pow(gridMouseX - i, 2) + Math.pow(gridMouseY - j, 2);
if (distSq < maxDist * maxDist) {
const index = 4 * (i + size * j);
const power = Math.min(maxDist / Math.sqrt(distSq), 10);
data[index] += strength * 100 * mouseState.vX * power;
data[index + 1] -= strength * 100 * mouseState.vY * power;
}
}
}
dataTexture.needsUpdate = true;
renderer.render(scene, camera);
};
animate();
return () => {
if (animationIdRef.current) {
cancelAnimationFrame(animationIdRef.current);
}
if (resizeObserverRef.current) {
resizeObserverRef.current.disconnect();
} else {
window.removeEventListener('resize', handleResize);
}
container.removeEventListener('mousemove', handleMouseMove);
container.removeEventListener('mouseleave', handleMouseLeave);
if (renderer) {
renderer.dispose();
renderer.forceContextLoss();
if (container.contains(renderer.domElement)) {
container.removeChild(renderer.domElement);
}
}
if (geometry) geometry.dispose();
if (material) material.dispose();
if (dataTexture) dataTexture.dispose();
if (uniforms.uTexture.value) uniforms.uTexture.value.dispose();
sceneRef.current = null;
rendererRef.current = null;
cameraRef.current = null;
planeRef.current = null;
};
}, [grid, mouse, strength, relaxation, imageSrc]);
return (
<div
ref={containerRef}
className={`distortion-container ${className}`}
style={{
width: '100%',
height: '100%',
minWidth: '0',
minHeight: '0'
}}
/>
);
};
export default GridDistortion;
Component CSS
.distortion-container {
width: 100%;
height: 100%;
overflow: hidden;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: ballpit code: ## Integrate the <Ballpit /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Ballpit
Variant: JavaScript + CSS
Dependencies: three
Usage Example
//Component inspired by Kevin Levron:
//https://x.com/soju22/status/1858925191671271801
import Ballpit from './Ballpit;'
<div style={{position: 'relative', overflow: 'hidden', minHeight: '500px', maxHeight: '500px', width: '100%'}}>
<Ballpit
count={200}
gravity={0.7}
friction={0.8}
wallBounce={0.95}
followCursor={true}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| count | number | 200 | Sets the number of balls in the ballpit. |
| gravity | number | 0.5 | Controls the gravity affecting the balls. |
| friction | number | 0.9975 | Sets the friction applied to the ball movement. |
| wallBounce | number | 0.95 | Determines how much balls bounce off walls. |
| followCursor | boolean | true | Enables or disables the sphere following the cursor. |
| colors | array | [0, 0, 0] | Defines the colors of the balls. |
| ambientColor | number | 16777215 | Sets the ambient light color. |
| ambientIntensity | number | 1 | Controls the intensity of ambient light. |
| lightIntensity | number | 200 | Sets the intensity of the main light source. |
| minSize | number | 0.5 | Specifies the minimum size of the balls. |
| maxSize | number | 1 | Specifies the maximum size of the balls. |
| size0 | number | 1 | Initial size value for the cursor ball. |
| maxVelocity | number | 0.15 | Limits the maximum velocity of the balls. |
| maxX | number | 5 | Defines the maximum X-coordinate boundary. |
| maxY | number | 5 | Defines the maximum Y-coordinate boundary. |
| maxZ | number | 2 | Defines the maximum Z-coordinate boundary. |
Full Component Source
import { useEffect, useRef } from 'react';
import {
Vector3 as a,
MeshPhysicalMaterial as c,
InstancedMesh as d,
Clock as e,
AmbientLight as f,
SphereGeometry as g,
ShaderChunk as h,
Scene as i,
Color as l,
Object3D as m,
SRGBColorSpace as n,
MathUtils as o,
PMREMGenerator as p,
Vector2 as r,
WebGLRenderer as s,
PerspectiveCamera as t,
PointLight as u,
ACESFilmicToneMapping as v,
Plane as w,
Raycaster as y
} from 'three';
import { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';
class x {
#e;
canvas;
camera;
cameraMinAspect;
cameraMaxAspect;
cameraFov;
maxPixelRatio;
minPixelRatio;
scene;
renderer;
#t;
size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };
render = this.#i;
onBeforeRender = () => {};
onAfterRender = () => {};
onAfterResize = () => {};
#s = false;
#n = false;
isDisposed = false;
#o;
#r;
#a;
#c = new e();
#h = { elapsed: 0, delta: 0 };
#l;
constructor(e) {
this.#e = { ...e };
this.#m();
this.#d();
this.#p();
this.resize();
this.#g();
}
#m() {
this.camera = new t();
this.cameraFov = this.camera.fov;
}
#d() {
this.scene = new i();
}
#p() {
if (this.#e.canvas) {
this.canvas = this.#e.canvas;
} else if (this.#e.id) {
this.canvas = document.getElementById(this.#e.id);
} else {
console.error('Three: Missing canvas or id parameter');
}
this.canvas.style.display = 'block';
const e = {
canvas: this.canvas,
powerPreference: 'high-performance',
...(this.#e.rendererOptions ?? {})
};
this.renderer = new s(e);
this.renderer.outputColorSpace = n;
}
#g() {
if (!(this.#e.size instanceof Object)) {
window.addEventListener('resize', this.#f.bind(this));
if (this.#e.size === 'parent' && this.canvas.parentNode) {
this.#r = new ResizeObserver(this.#f.bind(this));
this.#r.observe(this.canvas.parentNode);
}
}
this.#o = new IntersectionObserver(this.#u.bind(this), {
root: null,
rootMargin: '0px',
threshold: 0
});
this.#o.observe(this.canvas);
document.addEventListener('visibilitychange', this.#v.bind(this));
}
#y() {
window.removeEventListener('resize', this.#f.bind(this));
this.#r?.disconnect();
this.#o?.disconnect();
document.removeEventListener('visibilitychange', this.#v.bind(this));
}
#u(e) {
this.#s = e[0].isIntersecting;
this.#s ? this.#w() : this.#z();
}
#v() {
if (this.#s) {
document.hidden ? this.#z() : this.#w();
}
}
#f() {
if (this.#a) clearTimeout(this.#a);
this.#a = setTimeout(this.resize.bind(this), 100);
}
resize() {
let e, t;
if (this.#e.size instanceof Object) {
e = this.#e.size.width;
t = this.#e.size.height;
} else if (this.#e.size === 'parent' && this.canvas.parentNode) {
e = this.canvas.parentNode.offsetWidth;
t = this.canvas.parentNode.offsetHeight;
} else {
e = window.innerWidth;
t = window.innerHeight;
}
this.size.width = e;
this.size.height = t;
this.size.ratio = e / t;
this.#x();
this.#b();
this.onAfterResize(this.size);
}
#x() {
this.camera.aspect = this.size.width / this.size.height;
if (this.camera.isPerspectiveCamera && this.cameraFov) {
if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {
this.#A(this.cameraMinAspect);
} else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {
this.#A(this.cameraMaxAspect);
} else {
this.camera.fov = this.cameraFov;
}
}
this.camera.updateProjectionMatrix();
this.updateWorldSize();
}
#A(e) {
const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);
this.camera.fov = 2 * o.radToDeg(Math.atan(t));
}
updateWorldSize() {
if (this.camera.isPerspectiveCamera) {
const e = (this.camera.fov * Math.PI) / 180;
this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();
this.size.wWidth = this.size.wHeight * this.camera.aspect;
} else if (this.camera.isOrthographicCamera) {
this.size.wHeight = this.camera.top - this.camera.bottom;
this.size.wWidth = this.camera.right - this.camera.left;
}
}
#b() {
this.renderer.setSize(this.size.width, this.size.height);
this.#t?.setSize(this.size.width, this.size.height);
let e = window.devicePixelRatio;
if (this.maxPixelRatio && e > this.maxPixelRatio) {
e = this.maxPixelRatio;
} else if (this.minPixelRatio && e < this.minPixelRatio) {
e = this.minPixelRatio;
}
this.renderer.setPixelRatio(e);
this.size.pixelRatio = e;
}
get postprocessing() {
return this.#t;
}
set postprocessing(e) {
this.#t = e;
this.render = e.render.bind(e);
}
#w() {
if (this.#n) return;
const animate = () => {
this.#l = requestAnimationFrame(animate);
this.#h.delta = this.#c.getDelta();
this.#h.elapsed += this.#h.delta;
this.onBeforeRender(this.#h);
this.render();
this.onAfterRender(this.#h);
};
this.#n = true;
this.#c.start();
animate();
}
#z() {
if (this.#n) {
cancelAnimationFrame(this.#l);
this.#n = false;
this.#c.stop();
}
}
#i() {
this.renderer.render(this.scene, this.camera);
}
clear() {
this.scene.traverse(e => {
if (e.isMesh && typeof e.material === 'object' && e.material !== null) {
Object.keys(e.material).forEach(t => {
const i = e.material[t];
if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {
i.dispose();
}
});
e.material.dispose();
e.geometry.dispose();
}
});
this.scene.clear();
}
dispose() {
this.#y();
this.#z();
this.clear();
this.#t?.dispose();
this.renderer.dispose();
this.renderer.forceContextLoss();
this.isDisposed = true;
}
}
const b = new Map(),
A = new r();
let R = false;
function S(e) {
const t = {
position: new r(),
nPosition: new r(),
hover: false,
touching: false,
onEnter() {},
onMove() {},
onClick() {},
onLeave() {},
...e
};
(function (e, t) {
if (!b.has(e)) {
b.set(e, t);
if (!R) {
document.body.addEventListener('pointermove', M);
document.body.addEventListener('pointerleave', L);
document.body.addEventListener('click', C);
document.body.addEventListener('touchstart', TouchStart, { passive: false });
document.body.addEventListener('touchmove', TouchMove, { passive: false });
document.body.addEventListener('touchend', TouchEnd, { passive: false });
document.body.addEventListener('touchcancel', TouchEnd, { passive: false });
R = true;
}
}
})(e.domElement, t);
t.dispose = () => {
const t = e.domElement;
b.delete(t);
if (b.size === 0) {
document.body.removeEventListener('pointermove', M);
document.body.removeEventListener('pointerleave', L);
document.body.removeEventListener('click', C);
document.body.removeEventListener('touchstart', TouchStart);
document.body.removeEventListener('touchmove', TouchMove);
document.body.removeEventListener('touchend', TouchEnd);
document.body.removeEventListener('touchcancel', TouchEnd);
R = false;
}
};
return t;
}
function M(e) {
A.x = e.clientX;
A.y = e.clientY;
processInteraction();
}
function processInteraction() {
for (const [elem, t] of b) {
const i = elem.getBoundingClientRect();
if (D(i)) {
P(t, i);
if (!t.hover) {
t.hover = true;
t.onEnter(t);
}
t.onMove(t);
} else if (t.hover && !t.touching) {
t.hover = false;
t.onLeave(t);
}
}
}
function C(e) {
A.x = e.clientX;
A.y = e.clientY;
for (const [elem, t] of b) {
const i = elem.getBoundingClientRect();
P(t, i);
if (D(i)) t.onClick(t);
}
}
function L() {
for (const t of b.values()) {
if (t.hover) {
t.hover = false;
t.onLeave(t);
}
}
}
function TouchStart(e) {
if (e.touches.length > 0) {
e.preventDefault();
A.x = e.touches[0].clientX;
A.y = e.touches[0].clientY;
for (const [elem, t] of b) {
const rect = elem.getBoundingClientRect();
if (D(rect)) {
t.touching = true;
P(t, rect);
if (!t.hover) {
t.hover = true;
t.onEnter(t);
}
t.onMove(t);
}
}
}
}
function TouchMove(e) {
if (e.touches.length > 0) {
e.preventDefault();
A.x = e.touches[0].clientX;
A.y = e.touches[0].clientY;
for (const [elem, t] of b) {
const rect = elem.getBoundingClientRect();
P(t, rect);
if (D(rect)) {
if (!t.hover) {
t.hover = true;
t.touching = true;
t.onEnter(t);
}
t.onMove(t);
} else if (t.hover && t.touching) {
t.onMove(t);
}
}
}
}
function TouchEnd() {
for (const [, t] of b) {
if (t.touching) {
t.touching = false;
if (t.hover) {
t.hover = false;
t.onLeave(t);
}
}
}
}
function P(e, t) {
const { position: i, nPosition: s } = e;
i.x = A.x - t.left;
i.y = A.y - t.top;
s.x = (i.x / t.width) * 2 - 1;
s.y = (-i.y / t.height) * 2 + 1;
}
function D(e) {
const { x: t, y: i } = A;
const { left: s, top: n, width: o, height: r } = e;
return t >= s && t <= s + o && i >= n && i <= n + r;
}
const { randFloat: k, randFloatSpread: E } = o;
const F = new a();
const I = new a();
const O = new a();
const V = new a();
const B = new a();
const N = new a();
const _ = new a();
const j = new a();
const H = new a();
const T = new a();
class W {
constructor(e) {
this.config = e;
this.positionData = new Float32Array(3 * e.count).fill(0);
this.velocityData = new Float32Array(3 * e.count).fill(0);
this.sizeData = new Float32Array(e.count).fill(1);
this.center = new a();
this.#R();
this.setSizes();
}
#R() {
const { config: e, positionData: t } = this;
this.center.toArray(t, 0);
for (let i = 1; i < e.count; i++) {
const s = 3 * i;
t[s] = E(2 * e.maxX);
t[s + 1] = E(2 * e.maxY);
t[s + 2] = E(2 * e.maxZ);
}
}
setSizes() {
const { config: e, sizeData: t } = this;
t[0] = e.size0;
for (let i = 1; i < e.count; i++) {
t[i] = k(e.minSize, e.maxSize);
}
}
update(e) {
const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;
let r = 0;
if (t.controlSphere0) {
r = 1;
F.fromArray(s, 0);
F.lerp(i, 0.1).toArray(s, 0);
V.set(0, 0, 0).toArray(o, 0);
}
for (let idx = r; idx < t.count; idx++) {
const base = 3 * idx;
I.fromArray(s, base);
B.fromArray(o, base);
B.y -= e.delta * t.gravity * n[idx];
B.multiplyScalar(t.friction);
B.clampLength(0, t.maxVelocity);
I.add(B);
I.toArray(s, base);
B.toArray(o, base);
}
for (let idx = r; idx < t.count; idx++) {
const base = 3 * idx;
I.fromArray(s, base);
B.fromArray(o, base);
const radius = n[idx];
for (let jdx = idx + 1; jdx < t.count; jdx++) {
const otherBase = 3 * jdx;
O.fromArray(s, otherBase);
N.fromArray(o, otherBase);
const otherRadius = n[jdx];
_.copy(O).sub(I);
const dist = _.length();
const sumRadius = radius + otherRadius;
if (dist < sumRadius) {
const overlap = sumRadius - dist;
j.copy(_)
.normalize()
.multiplyScalar(0.5 * overlap);
H.copy(j).multiplyScalar(Math.max(B.length(), 1));
T.copy(j).multiplyScalar(Math.max(N.length(), 1));
I.sub(j);
B.sub(H);
I.toArray(s, base);
B.toArray(o, base);
O.add(j);
N.add(T);
O.toArray(s, otherBase);
N.toArray(o, otherBase);
}
}
if (t.controlSphere0) {
_.copy(F).sub(I);
const dist = _.length();
const sumRadius0 = radius + n[0];
if (dist < sumRadius0) {
const diff = sumRadius0 - dist;
j.copy(_.normalize()).multiplyScalar(diff);
H.copy(j).multiplyScalar(Math.max(B.length(), 2));
I.sub(j);
B.sub(H);
}
}
if (Math.abs(I.x) + radius > t.maxX) {
I.x = Math.sign(I.x) * (t.maxX - radius);
B.x = -B.x * t.wallBounce;
}
if (t.gravity === 0) {
if (Math.abs(I.y) + radius > t.maxY) {
I.y = Math.sign(I.y) * (t.maxY - radius);
B.y = -B.y * t.wallBounce;
}
} else if (I.y - radius < -t.maxY) {
I.y = -t.maxY + radius;
B.y = -B.y * t.wallBounce;
}
const maxBoundary = Math.max(t.maxZ, t.maxSize);
if (Math.abs(I.z) + radius > maxBoundary) {
I.z = Math.sign(I.z) * (t.maxZ - radius);
B.z = -B.z * t.wallBounce;
}
I.toArray(s, base);
B.toArray(o, base);
}
}
}
class Y extends c {
constructor(e) {
super(e);
this.uniforms = {
thicknessDistortion: { value: 0.1 },
thicknessAmbient: { value: 0 },
thicknessAttenuation: { value: 0.1 },
thicknessPower: { value: 2 },
thicknessScale: { value: 10 }
};
this.defines.USE_UV = '';
this.onBeforeCompile = e => {
Object.assign(e.uniforms, this.uniforms);
e.fragmentShader =
'\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ' +
e.fragmentShader;
e.fragmentShader = e.fragmentShader.replace(
'void main() {',
'\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n '
);
const t = h.lights_fragment_begin.replaceAll(
'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',
'\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n '
);
e.fragmentShader = e.fragmentShader.replace('#include <lights_fragment_begin>', t);
if (this.onBeforeCompile2) this.onBeforeCompile2(e);
};
}
}
const X = {
count: 200,
colors: [0, 0, 0],
ambientColor: 16777215,
ambientIntensity: 1,
lightIntensity: 200,
materialParams: {
metalness: 0.5,
roughness: 0.5,
clearcoat: 1,
clearcoatRoughness: 0.15
},
minSize: 0.5,
maxSize: 1,
size0: 1,
gravity: 0.5,
friction: 0.9975,
wallBounce: 0.95,
maxVelocity: 0.15,
maxX: 5,
maxY: 5,
maxZ: 2,
controlSphere0: false,
followCursor: true
};
const U = new m();
class Z extends d {
constructor(e, t = {}) {
const i = { ...X, ...t };
const s = new z();
const n = new p(e, 0.04).fromScene(s).texture;
const o = new g();
const r = new Y({ envMap: n, ...i.materialParams });
r.envMapRotation.x = -Math.PI / 2;
super(o, r, i.count);
this.config = i;
this.physics = new W(i);
this.#S();
this.setColors(i.colors);
}
#S() {
this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);
this.add(this.ambientLight);
this.light = new u(this.config.colors[0], this.config.lightIntensity);
this.add(this.light);
}
setColors(e) {
if (Array.isArray(e) && e.length > 1) {
const t = (function (e) {
let t, i;
function setColors(e) {
t = e;
i = [];
t.forEach(col => {
i.push(new l(col));
});
}
setColors(e);
return {
setColors,
getColorAt: function (ratio, out = new l()) {
const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);
const idx = Math.floor(scaled);
const start = i[idx];
if (idx >= t.length - 1) return start.clone();
const alpha = scaled - idx;
const end = i[idx + 1];
out.r = start.r + alpha * (end.r - start.r);
out.g = start.g + alpha * (end.g - start.g);
out.b = start.b + alpha * (end.b - start.b);
return out;
}
};
})(e);
for (let idx = 0; idx < this.count; idx++) {
this.setColorAt(idx, t.getColorAt(idx / this.count));
if (idx === 0) {
this.light.color.copy(t.getColorAt(idx / this.count));
}
}
this.instanceColor.needsUpdate = true;
}
}
update(e) {
this.physics.update(e);
for (let idx = 0; idx < this.count; idx++) {
U.position.fromArray(this.physics.positionData, 3 * idx);
if (idx === 0 && this.config.followCursor === false) {
U.scale.setScalar(0);
} else {
U.scale.setScalar(this.physics.sizeData[idx]);
}
U.updateMatrix();
this.setMatrixAt(idx, U.matrix);
if (idx === 0) this.light.position.copy(U.position);
}
this.instanceMatrix.needsUpdate = true;
}
}
function createBallpit(e, t = {}) {
const i = new x({
canvas: e,
size: 'parent',
rendererOptions: { antialias: true, alpha: true }
});
let s;
i.renderer.toneMapping = v;
i.camera.position.set(0, 0, 20);
i.camera.lookAt(0, 0, 0);
i.cameraMaxAspect = 1.5;
i.resize();
initialize(t);
const n = new y();
const o = new w(new a(0, 0, 1), 0);
const r = new a();
let c = false;
e.style.touchAction = 'none';
e.style.userSelect = 'none';
e.style.webkitUserSelect = 'none';
const h = S({
domElement: e,
onMove() {
n.setFromCamera(h.nPosition, i.camera);
i.camera.getWorldDirection(o.normal);
n.ray.intersectPlane(o, r);
s.physics.center.copy(r);
s.config.controlSphere0 = true;
},
onLeave() {
s.config.controlSphere0 = false;
}
});
function initialize(e) {
if (s) {
i.clear();
i.scene.remove(s);
}
s = new Z(i.renderer, e);
i.scene.add(s);
}
i.onBeforeRender = e => {
if (!c) s.update(e);
};
i.onAfterResize = e => {
s.config.maxX = e.wWidth / 2;
s.config.maxY = e.wHeight / 2;
};
return {
three: i,
get spheres() {
return s;
},
setCount(e) {
initialize({ ...s.config, count: e });
},
togglePause() {
c = !c;
},
dispose() {
h.dispose();
i.dispose();
}
};
}
const Ballpit = ({ className = '', followCursor = true, ...props }) => {
const canvasRef = useRef(null);
const spheresInstanceRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
spheresInstanceRef.current = createBallpit(canvas, { followCursor, ...props });
return () => {
if (spheresInstanceRef.current) {
spheresInstanceRef.current.dispose();
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return <canvas className={className} ref={canvasRef} style={{ width: '100%', height: '100%' }} />;
};
export default Ballpit;
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: orb code: ## Integrate the <Orb /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Orb
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Orb from './Orb';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<Orb
hoverIntensity={0.5}
rotateOnHover={true}
hue={0}
forceHoverState={false}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| hue | number | 0 | The base hue for the orb (in degrees). |
| hoverIntensity | number | 0.2 | Controls the intensity of the hover distortion effect. |
| rotateOnHover | boolean | true | Toggle to enable or disable continuous rotation on hover. |
| forceHoverState | boolean | false | Force hover animations even when the orb is not actually hovered. |
| backgroundColor | string | #000000 | The background color of the container. |
Full Component Source
import { Mesh, Program, Renderer, Triangle, Vec3 } from 'ogl';
import { useEffect, useRef } from 'react';
import './Orb.css';
export default function Orb({
hue = 0,
hoverIntensity = 0.2,
rotateOnHover = true,
forceHoverState = false,
backgroundColor = '#000000'
}) {
const ctnDom = useRef(null);
const vert = /* glsl */ `
precision highp float;
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const frag = /* glsl */ `
precision highp float;
uniform float iTime;
uniform vec3 iResolution;
uniform float hue;
uniform float hover;
uniform float rot;
uniform float hoverIntensity;
uniform vec3 backgroundColor;
varying vec2 vUv;
vec3 rgb2yiq(vec3 c) {
float y = dot(c, vec3(0.299, 0.587, 0.114));
float i = dot(c, vec3(0.596, -0.274, -0.322));
float q = dot(c, vec3(0.211, -0.523, 0.312));
return vec3(y, i, q);
}
vec3 yiq2rgb(vec3 c) {
float r = c.x + 0.956 * c.y + 0.621 * c.z;
float g = c.x - 0.272 * c.y - 0.647 * c.z;
float b = c.x - 1.106 * c.y + 1.703 * c.z;
return vec3(r, g, b);
}
vec3 adjustHue(vec3 color, float hueDeg) {
float hueRad = hueDeg * 3.14159265 / 180.0;
vec3 yiq = rgb2yiq(color);
float cosA = cos(hueRad);
float sinA = sin(hueRad);
float i = yiq.y * cosA - yiq.z * sinA;
float q = yiq.y * sinA + yiq.z * cosA;
yiq.y = i;
yiq.z = q;
return yiq2rgb(yiq);
}
vec3 hash33(vec3 p3) {
p3 = fract(p3 * vec3(0.1031, 0.11369, 0.13787));
p3 += dot(p3, p3.yxz + 19.19);
return -1.0 + 2.0 * fract(vec3(
p3.x + p3.y,
p3.x + p3.z,
p3.y + p3.z
) * p3.zyx);
}
float snoise3(vec3 p) {
const float K1 = 0.333333333;
const float K2 = 0.166666667;
vec3 i = floor(p + (p.x + p.y + p.z) * K1);
vec3 d0 = p - (i - (i.x + i.y + i.z) * K2);
vec3 e = step(vec3(0.0), d0 - d0.yzx);
vec3 i1 = e * (1.0 - e.zxy);
vec3 i2 = 1.0 - e.zxy * (1.0 - e);
vec3 d1 = d0 - (i1 - K2);
vec3 d2 = d0 - (i2 - K1);
vec3 d3 = d0 - 0.5;
vec4 h = max(0.6 - vec4(
dot(d0, d0),
dot(d1, d1),
dot(d2, d2),
dot(d3, d3)
), 0.0);
vec4 n = h * h * h * h * vec4(
dot(d0, hash33(i)),
dot(d1, hash33(i + i1)),
dot(d2, hash33(i + i2)),
dot(d3, hash33(i + 1.0))
);
return dot(vec4(31.316), n);
}
vec4 extractAlpha(vec3 colorIn) {
float a = max(max(colorIn.r, colorIn.g), colorIn.b);
return vec4(colorIn.rgb / (a + 1e-5), a);
}
const vec3 baseColor1 = vec3(0.611765, 0.262745, 0.996078);
const vec3 baseColor2 = vec3(0.298039, 0.760784, 0.913725);
const vec3 baseColor3 = vec3(0.062745, 0.078431, 0.600000);
const float innerRadius = 0.6;
const float noiseScale = 0.65;
float light1(float intensity, float attenuation, float dist) {
return intensity / (1.0 + dist * attenuation);
}
float light2(float intensity, float attenuation, float dist) {
return intensity / (1.0 + dist * dist * attenuation);
}
vec4 draw(vec2 uv) {
vec3 color1 = adjustHue(baseColor1, hue);
vec3 color2 = adjustHue(baseColor2, hue);
vec3 color3 = adjustHue(baseColor3, hue);
float ang = atan(uv.y, uv.x);
float len = length(uv);
float invLen = len > 0.0 ? 1.0 / len : 0.0;
float bgLuminance = dot(backgroundColor, vec3(0.299, 0.587, 0.114));
float n0 = snoise3(vec3(uv * noiseScale, iTime * 0.5)) * 0.5 + 0.5;
float r0 = mix(mix(innerRadius, 1.0, 0.4), mix(innerRadius, 1.0, 0.6), n0);
float d0 = distance(uv, (r0 * invLen) * uv);
float v0 = light1(1.0, 10.0, d0);
v0 *= smoothstep(r0 * 1.05, r0, len);
float innerFade = smoothstep(r0 * 0.8, r0 * 0.95, len);
v0 *= mix(innerFade, 1.0, bgLuminance * 0.7);
float cl = cos(ang + iTime * 2.0) * 0.5 + 0.5;
float a = iTime * -1.0;
vec2 pos = vec2(cos(a), sin(a)) * r0;
float d = distance(uv, pos);
float v1 = light2(1.5, 5.0, d);
v1 *= light1(1.0, 50.0, d0);
float v2 = smoothstep(1.0, mix(innerRadius, 1.0, n0 * 0.5), len);
float v3 = smoothstep(innerRadius, mix(innerRadius, 1.0, 0.5), len);
vec3 colBase = mix(color1, color2, cl);
float fadeAmount = mix(1.0, 0.1, bgLuminance);
vec3 darkCol = mix(color3, colBase, v0);
darkCol = (darkCol + v1) * v2 * v3;
darkCol = clamp(darkCol, 0.0, 1.0);
vec3 lightCol = (colBase + v1) * mix(1.0, v2 * v3, fadeAmount);
lightCol = mix(backgroundColor, lightCol, v0);
lightCol = clamp(lightCol, 0.0, 1.0);
vec3 finalCol = mix(darkCol, lightCol, bgLuminance);
return extractAlpha(finalCol);
}
vec4 mainImage(vec2 fragCoord) {
vec2 center = iResolution.xy * 0.5;
float size = min(iResolution.x, iResolution.y);
vec2 uv = (fragCoord - center) / size * 2.0;
float angle = rot;
float s = sin(angle);
float c = cos(angle);
uv = vec2(c * uv.x - s * uv.y, s * uv.x + c * uv.y);
uv.x += hover * hoverIntensity * 0.1 * sin(uv.y * 10.0 + iTime);
uv.y += hover * hoverIntensity * 0.1 * sin(uv.x * 10.0 + iTime);
return draw(uv);
}
void main() {
vec2 fragCoord = vUv * iResolution.xy;
vec4 col = mainImage(fragCoord);
gl_FragColor = vec4(col.rgb * col.a, col.a);
}
`;
useEffect(() => {
const container = ctnDom.current;
if (!container) return;
const renderer = new Renderer({ alpha: true, premultipliedAlpha: false });
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 0);
container.appendChild(gl.canvas);
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex: vert,
fragment: frag,
uniforms: {
iTime: { value: 0 },
iResolution: {
value: new Vec3(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height)
},
hue: { value: hue },
hover: { value: 0 },
rot: { value: 0 },
hoverIntensity: { value: hoverIntensity },
backgroundColor: { value: hexToVec3(backgroundColor) }
}
});
const mesh = new Mesh(gl, { geometry, program });
function resize() {
if (!container) return;
const dpr = window.devicePixelRatio || 1;
const width = container.clientWidth;
const height = container.clientHeight;
renderer.setSize(width * dpr, height * dpr);
gl.canvas.style.width = width + 'px';
gl.canvas.style.height = height + 'px';
program.uniforms.iResolution.value.set(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height);
}
window.addEventListener('resize', resize);
resize();
let targetHover = 0;
let lastTime = 0;
let currentRot = 0;
const rotationSpeed = 0.3;
const handleMouseMove = e => {
const rect = container.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const width = rect.width;
const height = rect.height;
const size = Math.min(width, height);
const centerX = width / 2;
const centerY = height / 2;
const uvX = ((x - centerX) / size) * 2.0;
const uvY = ((y - centerY) / size) * 2.0;
if (Math.sqrt(uvX * uvX + uvY * uvY) < 0.8) {
targetHover = 1;
} else {
targetHover = 0;
}
};
const handleMouseLeave = () => {
targetHover = 0;
};
container.addEventListener('mousemove', handleMouseMove);
container.addEventListener('mouseleave', handleMouseLeave);
let rafId;
const update = t => {
rafId = requestAnimationFrame(update);
const dt = (t - lastTime) * 0.001;
lastTime = t;
program.uniforms.iTime.value = t * 0.001;
program.uniforms.hue.value = hue;
program.uniforms.hoverIntensity.value = hoverIntensity;
program.uniforms.backgroundColor.value = hexToVec3(backgroundColor);
const effectiveHover = forceHoverState ? 1 : targetHover;
program.uniforms.hover.value += (effectiveHover - program.uniforms.hover.value) * 0.1;
if (rotateOnHover && effectiveHover > 0.5) {
currentRot += dt * rotationSpeed;
}
program.uniforms.rot.value = currentRot;
renderer.render({ scene: mesh });
};
rafId = requestAnimationFrame(update);
return () => {
cancelAnimationFrame(rafId);
window.removeEventListener('resize', resize);
container.removeEventListener('mousemove', handleMouseMove);
container.removeEventListener('mouseleave', handleMouseLeave);
container.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hue, hoverIntensity, rotateOnHover, forceHoverState, backgroundColor]);
return <div ref={ctnDom} className="orb-container" />;
}
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return new Vec3(r, g, b);
}
function hexToVec3(color) {
if (color.startsWith('#')) {
const r = parseInt(color.slice(1, 3), 16) / 255;
const g = parseInt(color.slice(3, 5), 16) / 255;
const b = parseInt(color.slice(5, 7), 16) / 255;
return new Vec3(r, g, b);
}
const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (rgbMatch) {
return new Vec3(parseInt(rgbMatch[1]) / 255, parseInt(rgbMatch[2]) / 255, parseInt(rgbMatch[3]) / 255);
}
const hslMatch = color.match(/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%/);
if (hslMatch) {
const h = parseInt(hslMatch[1]) / 360;
const s = parseInt(hslMatch[2]) / 100;
const l = parseInt(hslMatch[3]) / 100;
return hslToRgb(h, s, l);
}
return new Vec3(0, 0, 0);
}
Component CSS
.orb-container {
position: relative;
z-index: 0;
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: grid motion code: ## Integrate the <GridMotion /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: GridMotion
Variant: JavaScript + CSS
Dependencies: gsap
Usage Example
import GridMotion from './GridMotion';
// note: you'll need to make sure the parent container of this component is sized properly
const items = [
'Item 1',
<div key='jsx-item-1'>Custom JSX Content</div>,
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'Item 2',
<div key='jsx-item-2'>Custom JSX Content</div>,
'Item 4',
<div key='jsx-item-2'>Custom JSX Content</div>,
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'Item 5',
<div key='jsx-item-2'>Custom JSX Content</div>,
'Item 7',
<div key='jsx-item-2'>Custom JSX Content</div>,
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'Item 8',
<div key='jsx-item-2'>Custom JSX Content</div>,
'Item 10',
<div key='jsx-item-3'>Custom JSX Content</div>,
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'Item 11',
<div key='jsx-item-2'>Custom JSX Content</div>,
'Item 13',
<div key='jsx-item-4'>Custom JSX Content</div>,
'https://images.unsplash.com/photo-1723403804231-f4e9b515fe9d?q=80&w=3870&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'Item 14',
// Add more items as needed
];
<GridMotion items={items} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items | array | [] | An array of items to display in the grid. Each item can be a string, JSX element, or an image URL. |
| gradientColor | string | black | Controls the color of the radial gradient used as the background. |
Full Component Source
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import './GridMotion.css';
const GridMotion = ({ items = [], gradientColor = 'black' }) => {
const gridRef = useRef(null);
const rowRefs = useRef([]);
const mouseXRef = useRef(window.innerWidth / 2);
const totalItems = 28;
const defaultItems = Array.from({ length: totalItems }, (_, index) => `Item ${index + 1}`);
const combinedItems = items.length > 0 ? items.slice(0, totalItems) : defaultItems;
useEffect(() => {
gsap.ticker.lagSmoothing(0);
const handleMouseMove = e => {
mouseXRef.current = e.clientX;
};
const updateMotion = () => {
const maxMoveAmount = 300;
const baseDuration = 0.8;
const inertiaFactors = [0.6, 0.4, 0.3, 0.2];
rowRefs.current.forEach((row, index) => {
if (row) {
const direction = index % 2 === 0 ? 1 : -1;
const moveAmount = ((mouseXRef.current / window.innerWidth) * maxMoveAmount - maxMoveAmount / 2) * direction;
gsap.to(row, {
x: moveAmount,
duration: baseDuration + inertiaFactors[index % inertiaFactors.length],
ease: 'power3.out',
overwrite: 'auto'
});
}
});
};
const removeAnimationLoop = gsap.ticker.add(updateMotion);
window.addEventListener('mousemove', handleMouseMove);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
removeAnimationLoop();
};
}, []);
return (
<div className="noscroll loading" ref={gridRef}>
<section
className="intro"
style={{
background: `radial-gradient(circle, ${gradientColor} 0%, transparent 100%)`
}}
>
<div className="gridMotion-container">
{[...Array(4)].map((_, rowIndex) => (
<div key={rowIndex} className="row" ref={el => (rowRefs.current[rowIndex] = el)}>
{[...Array(7)].map((_, itemIndex) => {
const content = combinedItems[rowIndex * 7 + itemIndex];
return (
<div key={itemIndex} className="row__item">
<div className="row__item-inner" style={{ backgroundColor: '#111' }}>
{typeof content === 'string' && content.startsWith('http') ? (
<div
className="row__item-img"
style={{
backgroundImage: `url(${content})`
}}
></div>
) : (
<div className="row__item-content">{content}</div>
)}
</div>
</div>
);
})}
</div>
))}
</div>
<div className="fullview"></div>
</section>
</div>
);
};
export default GridMotion;
Component CSS
.noscroll {
height: 100%;
width: 100%;
overflow: hidden;
}
.intro {
width: 100%;
height: 100vh;
overflow: hidden;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.intro::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: 250px;
pointer-events: none;
z-index: 4;
}
.gridMotion-container {
gap: 1rem;
flex: none;
position: relative;
width: 150vw;
height: 150vh;
display: grid;
grid-template-rows: repeat(4, 1fr);
grid-template-columns: 100%;
transform: rotate(-15deg);
transform-origin: center center;
z-index: 2;
}
.row {
display: grid;
gap: 1rem;
grid-template-columns: repeat(7, 1fr);
will-change: transform, filter;
}
.row__item {
position: relative;
}
.row__item-inner {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 10px;
background-color: #111;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
}
.row__item-img {
width: 100%;
height: 100%;
background-size: cover;
background-position: 50% 50%;
position: absolute;
top: 0;
left: 0;
}
.row__item-content {
padding: 1rem;
text-align: center;
z-index: 1;
}
.fullview {
position: relative;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
}
.fullview .row__item-inner {
border-radius: 0px;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: shape grid code: ## Integrate the <ShapeGrid /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: ShapeGrid
Variant: JavaScript + CSS
Usage Example
import ShapeGrid from './ShapeGrid';
<ShapeGrid
speed={0.5}
squareSize={40}
direction='diagonal' // up, down, left, right, diagonal
borderColor='#fff'
hoverFillColor='#222'
shape='square' // square, hexagon, circle, triangle
hoverTrailAmount={5} // number of trailing hovered shapes (0 = no trail)
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| direction | string | 'right' | Direction of square animation. Options: 'diagonal', 'up', 'right', 'down', 'left'. |
| speed | number | 1 | Animation speed multiplier. |
| borderColor | string | '#999' | Color of the square borders. |
| squareSize | number | 40 | Size of individual squares in pixels. |
| hoverFillColor | string | '#222' | Fill color when hovering over squares. |
| shape | string | 'square' | Shape of the grid tiles. Options: 'square', 'hexagon', 'circle', 'triangle'. |
| hoverTrailAmount | number | 0 | Number of previously hovered shapes to keep visible as a fading trail. 0 disables the trail. |
Full Component Source
import { useRef, useEffect } from 'react';
import './ShapeGrid.css';
const ShapeGrid = ({
direction = 'right',
speed = 1,
borderColor = '#999',
squareSize = 40,
hoverFillColor = '#222',
shape = 'square',
hoverTrailAmount = 0,
className = ''
}) => {
const canvasRef = useRef(null);
const requestRef = useRef(null);
const numSquaresX = useRef();
const numSquaresY = useRef();
const gridOffset = useRef({ x: 0, y: 0 });
const hoveredSquare = useRef(null);
const trailCells = useRef([]);
const cellOpacities = useRef(new Map());
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
const isHex = shape === 'hexagon';
const isTri = shape === 'triangle';
const hexHoriz = squareSize * 1.5;
const hexVert = squareSize * Math.sqrt(3);
const resizeCanvas = () => {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
numSquaresX.current = Math.ceil(canvas.width / squareSize) + 1;
numSquaresY.current = Math.ceil(canvas.height / squareSize) + 1;
};
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
const drawHex = (cx, cy, size) => {
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = (Math.PI / 3) * i;
const vx = cx + size * Math.cos(angle);
const vy = cy + size * Math.sin(angle);
if (i === 0) ctx.moveTo(vx, vy);
else ctx.lineTo(vx, vy);
}
ctx.closePath();
};
const drawCircle = (cx, cy, size) => {
ctx.beginPath();
ctx.arc(cx, cy, size / 2, 0, Math.PI * 2);
ctx.closePath();
};
const drawTriangle = (cx, cy, size, flip) => {
ctx.beginPath();
if (flip) {
ctx.moveTo(cx, cy + size / 2);
ctx.lineTo(cx + size / 2, cy - size / 2);
ctx.lineTo(cx - size / 2, cy - size / 2);
} else {
ctx.moveTo(cx, cy - size / 2);
ctx.lineTo(cx + size / 2, cy + size / 2);
ctx.lineTo(cx - size / 2, cy + size / 2);
}
ctx.closePath();
};
const drawGrid = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (isHex) {
const colShift = Math.floor(gridOffset.current.x / hexHoriz);
const offsetX = ((gridOffset.current.x % hexHoriz) + hexHoriz) % hexHoriz;
const offsetY = ((gridOffset.current.y % hexVert) + hexVert) % hexVert;
const cols = Math.ceil(canvas.width / hexHoriz) + 3;
const rows = Math.ceil(canvas.height / hexVert) + 3;
for (let col = -2; col < cols; col++) {
for (let row = -2; row < rows; row++) {
const cx = col * hexHoriz + offsetX;
const cy = row * hexVert + ((col + colShift) % 2 !== 0 ? hexVert / 2 : 0) + offsetY;
const cellKey = `${col},${row}`;
const alpha = cellOpacities.current.get(cellKey);
if (alpha) {
ctx.globalAlpha = alpha;
drawHex(cx, cy, squareSize);
ctx.fillStyle = hoverFillColor;
ctx.fill();
ctx.globalAlpha = 1;
}
drawHex(cx, cy, squareSize);
ctx.strokeStyle = borderColor;
ctx.stroke();
}
}
} else if (isTri) {
const halfW = squareSize / 2;
const colShift = Math.floor(gridOffset.current.x / halfW);
const rowShift = Math.floor(gridOffset.current.y / squareSize);
const offsetX = ((gridOffset.current.x % halfW) + halfW) % halfW;
const offsetY = ((gridOffset.current.y % squareSize) + squareSize) % squareSize;
const cols = Math.ceil(canvas.width / halfW) + 4;
const rows = Math.ceil(canvas.height / squareSize) + 4;
for (let col = -2; col < cols; col++) {
for (let row = -2; row < rows; row++) {
const cx = col * halfW + offsetX;
const cy = row * squareSize + squareSize / 2 + offsetY;
const flip = ((col + colShift + row + rowShift) % 2 + 2) % 2 !== 0;
const cellKey = `${col},${row}`;
const alpha = cellOpacities.current.get(cellKey);
if (alpha) {
ctx.globalAlpha = alpha;
drawTriangle(cx, cy, squareSize, flip);
ctx.fillStyle = hoverFillColor;
ctx.fill();
ctx.globalAlpha = 1;
}
drawTriangle(cx, cy, squareSize, flip);
ctx.strokeStyle = borderColor;
ctx.stroke();
}
}
} else if (shape === 'circle') {
const offsetX = ((gridOffset.current.x % squareSize) + squareSize) % squareSize;
const offsetY = ((gridOffset.current.y % squareSize) + squareSize) % squareSize;
const cols = Math.ceil(canvas.width / squareSize) + 3;
const rows = Math.ceil(canvas.height / squareSize) + 3;
for (let col = -2; col < cols; col++) {
for (let row = -2; row < rows; row++) {
const cx = col * squareSize + squareSize / 2 + offsetX;
const cy = row * squareSize + squareSize / 2 + offsetY;
const cellKey = `${col},${row}`;
const alpha = cellOpacities.current.get(cellKey);
if (alpha) {
ctx.globalAlpha = alpha;
drawCircle(cx, cy, squareSize);
ctx.fillStyle = hoverFillColor;
ctx.fill();
ctx.globalAlpha = 1;
}
drawCircle(cx, cy, squareSize);
ctx.strokeStyle = borderColor;
ctx.stroke();
}
}
} else {
const offsetX = ((gridOffset.current.x % squareSize) + squareSize) % squareSize;
const offsetY = ((gridOffset.current.y % squareSize) + squareSize) % squareSize;
const cols = Math.ceil(canvas.width / squareSize) + 3;
const rows = Math.ceil(canvas.height / squareSize) + 3;
for (let col = -2; col < cols; col++) {
for (let row = -2; row < rows; row++) {
const sx = col * squareSize + offsetX;
const sy = row * squareSize + offsetY;
const cellKey = `${col},${row}`;
const alpha = cellOpacities.current.get(cellKey);
if (alpha) {
ctx.globalAlpha = alpha;
ctx.fillStyle = hoverFillColor;
ctx.fillRect(sx, sy, squareSize, squareSize);
ctx.globalAlpha = 1;
}
ctx.strokeStyle = borderColor;
ctx.strokeRect(sx, sy, squareSize, squareSize);
}
}
}
const gradient = ctx.createRadialGradient(
canvas.width / 2,
canvas.height / 2,
0,
canvas.width / 2,
canvas.height / 2,
Math.sqrt(canvas.width ** 2 + canvas.height ** 2) / 2
);
gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
};
const updateAnimation = () => {
const effectiveSpeed = Math.max(speed, 0.1);
const wrapX = isHex ? hexHoriz * 2 : squareSize;
const wrapY = isHex ? hexVert : isTri ? squareSize * 2 : squareSize;
switch (direction) {
case 'right':
gridOffset.current.x = (gridOffset.current.x - effectiveSpeed + wrapX) % wrapX;
break;
case 'left':
gridOffset.current.x = (gridOffset.current.x + effectiveSpeed + wrapX) % wrapX;
break;
case 'up':
gridOffset.current.y = (gridOffset.current.y + effectiveSpeed + wrapY) % wrapY;
break;
case 'down':
gridOffset.current.y = (gridOffset.current.y - effectiveSpeed + wrapY) % wrapY;
break;
case 'diagonal':
gridOffset.current.x = (gridOffset.current.x - effectiveSpeed + wrapX) % wrapX;
gridOffset.current.y = (gridOffset.current.y - effectiveSpeed + wrapY) % wrapY;
break;
default:
break;
}
updateCellOpacities();
drawGrid();
requestRef.current = requestAnimationFrame(updateAnimation);
};
const updateCellOpacities = () => {
const targets = new Map();
if (hoveredSquare.current) {
targets.set(`${hoveredSquare.current.x},${hoveredSquare.current.y}`, 1);
}
if (hoverTrailAmount > 0) {
for (let i = 0; i < trailCells.current.length; i++) {
const t = trailCells.current[i];
const key = `${t.x},${t.y}`;
if (!targets.has(key)) {
targets.set(key, (trailCells.current.length - i) / (trailCells.current.length + 1));
}
}
}
for (const [key] of targets) {
if (!cellOpacities.current.has(key)) {
cellOpacities.current.set(key, 0);
}
}
for (const [key, opacity] of cellOpacities.current) {
const target = targets.get(key) || 0;
const next = opacity + (target - opacity) * 0.15;
if (next < 0.005) {
cellOpacities.current.delete(key);
} else {
cellOpacities.current.set(key, next);
}
}
};
const handleMouseMove = event => {
const rect = canvas.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
if (isHex) {
const colShift = Math.floor(gridOffset.current.x / hexHoriz);
const offsetX = ((gridOffset.current.x % hexHoriz) + hexHoriz) % hexHoriz;
const offsetY = ((gridOffset.current.y % hexVert) + hexVert) % hexVert;
const adjustedX = mouseX - offsetX;
const adjustedY = mouseY - offsetY;
const col = Math.round(adjustedX / hexHoriz);
const rowOffset = (col + colShift) % 2 !== 0 ? hexVert / 2 : 0;
const row = Math.round((adjustedY - rowOffset) / hexVert);
if (
!hoveredSquare.current ||
hoveredSquare.current.x !== col ||
hoveredSquare.current.y !== row
) {
if (hoveredSquare.current && hoverTrailAmount > 0) {
trailCells.current.unshift({ ...hoveredSquare.current });
if (trailCells.current.length > hoverTrailAmount) trailCells.current.length = hoverTrailAmount;
}
hoveredSquare.current = { x: col, y: row };
}
} else if (isTri) {
const halfW = squareSize / 2;
const offsetX = ((gridOffset.current.x % halfW) + halfW) % halfW;
const offsetY = ((gridOffset.current.y % squareSize) + squareSize) % squareSize;
const adjustedX = mouseX - offsetX;
const adjustedY = mouseY - offsetY;
const col = Math.round(adjustedX / halfW);
const row = Math.floor(adjustedY / squareSize);
if (
!hoveredSquare.current ||
hoveredSquare.current.x !== col ||
hoveredSquare.current.y !== row
) {
if (hoveredSquare.current && hoverTrailAmount > 0) {
trailCells.current.unshift({ ...hoveredSquare.current });
if (trailCells.current.length > hoverTrailAmount) trailCells.current.length = hoverTrailAmount;
}
hoveredSquare.current = { x: col, y: row };
}
} else if (shape === 'circle') {
const offsetX = ((gridOffset.current.x % squareSize) + squareSize) % squareSize;
const offsetY = ((gridOffset.current.y % squareSize) + squareSize) % squareSize;
const adjustedX = mouseX - offsetX;
const adjustedY = mouseY - offsetY;
const col = Math.round(adjustedX / squareSize);
const row = Math.round(adjustedY / squareSize);
if (
!hoveredSquare.current ||
hoveredSquare.current.x !== col ||
hoveredSquare.current.y !== row
) {
if (hoveredSquare.current && hoverTrailAmount > 0) {
trailCells.current.unshift({ ...hoveredSquare.current });
if (trailCells.current.length > hoverTrailAmount) trailCells.current.length = hoverTrailAmount;
}
hoveredSquare.current = { x: col, y: row };
}
} else {
const offsetX = ((gridOffset.current.x % squareSize) + squareSize) % squareSize;
const offsetY = ((gridOffset.current.y % squareSize) + squareSize) % squareSize;
const adjustedX = mouseX - offsetX;
const adjustedY = mouseY - offsetY;
const col = Math.floor(adjustedX / squareSize);
const row = Math.floor(adjustedY / squareSize);
if (
!hoveredSquare.current ||
hoveredSquare.current.x !== col ||
hoveredSquare.current.y !== row
) {
if (hoveredSquare.current && hoverTrailAmount > 0) {
trailCells.current.unshift({ ...hoveredSquare.current });
if (trailCells.current.length > hoverTrailAmount) trailCells.current.length = hoverTrailAmount;
}
hoveredSquare.current = { x: col, y: row };
}
}
};
const handleMouseLeave = () => {
if (hoveredSquare.current && hoverTrailAmount > 0) {
trailCells.current.unshift({ ...hoveredSquare.current });
if (trailCells.current.length > hoverTrailAmount) trailCells.current.length = hoverTrailAmount;
}
hoveredSquare.current = null;
};
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mouseleave', handleMouseLeave);
requestRef.current = requestAnimationFrame(updateAnimation);
return () => {
window.removeEventListener('resize', resizeCanvas);
cancelAnimationFrame(requestRef.current);
canvas.removeEventListener('mousemove', handleMouseMove);
canvas.removeEventListener('mouseleave', handleMouseLeave);
};
}, [direction, speed, borderColor, hoverFillColor, squareSize, shape, hoverTrailAmount]);
return <canvas ref={canvasRef} className={`shapegrid-canvas ${className}`}></canvas>;
};
export default ShapeGrid;
Component CSS
.shapegrid-canvas {
width: 100%;
height: 100%;
border: none;
display: block;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: liquid chrome code: ## Integrate the <LiquidChrome /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: LiquidChrome
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import LiquidChrome from './LiquidChrome';
<div style={{ width: '100%', height: '600px', position: 'relative' }}>
<LiquidChrome
baseColor={[0.1, 0.1, 0.1]}
speed={1}
amplitude={0.6}
interactive={true}
/>
</div>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| baseColor | RGB array (number[3]) | [0.1, 0.1, 0.1] | Base color of the component. Specify as an RGB array. |
| speed | number | 1.0 | Animation speed multiplier. |
| amplitude | number | 0.6 | Amplitude of the distortion. |
| frequencyX | number | 2.5 | Frequency modifier for the x distortion. |
| frequencyY | number | 1.5 | Frequency modifier for the y distortion. |
| interactive | boolean | true | Enable mouse/touch interaction. |
Full Component Source
import { useRef, useEffect } from 'react';
import { Renderer, Program, Mesh, Triangle } from 'ogl';
import './LiquidChrome.css';
export const LiquidChrome = ({
baseColor = [0.1, 0.1, 0.1],
speed = 0.2,
amplitude = 0.3,
frequencyX = 3,
frequencyY = 3,
interactive = true,
...props
}) => {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const container = containerRef.current;
const renderer = new Renderer({ antialias: true });
const gl = renderer.gl;
gl.clearColor(1, 1, 1, 1);
const vertexShader = `
attribute vec2 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0.0, 1.0);
}
`;
const fragmentShader = `
precision highp float;
uniform float uTime;
uniform vec3 uResolution;
uniform vec3 uBaseColor;
uniform float uAmplitude;
uniform float uFrequencyX;
uniform float uFrequencyY;
uniform vec2 uMouse;
varying vec2 vUv;
vec4 renderImage(vec2 uvCoord) {
vec2 fragCoord = uvCoord * uResolution.xy;
vec2 uv = (2.0 * fragCoord - uResolution.xy) / min(uResolution.x, uResolution.y);
for (float i = 1.0; i < 10.0; i++){
uv.x += uAmplitude / i * cos(i * uFrequencyX * uv.y + uTime + uMouse.x * 3.14159);
uv.y += uAmplitude / i * cos(i * uFrequencyY * uv.x + uTime + uMouse.y * 3.14159);
}
vec2 diff = (uvCoord - uMouse);
float dist = length(diff);
float falloff = exp(-dist * 20.0);
float ripple = sin(10.0 * dist - uTime * 2.0) * 0.03;
uv += (diff / (dist + 0.0001)) * ripple * falloff;
vec3 color = uBaseColor / abs(sin(uTime - uv.y - uv.x));
return vec4(color, 1.0);
}
void main() {
vec4 col = vec4(0.0);
int samples = 0;
for (int i = -1; i <= 1; i++){
for (int j = -1; j <= 1; j++){
vec2 offset = vec2(float(i), float(j)) * (1.0 / min(uResolution.x, uResolution.y));
col += renderImage(vUv + offset);
samples++;
}
}
gl_FragColor = col / float(samples);
}
`;
const geometry = new Triangle(gl);
const program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
uTime: { value: 0 },
uResolution: {
value: new Float32Array([gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height])
},
uBaseColor: { value: new Float32Array(baseColor) },
uAmplitude: { value: amplitude },
uFrequencyX: { value: frequencyX },
uFrequencyY: { value: frequencyY },
uMouse: { value: new Float32Array([0, 0]) }
}
});
const mesh = new Mesh(gl, { geometry, program });
function resize() {
const scale = 1;
renderer.setSize(container.offsetWidth * scale, container.offsetHeight * scale);
const resUniform = program.uniforms.uResolution.value;
resUniform[0] = gl.canvas.width;
resUniform[1] = gl.canvas.height;
resUniform[2] = gl.canvas.width / gl.canvas.height;
}
window.addEventListener('resize', resize);
resize();
function handleMouseMove(event) {
const rect = container.getBoundingClientRect();
const x = (event.clientX - rect.left) / rect.width;
const y = 1 - (event.clientY - rect.top) / rect.height;
const mouseUniform = program.uniforms.uMouse.value;
mouseUniform[0] = x;
mouseUniform[1] = y;
}
function handleTouchMove(event) {
if (event.touches.length > 0) {
const touch = event.touches[0];
const rect = container.getBoundingClientRect();
const x = (touch.clientX - rect.left) / rect.width;
const y = 1 - (touch.clientY - rect.top) / rect.height;
const mouseUniform = program.uniforms.uMouse.value;
mouseUniform[0] = x;
mouseUniform[1] = y;
}
}
if (interactive) {
container.addEventListener('mousemove', handleMouseMove);
container.addEventListener('touchmove', handleTouchMove);
}
let animationId;
function update(t) {
animationId = requestAnimationFrame(update);
program.uniforms.uTime.value = t * 0.001 * speed;
renderer.render({ scene: mesh });
}
animationId = requestAnimationFrame(update);
container.appendChild(gl.canvas);
return () => {
cancelAnimationFrame(animationId);
window.removeEventListener('resize', resize);
if (interactive) {
container.removeEventListener('mousemove', handleMouseMove);
container.removeEventListener('touchmove', handleTouchMove);
}
if (gl.canvas.parentElement) {
gl.canvas.parentElement.removeChild(gl.canvas);
}
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, [baseColor, speed, amplitude, frequencyX, frequencyY, interactive]);
return <div ref={containerRef} className="liquidChrome-container" {...props} />;
};
export default LiquidChrome;
Component CSS
.liquidChrome-container {
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.
name: balatro code: ## Integrate the <Balatro /> component from React Bits
You are helping integrate an open-source React component into an existing application.
Component: Balatro
Variant: JavaScript + CSS
Dependencies: ogl
Usage Example
import Balatro from './Balatro';
<Balatro
isRotate={false}
mouseInteraction={true}
pixelFilter={700}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| spinRotation | number | -2.0 | Base rotation amount affecting the shader effect. |
| spinSpeed | number | 7.0 | Speed of the spin animation. |
| offset | [number, number] | [0.0, 0.0] | Offset for the shader effect. |
| color1 | string | "#DE443B" | Primary color in HEX format. |
| color2 | string | "#006BB4" | Secondary color in HEX format. |
| color3 | string | "#162325" | Tertiary color in HEX format. |
| contrast | number | 3.5 | Contrast value affecting color blending. |
| lighting | number | 0.4 | Lighting factor affecting brightness. |
| spinAmount | number | 0.25 | Amount of spin influence based on UV length. |
| pixelFilter | number | 745.0 | Pixel filter factor determining pixelation. |
| spinEase | number | 1.0 | Ease factor for spin. |
| isRotate | boolean | false | Determines if the shader rotates continuously. |
| mouseInteraction | boolean | true | Enables or disables mouse interaction for rotation. |
Full Component Source
import { Renderer, Program, Mesh, Triangle } from 'ogl';
import { useEffect, useRef } from 'react';
import './Balatro.css';
function hexToVec4(hex) {
let hexStr = hex.replace('#', '');
let r = 0,
g = 0,
b = 0,
a = 1;
if (hexStr.length === 6) {
r = parseInt(hexStr.slice(0, 2), 16) / 255;
g = parseInt(hexStr.slice(2, 4), 16) / 255;
b = parseInt(hexStr.slice(4, 6), 16) / 255;
} else if (hexStr.length === 8) {
r = parseInt(hexStr.slice(0, 2), 16) / 255;
g = parseInt(hexStr.slice(2, 4), 16) / 255;
b = parseInt(hexStr.slice(4, 6), 16) / 255;
a = parseInt(hexStr.slice(6, 8), 16) / 255;
}
return [r, g, b, a];
}
const vertexShader = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
const fragmentShader = `
precision highp float;
#define PI 3.14159265359
uniform float iTime;
uniform vec3 iResolution;
uniform float uSpinRotation;
uniform float uSpinSpeed;
uniform vec2 uOffset;
uniform vec4 uColor1;
uniform vec4 uColor2;
uniform vec4 uColor3;
uniform float uContrast;
uniform float uLighting;
uniform float uSpinAmount;
uniform float uPixelFilter;
uniform float uSpinEase;
uniform bool uIsRotate;
uniform vec2 uMouse;
varying vec2 vUv;
vec4 effect(vec2 screenSize, vec2 screen_coords) {
float pixel_size = length(screenSize.xy) / uPixelFilter;
vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;
float uv_len = length(uv);
float speed = (uSpinRotation * uSpinEase * 0.2);
if(uIsRotate){
speed = iTime * speed;
}
speed += 302.2;
float mouseInfluence = (uMouse.x * 2.0 - 1.0);
speed += mouseInfluence * 0.1;
float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));
vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;
uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);
uv *= 30.0;
float baseSpeed = iTime * uSpinSpeed;
speed = baseSpeed + mouseInfluence * 2.0;
vec2 uv2 = vec2(uv.x + uv.y);
for(int i = 0; i < 5; i++) {
uv2 += sin(max(uv.x, uv.y)) + uv;
uv += 0.5 * vec2(
cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),
sin(uv2.x - 0.113 * speed)
);
uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);
}
float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);
float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));
float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));
float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));
float c3p = 1.0 - min(1.0, c1p + c2p);
float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);
return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;
}
void main() {
vec2 uv = vUv * iResolution.xy;
gl_FragColor = effect(iResolution.xy, uv);
}
`;
export default function Balatro({
spinRotation = -2.0,
spinSpeed = 7.0,
offset = [0.0, 0.0],
color1 = '#DE443B',
color2 = '#006BB4',
color3 = '#162325',
contrast = 3.5,
lighting = 0.4,
spinAmount = 0.25,
pixelFilter = 745.0,
spinEase = 1.0,
isRotate = false,
mouseInteraction = true
}) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const container = containerRef.current;
const renderer = new Renderer();
const gl = renderer.gl;
gl.clearColor(0, 0, 0, 1);
let program;
function resize() {
renderer.setSize(container.offsetWidth, container.offsetHeight);
if (program) {
program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];
}
}
window.addEventListener('resize', resize);
resize();
const geometry = new Triangle(gl);
program = new Program(gl, {
vertex: vertexShader,
fragment: fragmentShader,
uniforms: {
iTime: { value: 0 },
iResolution: {
value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]
},
uSpinRotation: { value: spinRotation },
uSpinSpeed: { value: spinSpeed },
uOffset: { value: offset },
uColor1: { value: hexToVec4(color1) },
uColor2: { value: hexToVec4(color2) },
uColor3: { value: hexToVec4(color3) },
uContrast: { value: contrast },
uLighting: { value: lighting },
uSpinAmount: { value: spinAmount },
uPixelFilter: { value: pixelFilter },
uSpinEase: { value: spinEase },
uIsRotate: { value: isRotate },
uMouse: { value: [0.5, 0.5] }
}
});
const mesh = new Mesh(gl, { geometry, program });
let animationFrameId;
function update(time) {
animationFrameId = requestAnimationFrame(update);
program.uniforms.iTime.value = time * 0.001;
renderer.render({ scene: mesh });
}
animationFrameId = requestAnimationFrame(update);
container.appendChild(gl.canvas);
function handleMouseMove(e) {
if (!mouseInteraction) return;
const rect = container.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = 1.0 - (e.clientY - rect.top) / rect.height;
program.uniforms.uMouse.value = [x, y];
}
container.addEventListener('mousemove', handleMouseMove);
return () => {
cancelAnimationFrame(animationFrameId);
window.removeEventListener('resize', resize);
container.removeEventListener('mousemove', handleMouseMove);
container.removeChild(gl.canvas);
gl.getExtension('WEBGL_lose_context')?.loseContext();
};
}, [
spinRotation,
spinSpeed,
offset,
color1,
color2,
color3,
contrast,
lighting,
spinAmount,
pixelFilter,
spinEase,
isRotate,
mouseInteraction,
containerRef
]);
return <div ref={containerRef} className="balatro-container" />;
}
Component CSS
.balatro-container {
width: 100%;
height: 100%;
}
Integration Instructions
- Install any listed dependencies.
- Copy the component source into the appropriate directory in the project.
- Import the CSS file alongside the component.
- Import and render the component using the usage example above as a starting point.
- Adjust props as needed for the specific use case — refer to the props table for all available options.