PrismLoader
PreviewCode
// preview
Color from
#8e8e97
Color mid
#dbc2ff
Color to
#ffffff
// install
pnpmnpmyarnbun
npx shadcn@latest add "https://designpass.dev/r/PrismLoader-TS-TW.json"Install the PrismLoader component from DesignPass into this project by running:
npx shadcn@latest add "https://designpass.dev/r/PrismLoader-TS-TW.json"
If the project has no components.json yet, run `npx shadcn@latest init` first.
Then show me a minimal usage example.// source
tsjs
twcss
/*!
* PrismLoader, a DesignPass.dev component by Ernest Liu (ernestliu.com)
* Docs & live playground: https://designpass.dev/components/prism-loader
* MIT licensed, keep this notice in copies and adaptations.
*/
"use client";
import React, { useEffect, useRef, type CSSProperties } from "react";
export interface PrismLoaderProps {
/** Overall footprint of the loader (px). */
size?: number;
/** Seconds for one full revolution of the prism. */
speed?: number;
/** Tint of the face turned furthest away (dim base). */
colorFrom?: string;
/** Tint of the mid face (accent). */
colorMid?: string;
/** Edge glow and nearest-face tint (bright peak). */
colorTo?: string;
className?: string;
style?: CSSProperties;
}
/**
* A glass triangular prism revolving in real CSS 3D space, the volumetric
* sibling of TriLoader. Three rectangular faces stand on a preserve-3d stage
* at 120-degree intervals under a perspective camera, each tinted with a stop
* of the gradient (dim, accent, bright) so the rotation reads as light moving
* around the volume rather than an opacity trick. One Web Animations API
* rotation drives the whole solid. Zero dependencies, honors
* prefers-reduced-motion.
*/
export default function PrismLoader({
size = 48,
speed = 2.2,
colorFrom = "#8e8e97",
colorMid = "#dbc2ff",
colorTo = "#ffffff",
className = "",
style,
}: PrismLoaderProps) {
const prismRef = useRef<HTMLSpanElement>(null);
useEffect(() => {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const prism = prismRef.current;
if (!prism) return;
// A fixed forward tilt exposes the top edge while yaw does the lap, so
// the prism reads as a gem turning on a jeweler's stand.
const spin = prism.animate(
[
{ transform: "rotateX(-18deg) rotateY(0deg)" },
{ transform: "rotateX(-18deg) rotateY(360deg)" },
],
{ duration: speed * 1000, iterations: Infinity, easing: "linear" },
);
return () => spin.cancel();
}, [speed]);
const width = size * 0.52;
const height = size * 0.6;
// Distance from the prism axis to each face of an equilateral cross-section.
const apothem = width / (2 * Math.sqrt(3));
const tints = [colorTo, colorMid, colorFrom];
return (
<span
role="status"
aria-label="Loading"
className={`inline-block shrink-0 ${className}`}
style={{ width: size, height: size, perspective: size * 4, ...style }}
>
<span
aria-hidden="true"
className="flex h-full w-full items-center justify-center"
style={{ transformStyle: "preserve-3d" }}
>
<span
ref={prismRef}
className="relative block"
style={{
width,
height,
transformStyle: "preserve-3d",
transform: "rotateX(-18deg) rotateY(0deg)",
willChange: "transform",
}}
>
{tints.map((tint, index) => (
<span
key={index}
className="absolute inset-0"
style={{
transform: `rotateY(${index * 120}deg) translateZ(${apothem}px)`,
backgroundColor: `color-mix(in srgb, ${tint} 40%, transparent)`,
border: `1.5px solid color-mix(in srgb, ${colorTo} 80%, transparent)`,
boxShadow: `inset 0 0 ${width * 0.5}px color-mix(in srgb, ${colorMid} 40%, transparent)`,
}}
/>
))}
</span>
</span>
</span>
);
}
// props
Need the license details? Read the component license.