类型:3D · 分类:Three.js · 收录:2026-09-16 21:09:01 · 源码:会员可见
付费可阅读内容 · 可复制
# Structure Flow
Thirteen authored Three.js field studies collected as one family, spanning particle domes, horizons, orbital systems, matrices, topology, fluid fields, embers, and vortexes.
> 来源:ThreeUI Community · https://threeui.com · MIT License(Meng To / Design+Code)
> 组件页:https://threeui.com/component/structure-flow · 预览动画:https://threeui.com/previews/structure-flow.webm
> 分类:Three.js
> 包含变体(12):emerald-horizon、orbital-sphere、dot-matrix、expanse-field、logic-core、dimensional-field、data-field、topology-field、nebula、fluid-field、ember-storm、flux-vortex
/* ===== src/shaders/structure-flow/StructureFlowBackground.tsx (tsx) ===== */
import { useEffect, useRef } from "react";
import { createStructureFlowRenderer, STRUCTURE_FLOW_DEFAULTS, type StructureFlowOptions } from "./structureFlowRenderer";
export type StructureFlowBackgroundProps = Partial<StructureFlowOptions> & { className?: string };
export function StructureFlowBackground({ className = "", ...props }: StructureFlowBackgroundProps) {
const hostRef = useRef<HTMLDivElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const optionsRef = useRef({ ...STRUCTURE_FLOW_DEFAULTS, ...props }); optionsRef.current = { ...STRUCTURE_FLOW_DEFAULTS, ...props };
useEffect(() => {
const host = hostRef.current, canvas = canvasRef.current; if (!host || !canvas) return undefined;
const renderer = createStructureFlowRenderer(canvas, () => optionsRef.current); let frame = 0, visible = true;
const resize = () => { const bounds = host.getBoundingClientRect(); renderer.resize(bounds.width, bounds.height); renderer.render(); };
const tick = () => { renderer.render(); frame = visible && !document.hidden ? requestAnimationFrame(tick) : 0; };
const resizeObserver = new ResizeObserver(resize); const intersection = new IntersectionObserver(([entry]) => { visible = entry?.isIntersecting ?? true; if (visible && !frame) frame = requestAnimationFrame(tick); if (!visible && frame) cancelAnimationFrame(frame), frame = 0; });
resizeObserver.observe(host); intersection.observe(host); resize(); frame = requestAnimationFrame(tick);
return () => { if (frame) cancelAnimationFrame(frame); resizeObserver.disconnect(); intersection.disconnect(); renderer.dispose(); };
}, []);
const options = optionsRef.current;
const mask = `linear-gradient(to bottom, transparent ${options.maskStart * 100}%, black ${options.maskSolid * 100}%, black 100%)`;
return <div ref={hostRef} className={`threeui-background structure-flow${className ? ` ${className}` : ""}`} style={{ opacity: 0.8, WebkitMaskImage: mask, maskImage: mask }}><canvas ref={canvasRef} /></div>;
}
/* ===== src/shaders/structure-flow/StructureFlowCollection.tsx (tsx) ===== */
import { lazy, Suspense } from "react";
import type { DotMatrixBackgroundProps } from "../dot-matrix/DotMatrixBackground";
import type { EmeraldHorizonBackgroundProps } from "../emerald-horizon/EmeraldHorizonBackground";
import type { NeuformBatchEffectProps } from "../neuform-isolated/NeuformBatchEffects";
import type { NeuformCraftEffectProps } from "../neuform-isolated/NeuformCraftEffects";
import type { NeuformIsolatedEffectProps } from "../neuform-isolated/NeuformIsolatedEffects";
import type { OrbitalSphereBackgroundProps } from "../orbital-sphere/OrbitalSphereBackground";
import type { StructureFlowBackgroundProps } from "./StructureFlowBackground";
export const STRUCTURE_FLOW_VARIANTS = [
"structure-flow",
"emerald-horizon",
"orbital-sphere",
"dot-matrix",
"expanse-field",
"logic-core",
"dimensional-field",
"data-field",
"topology-field",
"nebula",
"fluid-field",
"ember-storm",
"flux-vortex",
] as const;
export type StructureFlowVariant = (typeof STRUCTURE_FLOW_VARIANTS)[number];
type StructureVariantProps = StructureFlowBackgroundProps & { variant?: "structure-flow" };
type EmeraldVariantProps = EmeraldHorizonBackgroundProps & { variant: "emerald-horizon" };
type OrbitalVariantProps = OrbitalSphereBackgroundProps & { variant: "orbital-sphere" };
type DotMatrixVariantProps = DotMatrixBackgroundProps & { variant: "dot-matrix" };
type IsolatedVariantProps = NeuformIsolatedEffectProps & {
variant: "expanse-field" | "logic-core" | "dimensional-field" | "data-field" | "topology-field";
};
type CraftVariantProps = NeuformCraftEffectProps & {
variant: "nebula" | "fluid-field" | "ember-storm";
};
type FluxVariantProps = NeuformBatchEffectProps & { variant: "flux-vortex" };
export type StructureFlowCollectionProps =
| StructureVariantProps
| EmeraldVariantProps
| OrbitalVariantProps
| DotMatrixVariantProps
| IsolatedVariantProps
| CraftVariantProps
| FluxVariantProps;
const StructureVariant = lazy(() =>
import("./StructureFlowBackground").then((module) => ({ default: module.StructureFlowBackground })),
);
const EmeraldVariant = lazy(() =>
import("../emerald-horizon/EmeraldHorizonBackground").then((module) => ({ default: module.EmeraldHorizonBackground })),
);
const OrbitalVariant = lazy(() =>
import("../orbital-sphere/OrbitalSphereBackground").then((module) => ({ default: module.OrbitalSphereBackground })),
);
const DotMatrixVariant = lazy(() =>
import("../dot-matrix/DotMatrixBackground").then((module) => ({ default: module.DotMatrixBackground })),
);
const ExpanseVariant = lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.ExpanseField })),
);
const LogicCoreVariant = lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.LogicCoreField })),
);
const DimensionalVariant = lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.DimensionalField })),
);
const DataVariant = lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.DataField })),
);
const TopologyVariant = lazy(() =>
import("../neuform-isolated/NeuformIsolatedEffects").then((module) => ({ default: module.TopologyField })),
);
const NebulaVariant = lazy(() =>
import("../neuform-isolated/NeuformCraftEffects").then((module) => ({ default: module.NebulaBackground })),
);
const FluidVariant = lazy(() =>
import("../neuform-isolated/NeuformCraftEffects").then((module) => ({ default: module.FluidFieldBackground })),
);
const EmberVariant = lazy(() =>
import("../neuform-isolated/NeuformCraftEffects").then((module) => ({ default: module.EmberStorm })),
);
const FluxVariant = lazy(() =>
import("../neuform-isolated/NeuformBatchEffects").then((module) => ({ default: module.FluxVortex })),
);
const FALLBACK = <div className="threeui-background" style={{ background: "#050607" }} />;
export function StructureFlowCollection(props: StructureFlowCollectionProps) {
if (props.variant === "emerald-horizon") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><EmeraldVariant {...variantProps} /></Suspense>;
}
if (props.variant === "orbital-sphere") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><OrbitalVariant {...variantProps} /></Suspense>;
}
if (props.variant === "dot-matrix") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><DotMatrixVariant {...variantProps} /></Suspense>;
}
if (props.variant === "expanse-field") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><ExpanseVariant {...variantProps} /></Suspense>;
}
if (props.variant === "logic-core") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><LogicCoreVariant {...variantProps} /></Suspense>;
}
if (props.variant === "dimensional-field") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><DimensionalVariant {...variantProps} /></Suspense>;
}
if (props.variant === "data-field") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><DataVariant {...variantProps} /></Suspense>;
}
if (props.variant === "topology-field") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><TopologyVariant {...variantProps} /></Suspense>;
}
if (props.variant === "nebula") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><NebulaVariant {...variantProps} /></Suspense>;
}
if (props.variant === "fluid-field") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><FluidVariant {...variantProps} /></Suspense>;
}
if (props.variant === "ember-storm") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><EmberVariant {...variantProps} /></Suspense>;
}
if (props.variant === "flux-vortex") {
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><FluxVariant {...variantProps} /></Suspense>;
}
const { variant: _variant, ...variantProps } = props;
return <Suspense fallback={FALLBACK}><StructureVariant {...variantProps} /></Suspense>;
}
/* ===== src/shaders/structure-flow/structureFlowRenderer.ts (typescript) ===== */
import * as THREE from "three128";
export type StructureFlowOptions = { speed: number; pointSize: number; opacity: number; maskStart: number; maskSolid: number };
export const STRUCTURE_FLOW_DEFAULTS: StructureFlowOptions = { speed: 1, pointSize: 0.08, opacity: 0.4, maskStart: 0.2, maskSolid: 0.5 };
export function createStructureFlowRenderer(canvas: HTMLCanvasElement, getOptions: () => StructureFlowOptions) {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, 1, 0.1, 1000);
camera.position.z = 30;
camera.position.y = 5;
const renderer = new THREE.WebGLRenderer({ canvas, alpha: true, antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const geometry = new THREE.BufferGeometry();
const count = 15000;
const positions = new Float32Array(count * 3);
const radius = 25;
for (let index = 0; index < count; index += 1) {
const u = Math.random();
Math.random(); // The canonical renderer sampled v even though it did not use it.
const theta = u * 2 * Math.PI;
const phi = Math.acos(Math.random() * 0.8 + 0.2);
positions[index * 3] = radius * Math.sin(phi) * Math.cos(theta);
positions[index * 3 + 1] = radius * Math.cos(phi) - 20;
positions[index * 3 + 2] = radius * Math.sin(phi) * Math.sin(theta);
}
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
const material = new THREE.PointsMaterial({ size: 0.08, color: 0xffffff, transparent: true, opacity: 0.4, blending: THREE.AdditiveBlending, depthWrite: false });
const particles = new THREE.Points(geometry, material);
scene.add(particles);
return {
resize(width: number, height: number) {
camera.aspect = width / Math.max(1, height);
camera.updateProjectionMatrix();
renderer.setSize(width, height, false);
},
render() {
const options = getOptions();
particles.rotation.y += 0.0008 * options.speed;
particles.rotation.z += 0.0002 * options.speed;
material.size = options.pointSize;
material.opacity = options.opacity;
renderer.render(scene, camera);
},
dispose() { geometry.dispose(); material.dispose(); renderer.dispose(); },
};
}
/* ===== src/shaders/structure-flow/three128.d.ts (typescript) ===== */
declare module "three128" {
const THREE: typeof import("three");
export = THREE;
}
