UNPKG

ng-cw-v12

Version:

Angular UI Component Library

477 lines (452 loc) 20.2 kB
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