Night Sky
A dynamic night sky background with twinkling stars, customizable density, and content layered on top.
Stars Above You
night-sky-background-preview.jsx
import NightSkyBackground from "@/components/backgrounds/night-sky-background/night-sky-background";
const NightSkyBackgroundPreview = () => {
return (
<NightSkyBackground
style={{
width: "100%",
height: "100%"
}}
wrapperProps={{
style: {
height: "100%",
display: "grid",
placeItems: "center",
color: "#fff",
}
}}
>
<h1>Stars Above You</h1>
</NightSkyBackground>
);
};
export default NightSkyBackgroundPreview; Installation
Run the command from your project root directory (the folder that contains package.json).
Terminal / Console
npx mosaicui-cli@latest backgrounds/night-sky-background 1. Copy the component file
Create a new file called night-sky-background.jsx
in your reusable components folder (for example
/src/components/) and paste the following
code into it.
night-sky-background.jsx
import { useState, useRef, useEffect, useLayoutEffect, useMemo, useCallback } from "react";
import styles from "./night-sky-background.module.css";
const random = (n = 1) => {
return Math.random() * n;
};
const NightSkyBackground = (props) => {
const {
density = 1,
children,
className,
wrapperProps = {},
wrapperTagName = "div",
...rest
} = props;
const {
className: wrapperClassName = "",
...restWrapperProps
} = wrapperProps;
const Wrapper = wrapperTagName || "div";
const spaceColor = "rgb(0, 0, 0)";
const starsCount = useMemo(() => (
Math.min(Math.max(100, 1000 * density), 10000)
), [density]);
const containerRef = useRef();
const canvasRef = useRef(null);
const rafId = useRef(null);
const [mounted, setMounted] = useState(false);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const { devicePixelRatio, canvasWidth, canvasHeight } = useMemo(() => {
const devicePixelRatio = Math.max(1, globalThis.devicePixelRatio || 1);
return {
devicePixelRatio,
canvasWidth: width * devicePixelRatio,
canvasHeight: height * devicePixelRatio,
};
}, [width, height]);
const ctx = useMemo(() => {
return canvasRef.current?.getContext("2d");
}, [canvasRef.current]);
const stars = useMemo(() => {
return Array.from({
length: starsCount,
}).map(() => ({
x: random(width),
y: random(height),
radius: random(),
color: (
random() < 0.8 ? [255, 255, 255] : (
[
Math.floor(random(255)),
Math.floor(random(255)),
Math.floor(random(255)),
]
)
),
twinkingRate: random() * 0.01,
opacity: random(),
shouldTwinkle: random() < 0.5,
}))
}, [starsCount, width, height]);
const render = useCallback(() => {
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
ctx.fillStyle = spaceColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.save();
for (const star of stars) {
let starOpacity = star.opacity;
if (star.shouldTwinkle) {
star.opacity = (star.opacity + star.twinkingRate) % 100;
starOpacity = Math.cos(star.opacity);
}
ctx.beginPath();
ctx.ellipse(
star.x,
star.y,
star.radius,
star.radius,
0,
0,
360,
false
);
ctx.closePath();
ctx.fillStyle = `rgba(${[...star.color, starOpacity].join(", ")})`;
ctx.fill();
}
ctx.restore();
rafId.current = requestAnimationFrame(render);
}, [
ctx,
devicePixelRatio,
width,
height,
canvasWidth,
canvasHeight,
stars,
]);
useEffect(() => {
const updateContainerDimensions = () => {
const {
width,
height,
} = containerRef.current.getBoundingClientRect();
setWidth(width);
setHeight(height);
};
const resizeObserver = new ResizeObserver(updateContainerDimensions);
resizeObserver.observe(containerRef.current);
updateContainerDimensions();
setMounted(true);
return () => {
resizeObserver.disconnect();
}
}, []);
useLayoutEffect(() => {
if (!mounted) return;
render();
return () => {
cancelAnimationFrame(rafId.current);
};
}, [mounted, render]);
return (
<div
{...rest}
ref={containerRef}
className={[
className,
styles["night-sky-background"]
].join(" ")}
>
<canvas
aria-hidden={true}
width={canvasWidth}
height={canvasHeight}
ref={canvasRef}
/>
<Wrapper
{...restWrapperProps}
className={[
wrapperClassName,
styles["wrapper"]
].join(" ")}
>
{children}
</Wrapper>
</div>
);
};
export default NightSkyBackground; 2. Copy the CSS module file
In the same folder, create a file called night-sky-background.module.css and paste the following
CSS.
night-sky-background.module.css
.night-sky-background {
position: relative;
background: rgb(0, 0, 0);
canvas {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.wrapper {
position: relative;
z-index: 3;
}
} Props
Configure the component with the following props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| density | number | No | 1 | Controls the number of stars rendered in the background. Min: 0.1, Max: 10. Higher = denser sky. |
| children | ReactNode | Yes | — | Content displayed on top of the night sky background. |
| className | string | No | — | Additional CSS classes applied to the main container. |
| wrapperProps | React.HTMLAttributes<any> | No | — | Extra props passed to the wrapper element containing the children. |
| wrapperTagName | string | No | "div" | HTML tag used as the wrapper element around the children. |