UNPKG

ng-cw-v12

Version:

Angular UI Component Library

313 lines (301 loc) 14.1 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 vertexShader = ` attribute vec3 position; attribute vec2 uv; varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); } `; // ─── 片元着色器 ───────────────────────────────────────────────────────────── const fragmentShader = ` precision highp float; uniform float uTime; uniform vec3 uColor; uniform vec3 uResolution; uniform vec2 uMouse; uniform float uAmplitude; uniform float uSpeed; varying vec2 vUv; void main() { float mr = min(uResolution.x, uResolution.y); vec2 uv = (vUv.xy * 2.0 - 1.0) * uResolution.xy / mr; uv += (uMouse - vec2(0.5)) * uAmplitude; float d = -uTime * 0.5 * uSpeed; float a = 0.0; for (float i = 0.0; i < 8.0; ++i) { a += cos(i - d - a * uv.x); d += sin(uv.y * i + a); } d += uTime * 0.5 * uSpeed; vec3 col = vec3(cos(uv * vec2(d, a)) * 0.6 + 0.4, cos(a + d) * 0.5 + 0.5); col = cos(col * cos(vec3(d, a, 2.5)) * 0.5 + 0.5) * uColor; gl_FragColor = vec4(col, 1.0); } `; class IridescenceBackgroundComponent { constructor(ngZone) { this.ngZone = ngZone; /** 颜色 [r, g, b] 数组(0-1) */ this.ncColor = [0.5, 0.6, 0.8]; /** 动画速度倍率(0-2) */ this.ncSpeed = 1.0; /** 鼠标驱动效应的振幅 */ this.ncAmplitude = 0.1; /** 是否响应鼠标操作 */ this._mouseReact = true; this.renderer = null; this.scene = null; this.camera = null; this.material = null; this.mesh = null; this.rafId = null; this.resizeRafId = null; this.isVisible = true; this.mousePos = new THREE.Vector2(0.5, 0.5); } set ncMouseReact(val) { this._mouseReact = val !== null && val !== undefined && val !== false && val !== 'false'; } get ncMouseReact() { return this._mouseReact; } ngOnInit() { } ngAfterViewInit() { this.initWebGL(); } ngOnDestroy() { this.cleanup(); } ngOnChanges(changes) { if (!this.material) return; if (changes['ncColor']) { this.material.uniforms['uColor'].value.setRGB(this.ncColor[0], this.ncColor[1], this.ncColor[2]); } if (changes['ncSpeed']) { this.material.uniforms['uSpeed'].value = this.ncSpeed; } if (changes['ncAmplitude']) { this.material.uniforms['uAmplitude'].value = this.ncAmplitude; } } // ─── 初始化 WebGL ────────────────────────────────────────────────────────── initWebGL() { const container = this.containerRef.nativeElement; this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); this.renderer.setClearColor(0xffffff, 1); // 限定最大 dpr,避免在超高分辨率屏幕上性能过低 this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); container.appendChild(this.renderer.domElement); this.scene = new THREE.Scene(); this.camera = new THREE.Camera(); // 空相机,不使用投影矩阵 const geometry = new THREE.PlaneGeometry(2, 2); this.material = new THREE.RawShaderMaterial({ vertexShader: vertexShader, fragmentShader: fragmentShader, uniforms: { uTime: { value: 0 }, uColor: { value: new THREE.Color(this.ncColor[0], this.ncColor[1], this.ncColor[2]) }, uResolution: { value: new THREE.Vector3() }, uMouse: { value: this.mousePos }, uAmplitude: { value: this.ncAmplitude }, uSpeed: { value: this.ncSpeed } } }); this.mesh = new THREE.Mesh(geometry, this.material); this.scene.add(this.mesh); this.setupResizeObserver(container); this.setupIntersectionObserver(container); this.resize(); this.ngZone.runOutsideAngular(() => { this.startAnimation(); }); } // ─── Resize 处理 ────────────────────────────────────────────────────────── resize() { const container = this.containerRef.nativeElement; if (!container || !this.renderer || !this.material) return; const width = container.offsetWidth; const height = container.offsetHeight; this.renderer.setSize(width, height); const canvas = this.renderer.domElement; this.material.uniforms['uResolution'].value.set(canvas.width, canvas.height, canvas.width / canvas.height); } setupResizeObserver(container) { this.resizeObserver = new ResizeObserver(() => { if (this.resizeRafId !== null) cancelAnimationFrame(this.resizeRafId); this.ngZone.runOutsideAngular(() => { this.resizeRafId = requestAnimationFrame(() => { this.resize(); this.resizeRafId = null; }); }); }); this.resizeObserver.observe(container); } // ─── 渲染循环 ───────────────────────────────────────────────────────────── startAnimation() { const update = (t) => { this.rafId = requestAnimationFrame(update); if (this.isVisible && this.renderer && this.scene && this.camera && this.material) { this.material.uniforms['uTime'].value = t * 0.001; this.renderer.render(this.scene, this.camera); } }; update(performance.now()); } stopAnimation() { if (this.rafId !== null) { cancelAnimationFrame(this.rafId); this.rafId = null; } } // ─── IntersectionObserver:不可见时暂停渲染以节省性能 ──────────────────── setupIntersectionObserver(container) { this.intersectionObserver = new IntersectionObserver(entries => { const entry = entries[0]; this.isVisible = entry.isIntersecting && entry.intersectionRatio > 0; }, { threshold: [0, 0.01, 0.1] }); this.intersectionObserver.observe(container); } // ─── 清理资源 ───────────────────────────────────────────────────────────── cleanup() { var _a, _b; this.stopAnimation(); if (this.resizeRafId !== null) { cancelAnimationFrame(this.resizeRafId); this.resizeRafId = null; } 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) { const canvas = this.renderer.domElement; if (canvas && canvas.parentNode) { canvas.parentNode.removeChild(canvas); } this.renderer.dispose(); } if (this.material) { this.material.dispose(); } if (this.mesh && this.mesh.geometry) { this.mesh.geometry.dispose(); } this.renderer = null; this.scene = null; this.camera = null; this.material = null; this.mesh = null; } // ─── HostListeners for Mouse Interaction ───────────────────────────────── onMouseMove(event) { if (!this.ncMouseReact || !this.material || !this.containerRef) return; const container = this.containerRef.nativeElement; const rect = container.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) return; // 检查是否在容器内 if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) { return; } const x = (event.clientX - rect.left) / rect.width; const y = 1.0 - (event.clientY - rect.top) / rect.height; // 更新 THREE.Vector2 this.mousePos.set(x, y); } onTouchMove(event) { if (!this.ncMouseReact || !this.material || !this.containerRef || event.touches.length === 0) return; const container = this.containerRef.nativeElement; const rect = container.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) return; const touch = event.touches[0]; // 检查是否在容器内 if (touch.clientX < rect.left || touch.clientX > rect.right || touch.clientY < rect.top || touch.clientY > rect.bottom) { return; } const x = (touch.clientX - rect.left) / rect.width; const y = 1.0 - (touch.clientY - rect.top) / rect.height; // 更新 THREE.Vector2 this.mousePos.set(x, y); } } IridescenceBackgroundComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: IridescenceBackgroundComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); IridescenceBackgroundComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.1.5", type: IridescenceBackgroundComponent, selector: "nc-iridescence-background", inputs: { ncColor: "ncColor", ncSpeed: "ncSpeed", ncAmplitude: "ncAmplitude", ncMouseReact: "ncMouseReact" }, host: { listeners: { "mousemove": "onMouseMove($event)", "touchmove": "onTouchMove($event)" } }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div #container class=\"nc-iridescence-canvas-container\"></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-iridescence-canvas-container{position:absolute;inset:0;z-index:0}.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: IridescenceBackgroundComponent, decorators: [{ type: Component, args: [{ selector: 'nc-iridescence-background', templateUrl: './iridescence-background.component.html', styleUrls: ['./iridescence-background.component.less'] }] }], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { containerRef: [{ type: ViewChild, args: ['container', { static: true }] }], ncColor: [{ type: Input }], ncSpeed: [{ type: Input }], ncAmplitude: [{ type: Input }], ncMouseReact: [{ type: Input }], onMouseMove: [{ type: HostListener, args: ['mousemove', ['$event']] }], onTouchMove: [{ type: HostListener, args: ['touchmove', ['$event']] }] } }); class NcIridescenceBackgroundModule { } NcIridescenceBackgroundModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcIridescenceBackgroundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NcIridescenceBackgroundModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcIridescenceBackgroundModule, declarations: [IridescenceBackgroundComponent], imports: [CommonModule], exports: [IridescenceBackgroundComponent] }); NcIridescenceBackgroundModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcIridescenceBackgroundModule, imports: [[ CommonModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcIridescenceBackgroundModule, decorators: [{ type: NgModule, args: [{ declarations: [ IridescenceBackgroundComponent ], imports: [ CommonModule ], exports: [ IridescenceBackgroundComponent ] }] }] }); /** * Generated bundle index. Do not edit. */ export { IridescenceBackgroundComponent, NcIridescenceBackgroundModule }; //# sourceMappingURL=ng-cw-v12-iridescence-background.js.map