ng-cw-v12
Version:
Angular UI Component Library
477 lines (452 loc) • 20.2 kB
JavaScript
import * as i0 from '@angular/core';
import { Component, ViewChild, Input, HostListener, NgModule } from '@angular/core';
import * as THREE from 'three';
import { CommonModule } from '@angular/common';
// ─── 顶点着色器 ───────────────────────────────────────────────────────
const vert = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 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 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] };
}
};
class LightRaysBackgroundComponent {
constructor(ngZone) {
this.ngZone = ngZone;
/** 光线发射源位置。可选项: 'top-center', 'top-left', 'top-right', 'right', 'left', 'bottom-center', 'bottom-right', 'bottom-left' */
this.ncRaysOrigin = 'top-center';
/** 射线颜色,十六进制格式 */
this.ncRaysColor = '#ffffff';
/** 射线动画速度(0.1-3) */
this.ncRaysSpeed = 1;
/** 光线扩散宽度(0.1-2) */
this.ncLightSpread = 1;
/** 光线的最大长度/范围(0.5-3)*/
this.ncRayLength = 2;
/** 启用脉动(闪烁)动画效果 */
this._pulsating = false;
/** 射线从源头开始淡出的距离(0.5-2)*/
this.ncFadeDistance = 1.0;
/** 颜色饱和度(0-1)*/
this.ncSaturation = 1.0;
/** 让光线向鼠标光标方向旋转 */
this._followMouse = true;
/** 鼠标对光线的影响程度(0-1)*/
this.ncMouseInfluence = 0.1;
/** 给光线添加噪点/颗粒感(0-1)*/
this.ncNoiseAmount = 0.0;
/** 对光线应用波形扭曲(0-1)*/
this.ncDistortion = 0.0;
/** 背景颜色 */
this.ncBgColor = 'black';
this.renderer = null;
this.material = null;
this.scene = null;
this.camera = null;
this.rafId = null;
this.resizeRafId = null;
this.isVisible = true;
this.running = false;
this.mouse = { x: 0.5, y: 0.5 };
this.smoothMouse = { x: 0.5, y: 0.5 };
this._onVisibility = () => {
if (document.hidden) {
this.pause();
}
else if (this.isVisible) {
this.ngZone.runOutsideAngular(() => {
this.start();
});
}
};
this.loop = (t) => {
if (!this.running)
return;
if (this.material && this.renderer && this.scene && this.camera) {
this.material.uniforms['iTime'].value = t * 0.001;
if (this.ncFollowMouse && this.ncMouseInfluence > 0.0) {
const smoothing = 0.92;
this.smoothMouse.x = this.smoothMouse.x * smoothing + this.mouse.x * (1 - smoothing);
this.smoothMouse.y = this.smoothMouse.y * smoothing + this.mouse.y * (1 - smoothing);
this.material.uniforms['mousePos'].value.set(this.smoothMouse.x, this.smoothMouse.y);
}
try {
this.renderer.render(this.scene, this.camera);
}
catch (error) {
console.warn('WebGL rendering error:', error);
return;
}
}
this.rafId = requestAnimationFrame(this.loop);
};
}
set ncPulsating(val) {
this._pulsating = val !== null && val !== undefined && val !== false && val !== 'false';
}
get ncPulsating() {
return this._pulsating;
}
set ncFollowMouse(val) {
this._followMouse = val !== null && val !== undefined && val !== false && val !== 'false';
}
get ncFollowMouse() {
return this._followMouse;
}
ngOnInit() { }
ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => {
this.initWebGL();
});
}
ngOnDestroy() {
this.cleanup();
}
ngOnChanges(changes) {
if (!this.material)
return;
const u = this.material.uniforms;
if (changes['ncRaysColor']) {
u['raysColor'].value.set(this.ncRaysColor);
}
if (changes['ncRaysSpeed'])
u['raysSpeed'].value = this.ncRaysSpeed;
if (changes['ncLightSpread'])
u['lightSpread'].value = this.ncLightSpread;
if (changes['ncRayLength'])
u['rayLength'].value = this.ncRayLength;
if (changes['ncPulsating'])
u['pulsating'].value = this.ncPulsating ? 1.0 : 0.0;
if (changes['ncFadeDistance'])
u['fadeDistance'].value = this.ncFadeDistance;
if (changes['ncSaturation'])
u['saturation'].value = this.ncSaturation;
if (changes['ncMouseInfluence'])
u['mouseInfluence'].value = this.ncMouseInfluence;
if (changes['ncNoiseAmount'])
u['noiseAmount'].value = this.ncNoiseAmount;
if (changes['ncDistortion'])
u['distortion'].value = this.ncDistortion;
if (changes['ncRaysOrigin']) {
this.ngZone.runOutsideAngular(() => {
this.updatePlacement();
});
}
}
initWebGL() {
const container = this.containerRef.nativeElement;
this.renderer = new THREE.WebGLRenderer({ alpha: true });
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
this.renderer.domElement.style.width = '100%';
this.renderer.domElement.style.height = '100%';
this.renderer.domElement.style.display = 'block';
container.appendChild(this.renderer.domElement);
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const geometry = new THREE.PlaneGeometry(2, 2);
this.material = new THREE.ShaderMaterial({
vertexShader: vert,
fragmentShader: frag,
uniforms: {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector2(1, 1) },
rayPos: { value: new THREE.Vector2(0, 0) },
rayDir: { value: new THREE.Vector2(0, 1) },
raysColor: { value: new THREE.Color(this.ncRaysColor) },
raysSpeed: { value: this.ncRaysSpeed },
lightSpread: { value: this.ncLightSpread },
rayLength: { value: this.ncRayLength },
pulsating: { value: this.ncPulsating ? 1.0 : 0.0 },
fadeDistance: { value: this.ncFadeDistance },
saturation: { value: this.ncSaturation },
mousePos: { value: new THREE.Vector2(0.5, 0.5) },
mouseInfluence: { value: this.ncMouseInfluence },
noiseAmount: { value: this.ncNoiseAmount },
distortion: { value: this.ncDistortion }
},
transparent: true,
depthWrite: false
});
const mesh = new THREE.Mesh(geometry, this.material);
this.scene.add(mesh);
this.setupResizeObserver(container);
this.setupIntersectionObserver(container);
// 初始化一次配置
this.updatePlacement();
document.addEventListener('visibilitychange', this._onVisibility);
}
updatePlacement() {
var _a;
if (!((_a = this.containerRef) === null || _a === void 0 ? void 0 : _a.nativeElement) || !this.renderer || !this.material)
return;
const container = this.containerRef.nativeElement;
const wCSS = container.clientWidth;
const hCSS = container.clientHeight;
if (wCSS === 0 || hCSS === 0)
return;
this.renderer.setSize(wCSS, hCSS, false); // false 因为我们使用 CSS 100% 缩放
const dpr = this.renderer.getPixelRatio();
const w = wCSS * dpr;
const h = hCSS * dpr;
this.material.uniforms['iResolution'].value.set(w, h);
const { anchor, dir } = getAnchorAndDir(this.ncRaysOrigin, w, h);
this.material.uniforms['rayPos'].value.set(anchor[0], anchor[1]);
this.material.uniforms['rayDir'].value.set(dir[0], dir[1]);
}
start() {
if (this.running)
return;
this.running = true;
this.rafId = requestAnimationFrame(this.loop);
}
pause() {
this.running = false;
if (this.rafId !== null) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
}
setupResizeObserver(container) {
this.resizeObserver = new ResizeObserver(() => {
if (!this.renderer)
return;
if (this.resizeRafId !== null)
cancelAnimationFrame(this.resizeRafId);
this.ngZone.runOutsideAngular(() => {
this.resizeRafId = requestAnimationFrame(() => {
this.updatePlacement();
this.resizeRafId = null;
});
});
});
this.resizeObserver.observe(container);
}
setupIntersectionObserver(container) {
this.intersectionObserver = new IntersectionObserver(entries => {
const entry = entries[0];
this.isVisible = entry.isIntersecting && entry.intersectionRatio > 0;
if (this.isVisible && !document.hidden) {
this.ngZone.runOutsideAngular(() => {
this.start();
});
}
else {
this.pause();
}
}, { threshold: [0, 0.01, 0.1] });
this.intersectionObserver.observe(container);
}
cleanup() {
var _a, _b;
this.pause();
if (this.resizeRafId !== null) {
cancelAnimationFrame(this.resizeRafId);
this.resizeRafId = null;
}
document.removeEventListener('visibilitychange', this._onVisibility);
try {
(_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
}
catch (e) {
void 0;
}
try {
(_b = this.intersectionObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
}
catch (e) {
void 0;
}
if (this.renderer) {
try {
const canvas = this.renderer.domElement;
this.renderer.dispose();
if (canvas && canvas.parentNode) {
canvas.parentNode.removeChild(canvas);
}
}
catch (error) {
console.warn('Error during WebGL cleanup:', error);
}
this.renderer = null;
}
if (this.material) {
this.material.dispose();
this.material = null;
}
this.scene = null;
this.camera = null;
}
onMouseMove(event) {
var _a;
if (!this.ncFollowMouse || !((_a = this.containerRef) === null || _a === void 0 ? void 0 : _a.nativeElement))
return;
const rect = this.containerRef.nativeElement.getBoundingClientRect();
const x = (event.clientX - rect.left) / rect.width;
const y = (event.clientY - rect.top) / rect.height;
this.mouse = { x, y };
}
}
LightRaysBackgroundComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: LightRaysBackgroundComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
LightRaysBackgroundComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.1.5", type: LightRaysBackgroundComponent, selector: "nc-light-rays-background", inputs: { ncRaysOrigin: "ncRaysOrigin", ncRaysColor: "ncRaysColor", ncRaysSpeed: "ncRaysSpeed", ncLightSpread: "ncLightSpread", ncRayLength: "ncRayLength", ncPulsating: "ncPulsating", ncFadeDistance: "ncFadeDistance", ncSaturation: "ncSaturation", ncFollowMouse: "ncFollowMouse", ncMouseInfluence: "ncMouseInfluence", ncNoiseAmount: "ncNoiseAmount", ncDistortion: "ncDistortion", ncBgColor: "ncBgColor" }, host: { listeners: { "mousemove": "onMouseMove($event)" } }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div #container class=\"nc-light-rays-canvas-container\" [style.background-color]=\"ncBgColor\"></div>\r\n<div class=\"nc-content-wrapper\">\r\n <ng-content></ng-content>\r\n</div>", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden}.nc-light-rays-canvas-container{position:absolute;inset:0;z-index:0;pointer-events:none}.nc-content-wrapper{position:relative;z-index:1;width:100%;height:100%}\n"] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: LightRaysBackgroundComponent, decorators: [{
type: Component,
args: [{
selector: 'nc-light-rays-background',
templateUrl: './light-rays-background.component.html',
styleUrls: ['./light-rays-background.component.less']
}]
}], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { containerRef: [{
type: ViewChild,
args: ['container', { static: true }]
}], ncRaysOrigin: [{
type: Input
}], ncRaysColor: [{
type: Input
}], ncRaysSpeed: [{
type: Input
}], ncLightSpread: [{
type: Input
}], ncRayLength: [{
type: Input
}], ncPulsating: [{
type: Input
}], ncFadeDistance: [{
type: Input
}], ncSaturation: [{
type: Input
}], ncFollowMouse: [{
type: Input
}], ncMouseInfluence: [{
type: Input
}], ncNoiseAmount: [{
type: Input
}], ncDistortion: [{
type: Input
}], ncBgColor: [{
type: Input
}], onMouseMove: [{
type: HostListener,
args: ['mousemove', ['$event']]
}] } });
class NcLightRaysBackgroundModule {
}
NcLightRaysBackgroundModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcLightRaysBackgroundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
NcLightRaysBackgroundModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcLightRaysBackgroundModule, declarations: [LightRaysBackgroundComponent], imports: [CommonModule], exports: [LightRaysBackgroundComponent] });
NcLightRaysBackgroundModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcLightRaysBackgroundModule, imports: [[
CommonModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcLightRaysBackgroundModule, decorators: [{
type: NgModule,
args: [{
declarations: [
LightRaysBackgroundComponent
],
imports: [
CommonModule
],
exports: [
LightRaysBackgroundComponent
]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { LightRaysBackgroundComponent, NcLightRaysBackgroundModule };
//# sourceMappingURL=ng-cw-v12-light-rays-background.js.map