UNPKG

ng-cw-v12

Version:

Angular UI Component Library

416 lines (395 loc) 18 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 = ` attribute vec3 position; varying vec2 vUv; void main() { vUv = position.xy * 0.5 + 0.5; gl_Position = vec4(position, 1.0); } `; const frag = ` precision highp float; uniform float iTime; uniform vec2 iResolution; uniform bool enableRainbow; uniform vec3 gridColor; uniform float rippleIntensity; uniform float gridSize; uniform float gridThickness; uniform float fadeDistance; uniform float vignetteStrength; uniform float glowIntensity; uniform float opacity; uniform float gridRotation; uniform bool mouseInteraction; uniform vec2 mousePosition; uniform float mouseInfluence; uniform float mouseInteractionRadius; varying vec2 vUv; float pi = 3.141592; mat2 rotate(float angle) { float s = sin(angle); float c = cos(angle); return mat2(c, -s, s, c); } void main() { vec2 uv = vUv * 2.0 - 1.0; uv.x *= iResolution.x / iResolution.y; if (gridRotation != 0.0) { uv = rotate(gridRotation * pi / 180.0) * uv; } float dist = length(uv); float func = sin(pi * (iTime - dist)); vec2 rippleUv = uv + uv * func * rippleIntensity; if (mouseInteraction && mouseInfluence > 0.0) { vec2 mouseUv = (mousePosition * 2.0 - 1.0); mouseUv.x *= iResolution.x / iResolution.y; float mouseDist = length(uv - mouseUv); float influence = mouseInfluence * exp(-mouseDist * mouseDist / (mouseInteractionRadius * mouseInteractionRadius)); float mouseWave = sin(pi * (iTime * 2.0 - mouseDist * 3.0)) * influence; rippleUv += normalize(uv - mouseUv) * mouseWave * rippleIntensity * 0.3; } vec2 a = sin(gridSize * 0.5 * pi * rippleUv - pi / 2.0); vec2 b = abs(a); float aaWidth = 0.5; vec2 smoothB = vec2( smoothstep(0.0, aaWidth, b.x), smoothstep(0.0, aaWidth, b.y) ); vec3 color = vec3(0.0); color += exp(-gridThickness * smoothB.x * (0.8 + 0.5 * sin(pi * iTime))); color += exp(-gridThickness * smoothB.y); color += 0.5 * exp(-(gridThickness / 4.0) * sin(smoothB.x)); color += 0.5 * exp(-(gridThickness / 3.0) * smoothB.y); if (glowIntensity > 0.0) { color += glowIntensity * exp(-gridThickness * 0.5 * smoothB.x); color += glowIntensity * exp(-gridThickness * 0.5 * smoothB.y); } float ddd = exp(-2.0 * clamp(pow(dist, fadeDistance), 0.0, 1.0)); vec2 vignetteCoords = vUv - 0.5; float vignetteDistance = length(vignetteCoords); float vignette = 1.0 - pow(vignetteDistance * 2.0, vignetteStrength); vignette = clamp(vignette, 0.0, 1.0); vec3 t; if (enableRainbow) { t = vec3( uv.x * 0.5 + 0.5 * sin(iTime), uv.y * 0.5 + 0.5 * cos(iTime), pow(cos(iTime), 4.0) ) + 0.5; } else { t = gridColor; } float finalFade = ddd * vignette; float alpha = length(color) * finalFade * opacity; gl_FragColor = vec4(color * t * finalFade * opacity, alpha); } `; class RippleGridBackgroundComponent { constructor(ngZone) { this.ngZone = ngZone; /** 彩虹模式关闭时网格的颜色 */ this.ncGridColor = '#5227ff'; /** 控制中心波纹效应的强度(0-0.3)*/ this.ncRippleIntensity = 0.05; /** 控制网格图案的密度/大小(5-30)*/ this.ncGridSize = 10.0; /** 控制网格线的粗细(5-50)*/ this.ncGridThickness = 15.0; /** 控制淡入淡出效果从中心向外延伸的距离(0.5-3)*/ this.ncFadeDistance = 1.5; /** 控制暗角(边缘变暗)效果的强度(0.5-5)*/ this.ncVignetteStrength = 2.0; /** 为网格线添加发光效果(0-1)*/ this.ncGlowIntensity = 0.1; /** 整体效果的不透明度(0-1)*/ this.ncOpacity = 1.0; /** 将整个网格图案旋转一定角度(0-360)*/ this.ncGridRotation = 0; /** 启用鼠标/触摸交互以产生涟漪效 */ this._mouseInteraction = true; /** 控制鼠标交互效果的半径(0.2-2)*/ this.ncMouseInteractionRadius = 1; /** 为网格启用彩虹色循环动画 */ this._enableRainbow = false; this.rafId = null; this.resizeRafId = null; this.isVisible = true; this.isDestroyed = false; this.clock = new THREE.Clock(); this.mousePosition = new THREE.Vector2(0.5, 0.5); this.targetMouse = new THREE.Vector2(0.5, 0.5); this.targetInfluence = 0; } set ncMouseInteraction(val) { this._mouseInteraction = val !== null && val !== undefined && val !== false && val !== 'false'; } get ncMouseInteraction() { return this._mouseInteraction; } set ncEnableRainbow(val) { this._enableRainbow = val !== null && val !== undefined && val !== false && val !== 'false'; } get ncEnableRainbow() { return this._enableRainbow; } ngOnInit() { } ngAfterViewInit() { this.initWebGL(); this.setupResizeObserver(); this.setupIntersectionObserver(); this.ngZone.runOutsideAngular(() => { this.startAnimation(); }); } ngOnDestroy() { this.isDestroyed = true; this.cleanup(); } ngOnChanges(changes) { if (!this.material) return; this.updateUniforms(); } hexToRgb(hex) { let cleanHex = hex; if (cleanHex.startsWith('#')) { cleanHex = cleanHex.slice(1); } const result = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(cleanHex); return result ? new THREE.Vector3(parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255) : new THREE.Vector3(1, 1, 1); } updateUniforms() { if (!this.material) return; const u = this.material.uniforms; u.enableRainbow.value = this.ncEnableRainbow; u.gridColor.value.copy(this.hexToRgb(this.ncGridColor)); u.rippleIntensity.value = this.ncRippleIntensity; u.gridSize.value = this.ncGridSize; u.gridThickness.value = this.ncGridThickness; u.fadeDistance.value = this.ncFadeDistance; u.vignetteStrength.value = this.ncVignetteStrength; u.glowIntensity.value = this.ncGlowIntensity; u.opacity.value = this.ncOpacity; u.gridRotation.value = this.ncGridRotation; u.mouseInteraction.value = this.ncMouseInteraction; u.mouseInteractionRadius.value = this.ncMouseInteractionRadius; } initWebGL() { const el = this.containerRef.nativeElement; this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, powerPreference: 'high-performance' }); const dpr = Math.min(window.devicePixelRatio, 2); this.renderer.setPixelRatio(dpr); this.renderer.setSize(el.offsetWidth, el.offsetHeight); el.appendChild(this.renderer.domElement); this.scene = new THREE.Scene(); // The camera doesn't matter much as we use RawShaderMaterial that sets gl_Position directly this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10); this.camera.position.z = 1; const geometry = new THREE.PlaneGeometry(2, 2); this.material = new THREE.RawShaderMaterial({ vertexShader: vert, fragmentShader: frag, uniforms: { iTime: { value: 0 }, iResolution: { value: new THREE.Vector2(el.offsetWidth, el.offsetHeight) }, enableRainbow: { value: this.ncEnableRainbow }, gridColor: { value: this.hexToRgb(this.ncGridColor) }, rippleIntensity: { value: this.ncRippleIntensity }, gridSize: { value: this.ncGridSize }, gridThickness: { value: this.ncGridThickness }, fadeDistance: { value: this.ncFadeDistance }, vignetteStrength: { value: this.ncVignetteStrength }, glowIntensity: { value: this.ncGlowIntensity }, opacity: { value: this.ncOpacity }, gridRotation: { value: this.ncGridRotation }, mouseInteraction: { value: this.ncMouseInteraction }, mousePosition: { value: new THREE.Vector2(0.5, 0.5) }, mouseInfluence: { value: 0 }, mouseInteractionRadius: { value: this.ncMouseInteractionRadius } }, blending: THREE.NormalBlending, transparent: true, depthTest: false, depthWrite: false }); this.mesh = new THREE.Mesh(geometry, this.material); this.scene.add(this.mesh); this.clock.start(); } onMouseMove(e) { if (!this.ncMouseInteraction || !this.containerRef) return; const rect = this.containerRef.nativeElement.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; const y = 1.0 - (e.clientY - rect.top) / rect.height; // Flip Y coordinate this.targetMouse.set(x, y); } onMouseEnter() { if (!this.ncMouseInteraction) return; this.targetInfluence = 1.0; } onMouseLeave() { if (!this.ncMouseInteraction) return; this.targetInfluence = 0.0; } setupResizeObserver() { const el = this.containerRef.nativeElement; this.resizeObserver = new ResizeObserver(() => { if (this.resizeRafId !== null) cancelAnimationFrame(this.resizeRafId); this.ngZone.runOutsideAngular(() => { this.resizeRafId = requestAnimationFrame(() => { if (!this.renderer || !el) return; const w = el.offsetWidth || 1; const h = el.offsetHeight || 1; this.renderer.setSize(w, h); if (this.material) { this.material.uniforms.iResolution.value.set(w, h); } this.resizeRafId = null; }); }); }); this.resizeObserver.observe(el); } setupIntersectionObserver() { const el = this.containerRef.nativeElement; this.intersectionObserver = new IntersectionObserver((entries) => { this.isVisible = entries[0].isIntersecting; if (this.isVisible) { if (this.rafId === null) { this.ngZone.runOutsideAngular(() => { this.startAnimation(); }); } } }, { threshold: [0, 0.01, 0.1] }); this.intersectionObserver.observe(el); } startAnimation() { if (this.isDestroyed) return; const render = () => { if (!this.isVisible) { this.rafId = null; return; } this.rafId = requestAnimationFrame(render); const t = this.clock.getElapsedTime(); if (this.material) { this.material.uniforms.iTime.value = t; // Mouse smoothing const lerpFactor = 0.1; this.mousePosition.x += (this.targetMouse.x - this.mousePosition.x) * lerpFactor; this.mousePosition.y += (this.targetMouse.y - this.mousePosition.y) * lerpFactor; const currentInfluence = this.material.uniforms.mouseInfluence.value; this.material.uniforms.mouseInfluence.value += (this.targetInfluence - currentInfluence) * 0.05; this.material.uniforms.mousePosition.value.copy(this.mousePosition); } this.renderer.render(this.scene, this.camera); }; render(); } cleanup() { var _a, _b, _c; if (this.rafId !== null) cancelAnimationFrame(this.rafId); if (this.resizeRafId !== null) cancelAnimationFrame(this.resizeRafId); (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect(); (_b = this.intersectionObserver) === null || _b === void 0 ? void 0 : _b.disconnect(); if (this.renderer) { const el = (_c = this.containerRef) === null || _c === void 0 ? void 0 : _c.nativeElement; if (el && this.renderer.domElement.parentNode === el) { el.removeChild(this.renderer.domElement); } this.renderer.dispose(); } } } RippleGridBackgroundComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: RippleGridBackgroundComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); RippleGridBackgroundComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.1.5", type: RippleGridBackgroundComponent, selector: "nc-ripple-grid-background", inputs: { ncGridColor: "ncGridColor", ncRippleIntensity: "ncRippleIntensity", ncGridSize: "ncGridSize", ncGridThickness: "ncGridThickness", ncFadeDistance: "ncFadeDistance", ncVignetteStrength: "ncVignetteStrength", ncGlowIntensity: "ncGlowIntensity", ncOpacity: "ncOpacity", ncGridRotation: "ncGridRotation", ncMouseInteraction: "ncMouseInteraction", ncMouseInteractionRadius: "ncMouseInteractionRadius", ncEnableRainbow: "ncEnableRainbow" }, host: { listeners: { "mousemove": "onMouseMove($event)", "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()" } }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div #container class=\"nc-ripple-grid-canvas-container\"></div>\r\n<div class=\"nc-content-wrapper\">\r\n <ng-content></ng-content>\r\n</div>\r\n", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden}.nc-ripple-grid-canvas-container{position:absolute;inset:0;z-index:0;background-color:#000}.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: RippleGridBackgroundComponent, decorators: [{ type: Component, args: [{ selector: 'nc-ripple-grid-background', templateUrl: './ripple-grid-background.component.html', styleUrls: ['./ripple-grid-background.component.less'] }] }], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { containerRef: [{ type: ViewChild, args: ['container', { static: true }] }], ncGridColor: [{ type: Input }], ncRippleIntensity: [{ type: Input }], ncGridSize: [{ type: Input }], ncGridThickness: [{ type: Input }], ncFadeDistance: [{ type: Input }], ncVignetteStrength: [{ type: Input }], ncGlowIntensity: [{ type: Input }], ncOpacity: [{ type: Input }], ncGridRotation: [{ type: Input }], ncMouseInteraction: [{ type: Input }], ncMouseInteractionRadius: [{ type: Input }], ncEnableRainbow: [{ type: Input }], onMouseMove: [{ type: HostListener, args: ['mousemove', ['$event']] }], onMouseEnter: [{ type: HostListener, args: ['mouseenter'] }], onMouseLeave: [{ type: HostListener, args: ['mouseleave'] }] } }); class NcRippleGridBackgroundModule { } NcRippleGridBackgroundModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcRippleGridBackgroundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NcRippleGridBackgroundModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcRippleGridBackgroundModule, declarations: [RippleGridBackgroundComponent], imports: [CommonModule], exports: [RippleGridBackgroundComponent] }); NcRippleGridBackgroundModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcRippleGridBackgroundModule, imports: [[ CommonModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcRippleGridBackgroundModule, decorators: [{ type: NgModule, args: [{ declarations: [ RippleGridBackgroundComponent ], imports: [ CommonModule ], exports: [ RippleGridBackgroundComponent ] }] }] }); /** * Generated bundle index. Do not edit. */ export { NcRippleGridBackgroundModule, RippleGridBackgroundComponent }; //# sourceMappingURL=ng-cw-v12-ripple-grid-background.js.map