UNPKG

ng-cw-v12

Version:

Angular UI Component Library

591 lines (568 loc) 24.4 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 = ` precision highp float; attribute vec3 position; void main() { gl_Position = vec4(position.xy, 0.0, 1.0); } `; const fragmentShader = ` precision highp float; uniform vec2 iResolution; uniform float iTime; uniform float uHeight; uniform float uBaseHalf; uniform mat3 uRot; uniform int uUseBaseWobble; uniform float uGlow; uniform vec2 uOffsetPx; uniform float uNoise; uniform float uSaturation; uniform float uScale; uniform float uHueShift; uniform float uColorFreq; uniform float uBloom; uniform float uCenterShift; uniform float uInvBaseHalf; uniform float uInvHeight; uniform float uMinAxis; uniform float uPxScale; uniform float uTimeScale; vec4 tanh4(vec4 x){ vec4 e2x = exp(2.0*x); return (e2x - 1.0) / (e2x + 1.0); } float rand(vec2 co){ return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453123); } float sdOctaAnisoInv(vec3 p){ vec3 q = vec3(abs(p.x) * uInvBaseHalf, abs(p.y) * uInvHeight, abs(p.z) * uInvBaseHalf); float m = q.x + q.y + q.z - 1.0; return m * uMinAxis * 0.5773502691896258; } float sdPyramidUpInv(vec3 p){ float oct = sdOctaAnisoInv(p); float halfSpace = -p.y; return max(oct, halfSpace); } mat3 hueRotation(float a){ float c = cos(a), s = sin(a); mat3 W = mat3( 0.299, 0.299, 0.299, 0.587, 0.587, 0.587, 0.114, 0.114, 0.114 ); mat3 U = mat3( 0.701, -0.299, -0.300, -0.587, 0.413, -0.588, -0.114, -0.114, 0.886 ); mat3 V = mat3( 0.168, 0.328, -0.497, -0.331, 0.035, 0.296, 0.500, -0.500, 0.201 ); return W + U * c + V * s; } void main(){ vec2 f = (gl_FragCoord.xy - 0.5 * iResolution.xy - uOffsetPx) * uPxScale; float z = 5.0; float d = 0.0; vec3 p; vec4 o = vec4(0.0); float centerShift = uCenterShift; float cf = uColorFreq; mat2 wob = mat2(1.0); if (uUseBaseWobble == 1) { float t = iTime * uTimeScale; float c0 = cos(t + 0.0); float c1 = cos(t + 33.0); float c2 = cos(t + 11.0); wob = mat2(c0, c1, c2, c0); } const int STEPS = 100; for (int i = 0; i < STEPS; i++) { p = vec3(f, z); p.xz = p.xz * wob; p = uRot * p; vec3 q = p; q.y += centerShift; d = 0.1 + 0.2 * abs(sdPyramidUpInv(q)); z -= d; o += (sin((p.y + z) * cf + vec4(0.0, 1.0, 2.0, 3.0)) + 1.0) / d; } o = tanh4(o * o * (uGlow * uBloom) / 1e5); vec3 col = o.rgb; float n = rand(gl_FragCoord.xy + vec2(iTime)); col += (n - 0.5) * uNoise; col = clamp(col, 0.0, 1.0); float L = dot(col, vec3(0.2126, 0.7152, 0.0722)); col = clamp(mix(vec3(L), col, uSaturation), 0.0, 1.0); if(abs(uHueShift) > 0.0001){ col = clamp(hueRotation(uHueShift) * col, 0.0, 1.0); } gl_FragColor = vec4(col, o.a); } `; class PrismBackgroundComponent { constructor(ngZone) { this.ngZone = ngZone; /** 棱镜高度(1-8) */ this.ncHeight = 3.5; /** 棱镜基础宽度(1-8) */ this.ncBaseWidth = 5.5; /** 动画类型:rotate, hover, 3drotate */ this.ncAnimationType = 'rotate'; /** 发光强度(0-3) */ this.ncGlow = 1; /** 偏移量 {x, y} */ this.ncOffset = { x: 0, y: 0 }; /** 噪点强度(0-1) */ this.ncNoise = 0; /** 画布是否具有 alpha 通道(透明背景) */ this._transparent = true; /** 棱镜在屏幕空间中的整体比例(1-5) */ this.ncScale = 3.6; /** 色相偏移(-3.14-3.14) */ this.ncHueShift = 0; /** 颜色变化频率(0-4) */ this.ncColorFrequency = 1; /** 悬停反馈强度 */ this.ncHoverStrength = 2; /** 悬停缓动系数(0-1,数值越高反应越灵敏) */ this.ncInertia = 0.05; /** 在光泽之上叠加额外的光晕效果(0.5-2) */ this.ncBloom = 1; /** 若不在屏幕中,是否挂起以节省性能 */ this._suspendWhenOffscreen = false; /** 动画速度(0-2) */ this.ncTimeScale = 0.5; /** 背景颜色 */ this.ncBgColor = '#000000'; this.rafId = null; this.dpr = 1; this.pointer = { x: 0, y: 0, inside: false }; this.yaw = 0; this.pitch = 0; this.roll = 0; this.targetYaw = 0; this.targetPitch = 0; this.wX = 0; this.wY = 0; this.wZ = 0; this.phX = 0; this.phZ = 0; this.isVisible = true; this.t0 = performance.now(); this.renderLoop = (t) => { var _a, _b, _c; if (!this.material || !this.renderer) return; const time = (t - this.t0) * 0.001; this.material.uniforms['iTime'].value = time; let continueRAF = true; const NOISE_IS_ZERO = Math.max(0.0, this.ncNoise) < 1e-6; const TS = Math.max(0, (_a = this.ncTimeScale) !== null && _a !== void 0 ? _a : 1); const HOVSTR = Math.max(0, (_b = this.ncHoverStrength) !== null && _b !== void 0 ? _b : 1); const INERT = Math.max(0, Math.min(1, (_c = this.ncInertia) !== null && _c !== void 0 ? _c : 0.12)); if (this.ncAnimationType === 'hover') { this.material.uniforms['uUseBaseWobble'].value = 0; const maxPitch = 0.6 * HOVSTR; const maxYaw = 0.6 * HOVSTR; this.targetYaw = (this.pointer.inside ? -this.pointer.x : 0) * maxYaw; this.targetPitch = (this.pointer.inside ? this.pointer.y : 0) * maxPitch; this.yaw = this.lerp(this.yaw, this.targetYaw, INERT); this.pitch = this.lerp(this.pitch, this.targetPitch, INERT); this.roll = this.lerp(this.roll, 0, 0.1); this.material.uniforms['uRot'].value.copy(this.setMat3FromEuler(this.yaw, this.pitch, this.roll)); if (NOISE_IS_ZERO) { const settled = Math.abs(this.yaw - this.targetYaw) < 1e-4 && Math.abs(this.pitch - this.targetPitch) < 1e-4 && Math.abs(this.roll) < 1e-4; if (settled) continueRAF = false; } } else if (this.ncAnimationType === '3drotate') { this.material.uniforms['uUseBaseWobble'].value = 0; const tScaled = time * TS; this.yaw = tScaled * this.wY; this.pitch = Math.sin(tScaled * this.wX + this.phX) * 0.6; this.roll = Math.sin(tScaled * this.wZ + this.phZ) * 0.5; this.material.uniforms['uRot'].value.copy(this.setMat3FromEuler(this.yaw, this.pitch, this.roll)); if (TS < 1e-6) continueRAF = false; } else { this.material.uniforms['uUseBaseWobble'].value = 1; const mat = new THREE.Matrix3(); mat.identity(); this.material.uniforms['uRot'].value.copy(mat); if (TS < 1e-6) continueRAF = false; } this.renderer.render(this.scene, this.camera); if (continueRAF && this.isVisible) { this.rafId = requestAnimationFrame(this.renderLoop); } else { this.rafId = null; } }; } set ncTransparent(val) { this._transparent = val !== null && val !== undefined && val !== false && val !== 'false'; } get ncTransparent() { return this._transparent; } set ncSuspendWhenOffscreen(val) { this._suspendWhenOffscreen = val !== null && val !== undefined && val !== false && val !== 'false'; } get ncSuspendWhenOffscreen() { return this._suspendWhenOffscreen; } ngOnInit() { const rnd = () => Math.random(); this.wX = 0.3 + rnd() * 0.6; this.wY = 0.2 + rnd() * 0.7; this.wZ = 0.1 + rnd() * 0.5; this.phX = rnd() * Math.PI * 2; this.phZ = rnd() * Math.PI * 2; } ngAfterViewInit() { this.ngZone.runOutsideAngular(() => { this.initWebGL(); }); } ngOnDestroy() { this.cleanup(); } ngOnChanges(changes) { var _a; this.updateUniforms(); // 当参数发生改变时,如果动画不在循环状态下(比如时间缩放由0变回1),应该恢复渲染循环 if (this.isVisible) { this.ngZone.runOutsideAngular(() => this.startRAF()); } // 如果离屏挂起状态改变,需要特殊处理 if (changes['ncSuspendWhenOffscreen']) { if (this.ncSuspendWhenOffscreen && ((_a = this.containerRef) === null || _a === void 0 ? void 0 : _a.nativeElement)) { if (!this.intersectionObserver) { this.setupIntersectionObserver(this.containerRef.nativeElement); } } else { if (this.intersectionObserver) { this.intersectionObserver.disconnect(); this.intersectionObserver = null; } if (this.renderer) { this.ngZone.runOutsideAngular(() => this.startRAF()); } } } } initWebGL() { var _a, _b, _c, _d; const container = this.containerRef.nativeElement; this.dpr = Math.min(2, window.devicePixelRatio || 1); this.renderer = new THREE.WebGLRenderer({ alpha: this.ncTransparent, antialias: false }); this.renderer.setPixelRatio(this.dpr); Object.assign(this.renderer.domElement.style, { position: 'absolute', inset: '0', width: '100%', height: '100%', display: 'block' }); container.appendChild(this.renderer.domElement); this.scene = new THREE.Scene(); this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); const H = Math.max(0.001, this.ncHeight); const BW = Math.max(0.001, this.ncBaseWidth); const BASE_HALF = BW * 0.5; this.material = new THREE.RawShaderMaterial({ vertexShader, fragmentShader, transparent: this.ncTransparent, depthTest: false, depthWrite: false, uniforms: { iResolution: { value: new THREE.Vector2() }, iTime: { value: 0 }, uHeight: { value: H }, uBaseHalf: { value: BASE_HALF }, uUseBaseWobble: { value: 1 }, uRot: { value: new THREE.Matrix3() }, uGlow: { value: Math.max(0.0, this.ncGlow) }, uOffsetPx: { value: new THREE.Vector2(this.ncOffset.x * this.dpr, this.ncOffset.y * this.dpr) }, uNoise: { value: Math.max(0.0, this.ncNoise) }, uSaturation: { value: this.ncTransparent ? 1.5 : 1 }, uScale: { value: Math.max(0.001, this.ncScale) }, uHueShift: { value: (_a = this.ncHueShift) !== null && _a !== void 0 ? _a : 0 }, uColorFreq: { value: Math.max(0.0, (_b = this.ncColorFrequency) !== null && _b !== void 0 ? _b : 1) }, uBloom: { value: Math.max(0.0, (_c = this.ncBloom) !== null && _c !== void 0 ? _c : 1) }, uCenterShift: { value: H * 0.25 }, uInvBaseHalf: { value: 1 / BASE_HALF }, uInvHeight: { value: 1 / H }, uMinAxis: { value: Math.min(BASE_HALF, H) }, uPxScale: { value: 1 }, uTimeScale: { value: Math.max(0, (_d = this.ncTimeScale) !== null && _d !== void 0 ? _d : 1) } } }); const geometry = new THREE.PlaneGeometry(2, 2); this.mesh = new THREE.Mesh(geometry, this.material); this.scene.add(this.mesh); this.setupResizeObserver(container); if (this.ncSuspendWhenOffscreen) { this.setupIntersectionObserver(container); } else { this.startRAF(); } } updateUniforms() { var _a, _b, _c, _d; if (!this.material) return; const H = Math.max(0.001, this.ncHeight); const BW = Math.max(0.001, this.ncBaseWidth); const BASE_HALF = BW * 0.5; const SCALE = Math.max(0.001, this.ncScale); this.material.uniforms['uHeight'].value = H; this.material.uniforms['uBaseHalf'].value = BASE_HALF; this.material.uniforms['uGlow'].value = Math.max(0.0, this.ncGlow); this.material.uniforms['uOffsetPx'].value.set(this.ncOffset.x * this.dpr, this.ncOffset.y * this.dpr); this.material.uniforms['uNoise'].value = Math.max(0.0, this.ncNoise); this.material.uniforms['uSaturation'].value = this.ncTransparent ? 1.5 : 1; this.material.uniforms['uScale'].value = SCALE; this.material.uniforms['uHueShift'].value = (_a = this.ncHueShift) !== null && _a !== void 0 ? _a : 0; this.material.uniforms['uColorFreq'].value = Math.max(0.0, (_b = this.ncColorFrequency) !== null && _b !== void 0 ? _b : 1); this.material.uniforms['uBloom'].value = Math.max(0.0, (_c = this.ncBloom) !== null && _c !== void 0 ? _c : 1); this.material.uniforms['uCenterShift'].value = H * 0.25; this.material.uniforms['uInvBaseHalf'].value = 1 / BASE_HALF; this.material.uniforms['uInvHeight'].value = 1 / H; this.material.uniforms['uMinAxis'].value = Math.min(BASE_HALF, H); this.material.uniforms['uTimeScale'].value = Math.max(0, (_d = this.ncTimeScale) !== null && _d !== void 0 ? _d : 1); // 更新需要依赖高度和缩放的 uPxScale const canvasHeight = this.renderer.domElement.height || 1; this.material.uniforms['uPxScale'].value = 1 / (canvasHeight * 0.1 * SCALE); // 更新 renderer 的 alpha if (this.renderer) { // 通过直接重建背景是否透明 this.renderer.setClearColor(0x000000, this.ncTransparent ? 0 : 1); this.material.transparent = this.ncTransparent; } } setupResizeObserver(container) { this.resizeObserver = new ResizeObserver(() => { this.ngZone.runOutsideAngular(() => { if (!this.renderer || !container) return; const w = container.clientWidth || 1; const h = container.clientHeight || 1; this.renderer.setSize(w, h, false); const canvas = this.renderer.domElement; this.material.uniforms['iResolution'].value.set(canvas.width, canvas.height); const SCALE = Math.max(0.001, this.ncScale); this.material.uniforms['uPxScale'].value = 1 / ((canvas.height || 1) * 0.1 * SCALE); }); }); this.resizeObserver.observe(container); } setupIntersectionObserver(container) { this.intersectionObserver = new IntersectionObserver(entries => { const vis = entries.some(e => e.isIntersecting); this.isVisible = vis; if (vis) { this.ngZone.runOutsideAngular(() => this.startRAF()); } else { this.stopRAF(); } }); this.intersectionObserver.observe(container); } lerp(a, b, t) { return a + (b - a) * t; } setMat3FromEuler(yawY, pitchX, rollZ) { const cy = Math.cos(yawY), sy = Math.sin(yawY); const cx = Math.cos(pitchX), sx = Math.sin(pitchX); const cz = Math.cos(rollZ), sz = Math.sin(rollZ); const r00 = cy * cz + sy * sx * sz; const r01 = -cy * sz + sy * sx * cz; const r02 = sy * cx; const r10 = cx * sz; const r11 = cx * cz; const r12 = -sx; const r20 = -sy * cz + cy * sx * sz; const r21 = sy * sz + cy * sx * cz; const r22 = cy * cx; const mat = new THREE.Matrix3(); mat.set(r00, r01, r02, r10, r11, r12, r20, r21, r22); return mat; } startRAF() { if (this.rafId === null) { this.rafId = requestAnimationFrame(this.renderLoop); } } stopRAF() { if (this.rafId !== null) { cancelAnimationFrame(this.rafId); this.rafId = null; } } cleanup() { var _a, _b; this.stopRAF(); 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(); this.renderer = null; } if (this.geometry) { this.geometry.dispose(); } if (this.material) { this.material.dispose(); } } // 为了能够在 cleanup 中使用 get geometry() { var _a; return (_a = this.mesh) === null || _a === void 0 ? void 0 : _a.geometry; } // ─── 鼠标事件 (HostListener) ──────────────────────────────────────────────── onPointerMove(e) { if (this.ncAnimationType !== 'hover') return; const ww = Math.max(1, window.innerWidth); const wh = Math.max(1, window.innerHeight); const cx = ww * 0.5; const cy = wh * 0.5; const nx = (e.clientX - cx) / (ww * 0.5); const ny = (e.clientY - cy) / (wh * 0.5); this.pointer.x = Math.max(-1, Math.min(1, nx)); this.pointer.y = Math.max(-1, Math.min(1, ny)); this.pointer.inside = true; // 如果动画之前停止了(比如因为 inertia settled),此时重新唤醒 this.ngZone.runOutsideAngular(() => this.startRAF()); } onMouseLeave() { if (this.ncAnimationType !== 'hover') return; this.pointer.inside = false; this.ngZone.runOutsideAngular(() => this.startRAF()); } onBlur() { if (this.ncAnimationType !== 'hover') return; this.pointer.inside = false; this.ngZone.runOutsideAngular(() => this.startRAF()); } } PrismBackgroundComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: PrismBackgroundComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); PrismBackgroundComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.1.5", type: PrismBackgroundComponent, selector: "nc-prism-background", inputs: { ncHeight: "ncHeight", ncBaseWidth: "ncBaseWidth", ncAnimationType: "ncAnimationType", ncGlow: "ncGlow", ncOffset: "ncOffset", ncNoise: "ncNoise", ncTransparent: "ncTransparent", ncScale: "ncScale", ncHueShift: "ncHueShift", ncColorFrequency: "ncColorFrequency", ncHoverStrength: "ncHoverStrength", ncInertia: "ncInertia", ncBloom: "ncBloom", ncSuspendWhenOffscreen: "ncSuspendWhenOffscreen", ncTimeScale: "ncTimeScale", ncBgColor: "ncBgColor" }, host: { listeners: { "window:pointermove": "onPointerMove($event)", "window:mouseleave": "onMouseLeave()", "window:blur": "onBlur()" } }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div #container class=\"nc-prism-canvas-container\" [style.background-color]=\"ncBgColor\"></div>\n<div class=\"nc-content-wrapper\">\n <ng-content></ng-content>\n</div>", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden}.nc-prism-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: PrismBackgroundComponent, decorators: [{ type: Component, args: [{ selector: 'nc-prism-background', templateUrl: './prism-background.component.html', styleUrls: ['./prism-background.component.less'] }] }], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { containerRef: [{ type: ViewChild, args: ['container', { static: true }] }], ncHeight: [{ type: Input }], ncBaseWidth: [{ type: Input }], ncAnimationType: [{ type: Input }], ncGlow: [{ type: Input }], ncOffset: [{ type: Input }], ncNoise: [{ type: Input }], ncTransparent: [{ type: Input }], ncScale: [{ type: Input }], ncHueShift: [{ type: Input }], ncColorFrequency: [{ type: Input }], ncHoverStrength: [{ type: Input }], ncInertia: [{ type: Input }], ncBloom: [{ type: Input }], ncSuspendWhenOffscreen: [{ type: Input }], ncTimeScale: [{ type: Input }], ncBgColor: [{ type: Input }], onPointerMove: [{ type: HostListener, args: ['window:pointermove', ['$event']] }], onMouseLeave: [{ type: HostListener, args: ['window:mouseleave'] }], onBlur: [{ type: HostListener, args: ['window:blur'] }] } }); class NcPrismBackgroundModule { } NcPrismBackgroundModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcPrismBackgroundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NcPrismBackgroundModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcPrismBackgroundModule, declarations: [PrismBackgroundComponent], imports: [CommonModule], exports: [PrismBackgroundComponent] }); NcPrismBackgroundModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcPrismBackgroundModule, imports: [[ CommonModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcPrismBackgroundModule, decorators: [{ type: NgModule, args: [{ declarations: [ PrismBackgroundComponent ], imports: [ CommonModule ], exports: [ PrismBackgroundComponent ] }] }] }); /** * Generated bundle index. Do not edit. */ export { NcPrismBackgroundModule, PrismBackgroundComponent }; //# sourceMappingURL=ng-cw-v12-prism-background.js.map