UNPKG

ng-cw-v12

Version:

Angular UI Component Library

380 lines (358 loc) 15.3 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 = ` varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); } `; const fragmentShader = ` precision highp float; uniform float iTime; uniform vec3 iResolution; uniform vec3 uColor; uniform float uAmplitude; uniform float uDistance; uniform vec2 uMouse; #define PI 3.1415926538 const int u_line_count = 40; const float u_line_width = 7.0; const float u_line_blur = 10.0; float Perlin2D(vec2 P) { vec2 Pi = floor(P); vec4 Pf_Pfmin1 = P.xyxy - vec4(Pi, Pi + 1.0); vec4 Pt = vec4(Pi.xy, Pi.xy + 1.0); Pt = Pt - floor(Pt * (1.0 / 71.0)) * 71.0; Pt += vec2(26.0, 161.0).xyxy; Pt *= Pt; Pt = Pt.xzxz * Pt.yyww; vec4 hash_x = fract(Pt * (1.0 / 951.135664)); vec4 hash_y = fract(Pt * (1.0 / 642.949883)); vec4 grad_x = hash_x - 0.49999; vec4 grad_y = hash_y - 0.49999; vec4 grad_results = inversesqrt(grad_x * grad_x + grad_y * grad_y) * (grad_x * Pf_Pfmin1.xzxz + grad_y * Pf_Pfmin1.yyww); grad_results *= 1.4142135623730950; vec2 blend = Pf_Pfmin1.xy * Pf_Pfmin1.xy * Pf_Pfmin1.xy * (Pf_Pfmin1.xy * (Pf_Pfmin1.xy * 6.0 - 15.0) + 10.0); vec4 blend2 = vec4(blend, vec2(1.0 - blend)); return dot(grad_results, blend2.zxzx * blend2.wwyy); } float pixel(float count, vec2 resolution) { return (1.0 / max(resolution.x, resolution.y)) * count; } float lineFn(vec2 st, float width, float perc, float offset, vec2 mouse, float time, float amplitude, float distance) { float split_offset = (perc * 0.4); float split_point = 0.1 + split_offset; float amplitude_normal = smoothstep(split_point, 0.7, st.x); float amplitude_strength = 0.5; float finalAmplitude = amplitude_normal * amplitude_strength * amplitude * (1.0 + (mouse.y - 0.5) * 0.2); float time_scaled = time / 10.0 + (mouse.x - 0.5) * 1.0; float blur = smoothstep(split_point, split_point + 0.05, st.x) * perc; float xnoise = mix( Perlin2D(vec2(time_scaled, st.x + perc) * 2.5), Perlin2D(vec2(time_scaled, st.x + time_scaled) * 3.5) / 1.5, st.x * 0.3 ); float y = 0.5 + (perc - 0.5) * distance + xnoise / 2.0 * finalAmplitude; float line_start = smoothstep( y + (width / 2.0) + (u_line_blur * pixel(1.0, iResolution.xy) * blur), y, st.y ); float line_end = smoothstep( y, y - (width / 2.0) - (u_line_blur * pixel(1.0, iResolution.xy) * blur), st.y ); return clamp( (line_start - line_end) * (1.0 - smoothstep(0.0, 1.0, pow(perc, 0.3))), 0.0, 1.0 ); } void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 uv = fragCoord / iResolution.xy; float line_strength = 1.0; for (int i = 0; i < u_line_count; i++) { float p = float(i) / float(u_line_count); line_strength *= (1.0 - lineFn( uv, u_line_width * pixel(1.0, iResolution.xy) * (1.0 - p), p, (PI * 1.0) * p, uMouse, iTime, uAmplitude, uDistance )); } float colorVal = 1.0 - line_strength; fragColor = vec4(uColor * colorVal, colorVal); } void main() { mainImage(gl_FragColor, gl_FragCoord.xy); } `; class ThreadsBackgroundComponent { constructor(ngZone) { this.ngZone = ngZone; /** 颜色 [r, g, b] 数组(0-1) */ this.ncColor = [1, 1, 1]; /** 振幅大小(0-5) */ this.ncAmplitude = 1; /** 线条距离(0-2) */ this.ncDistance = 0; /** 背景颜色 */ this.ncBgColor = 'black'; /** 是否启用鼠标交互 */ this._enableMouseInteraction = false; this.animationFrameId = null; this.isVisible = true; this.isRunning = false; this.currentMouse = new THREE.Vector2(0.5, 0.5); this.targetMouse = new THREE.Vector2(0.5, 0.5); this.onVisibilityChange = () => { if (document.hidden) { this.pause(); } else if (this.isVisible) { this.ngZone.runOutsideAngular(() => { this.start(); }); } }; this.update = (t) => { if (!this.isRunning) return; if (this.ncEnableMouseInteraction) { const smoothing = 0.05; this.currentMouse.x += smoothing * (this.targetMouse.x - this.currentMouse.x); this.currentMouse.y += smoothing * (this.targetMouse.y - this.currentMouse.y); this.material.uniforms['uMouse'].value.copy(this.currentMouse); } else { this.material.uniforms['uMouse'].value.set(0.5, 0.5); } this.material.uniforms['iTime'].value = t * 0.001; this.renderer.render(this.scene, this.camera); this.animationFrameId = requestAnimationFrame(this.update); }; } set ncEnableMouseInteraction(val) { this._enableMouseInteraction = val !== null && val !== undefined && val !== false && val !== 'false'; } get ncEnableMouseInteraction() { return this._enableMouseInteraction; } ngOnInit() { } ngAfterViewInit() { this.ngZone.runOutsideAngular(() => { this.initWebGL(); }); } ngOnDestroy() { this.cleanup(); } ngOnChanges(changes) { if (this.material) { if (changes['ncColor']) { this.material.uniforms['uColor'].value.copy(this.getThreeColor(this.ncColor)); } if (changes['ncAmplitude']) { this.material.uniforms['uAmplitude'].value = this.ncAmplitude; } if (changes['ncDistance']) { this.material.uniforms['uDistance'].value = this.ncDistance; } } } getThreeColor(color) { if (Array.isArray(color)) { return new THREE.Color(color[0], color[1], color[2]); } return new THREE.Color(color); } initWebGL() { const container = this.containerRef.nativeElement; // 初始化渲染器 this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: false }); this.renderer.setClearColor(0x000000, 0); // 采用与 Threads.jsx 中 blendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA) 一致的默认混合方式 container.appendChild(this.renderer.domElement); this.scene = new THREE.Scene(); this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); this.geometry = new THREE.PlaneGeometry(2, 2); // 初始分辨率设定,后续由 resize 更新 const canvasWidth = container.clientWidth || 1; const canvasHeight = container.clientHeight || 1; this.material = new THREE.ShaderMaterial({ vertexShader, fragmentShader, transparent: true, uniforms: { iTime: { value: 0 }, iResolution: { value: new THREE.Vector3(canvasWidth, canvasHeight, canvasWidth / canvasHeight) }, uColor: { value: this.getThreeColor(this.ncColor) }, uAmplitude: { value: this.ncAmplitude }, uDistance: { value: this.ncDistance }, uMouse: { value: new THREE.Vector2(0.5, 0.5) } } }); this.mesh = new THREE.Mesh(this.geometry, this.material); this.scene.add(this.mesh); this.setupResizeObserver(container); this.setupIntersectionObserver(container); this.resize(); this.start(); } setupResizeObserver(container) { this.resizeObserver = new ResizeObserver(() => { this.ngZone.runOutsideAngular(() => { this.resize(); }); }); 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); document.addEventListener('visibilitychange', this.onVisibilityChange); } resize() { if (!this.containerRef || !this.renderer) return; const container = this.containerRef.nativeElement; const width = container.clientWidth; const height = container.clientHeight; this.renderer.setSize(width, height); // 可选设置 pixelRatio,如不需要极高精度可保留 1 提高性能 // this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); this.material.uniforms['iResolution'].value.set(width, height, (width / Math.max(height, 1))); } start() { if (this.isRunning) return; this.isRunning = true; this.animationFrameId = requestAnimationFrame(this.update); } pause() { this.isRunning = false; if (this.animationFrameId !== null) { cancelAnimationFrame(this.animationFrameId); this.animationFrameId = null; } } cleanup() { var _a, _b; this.pause(); 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; } document.removeEventListener('visibilitychange', this.onVisibilityChange); if (this.renderer) { const canvas = this.renderer.domElement; if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas); this.renderer.dispose(); this.renderer.forceContextLoss(); } if (this.geometry) this.geometry.dispose(); if (this.material) this.material.dispose(); } onMouseMove(event) { if (!this.ncEnableMouseInteraction || !this.containerRef) return; const container = this.containerRef.nativeElement; const rect = container.getBoundingClientRect(); const x = (event.clientX - rect.left) / rect.width; const y = 1.0 - (event.clientY - rect.top) / rect.height; this.targetMouse.set(x, y); } onMouseLeave() { if (!this.ncEnableMouseInteraction) return; this.targetMouse.set(0.5, 0.5); } } ThreadsBackgroundComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: ThreadsBackgroundComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); ThreadsBackgroundComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.1.5", type: ThreadsBackgroundComponent, selector: "nc-threads-background", inputs: { ncColor: "ncColor", ncAmplitude: "ncAmplitude", ncDistance: "ncDistance", ncBgColor: "ncBgColor", ncEnableMouseInteraction: "ncEnableMouseInteraction" }, host: { listeners: { "mousemove": "onMouseMove($event)", "mouseleave": "onMouseLeave()" } }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div #container class=\"nc-threads-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-threads-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: ThreadsBackgroundComponent, decorators: [{ type: Component, args: [{ selector: 'nc-threads-background', templateUrl: './threads-background.component.html', styleUrls: ['./threads-background.component.less'] }] }], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { containerRef: [{ type: ViewChild, args: ['container', { static: true }] }], ncColor: [{ type: Input }], ncAmplitude: [{ type: Input }], ncDistance: [{ type: Input }], ncBgColor: [{ type: Input }], ncEnableMouseInteraction: [{ type: Input }], onMouseMove: [{ type: HostListener, args: ['mousemove', ['$event']] }], onMouseLeave: [{ type: HostListener, args: ['mouseleave'] }] } }); class NcThreadsBackgroundModule { } NcThreadsBackgroundModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcThreadsBackgroundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NcThreadsBackgroundModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcThreadsBackgroundModule, declarations: [ThreadsBackgroundComponent], imports: [CommonModule], exports: [ThreadsBackgroundComponent] }); NcThreadsBackgroundModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcThreadsBackgroundModule, imports: [[ CommonModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcThreadsBackgroundModule, decorators: [{ type: NgModule, args: [{ declarations: [ ThreadsBackgroundComponent ], imports: [ CommonModule ], exports: [ ThreadsBackgroundComponent ] }] }] }); /** * Generated bundle index. Do not edit. */ export { NcThreadsBackgroundModule, ThreadsBackgroundComponent }; //# sourceMappingURL=ng-cw-v12-threads-background.js.map