UNPKG

ng-cw-v12

Version:

Angular UI Component Library

480 lines (449 loc) 18.5 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 = ` precision highp float; attribute vec3 position; attribute vec2 uv; varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); } `; const frag = ` precision highp float; uniform float iTime; uniform vec3 iResolution; uniform float hue; uniform float hover; uniform float rot; uniform float hoverIntensity; uniform vec3 backgroundColor; varying vec2 vUv; vec3 rgb2yiq(vec3 c) { float y = dot(c, vec3(0.299, 0.587, 0.114)); float i = dot(c, vec3(0.596, -0.274, -0.322)); float q = dot(c, vec3(0.211, -0.523, 0.312)); return vec3(y, i, q); } vec3 yiq2rgb(vec3 c) { float r = c.x + 0.956 * c.y + 0.621 * c.z; float g = c.x - 0.272 * c.y - 0.647 * c.z; float b = c.x - 1.106 * c.y + 1.703 * c.z; return vec3(r, g, b); } vec3 adjustHue(vec3 color, float hueDeg) { float hueRad = hueDeg * 3.14159265 / 180.0; vec3 yiq = rgb2yiq(color); float cosA = cos(hueRad); float sinA = sin(hueRad); float i = yiq.y * cosA - yiq.z * sinA; float q = yiq.y * sinA + yiq.z * cosA; yiq.y = i; yiq.z = q; return yiq2rgb(yiq); } vec3 hash33(vec3 p3) { p3 = fract(p3 * vec3(0.1031, 0.11369, 0.13787)); p3 += dot(p3, p3.yxz + 19.19); return -1.0 + 2.0 * fract(vec3( p3.x + p3.y, p3.x + p3.z, p3.y + p3.z ) * p3.zyx); } float snoise3(vec3 p) { const float K1 = 0.333333333; const float K2 = 0.166666667; vec3 i = floor(p + (p.x + p.y + p.z) * K1); vec3 d0 = p - (i - (i.x + i.y + i.z) * K2); vec3 e = step(vec3(0.0), d0 - d0.yzx); vec3 i1 = e * (1.0 - e.zxy); vec3 i2 = 1.0 - e.zxy * (1.0 - e); vec3 d1 = d0 - (i1 - K2); vec3 d2 = d0 - (i2 - K1); vec3 d3 = d0 - 0.5; vec4 h = max(0.6 - vec4( dot(d0, d0), dot(d1, d1), dot(d2, d2), dot(d3, d3) ), 0.0); vec4 n = h * h * h * h * vec4( dot(d0, hash33(i)), dot(d1, hash33(i + i1)), dot(d2, hash33(i + i2)), dot(d3, hash33(i + 1.0)) ); return dot(vec4(31.316), n); } vec4 extractAlpha(vec3 colorIn) { float a = max(max(colorIn.r, colorIn.g), colorIn.b); return vec4(colorIn.rgb / (a + 1e-5), a); } const vec3 baseColor1 = vec3(0.611765, 0.262745, 0.996078); const vec3 baseColor2 = vec3(0.298039, 0.760784, 0.913725); const vec3 baseColor3 = vec3(0.062745, 0.078431, 0.600000); const float innerRadius = 0.6; const float noiseScale = 0.65; float light1(float intensity, float attenuation, float dist) { return intensity / (1.0 + dist * attenuation); } float light2(float intensity, float attenuation, float dist) { return intensity / (1.0 + dist * dist * attenuation); } vec4 draw(vec2 uv) { vec3 color1 = adjustHue(baseColor1, hue); vec3 color2 = adjustHue(baseColor2, hue); vec3 color3 = adjustHue(baseColor3, hue); float ang = atan(uv.y, uv.x); float len = length(uv); float invLen = len > 0.0 ? 1.0 / len : 0.0; float bgLuminance = dot(backgroundColor, vec3(0.299, 0.587, 0.114)); float n0 = snoise3(vec3(uv * noiseScale, iTime * 0.5)) * 0.5 + 0.5; float r0 = mix(mix(innerRadius, 1.0, 0.4), mix(innerRadius, 1.0, 0.6), n0); float d0 = distance(uv, (r0 * invLen) * uv); float v0 = light1(1.0, 10.0, d0); v0 *= smoothstep(r0 * 1.05, r0, len); float innerFade = smoothstep(r0 * 0.8, r0 * 0.95, len); v0 *= mix(innerFade, 1.0, bgLuminance * 0.7); float cl = cos(ang + iTime * 2.0) * 0.5 + 0.5; float a = iTime * -1.0; vec2 pos = vec2(cos(a), sin(a)) * r0; float d = distance(uv, pos); float v1 = light2(1.5, 5.0, d); v1 *= light1(1.0, 50.0, d0); float v2 = smoothstep(1.0, mix(innerRadius, 1.0, n0 * 0.5), len); float v3 = smoothstep(innerRadius, mix(innerRadius, 1.0, 0.5), len); vec3 colBase = mix(color1, color2, cl); float fadeAmount = mix(1.0, 0.1, bgLuminance); vec3 darkCol = mix(color3, colBase, v0); darkCol = (darkCol + v1) * v2 * v3; darkCol = clamp(darkCol, 0.0, 1.0); vec3 lightCol = (colBase + v1) * mix(1.0, v2 * v3, fadeAmount); lightCol = mix(backgroundColor, lightCol, v0); lightCol = clamp(lightCol, 0.0, 1.0); vec3 finalCol = mix(darkCol, lightCol, bgLuminance); return extractAlpha(finalCol); } vec4 mainImage(vec2 fragCoord) { vec2 center = iResolution.xy * 0.5; float size = min(iResolution.x, iResolution.y); vec2 uv = (fragCoord - center) / size * 2.0; float angle = rot; float s = sin(angle); float c = cos(angle); uv = vec2(c * uv.x - s * uv.y, s * uv.x + c * uv.y); uv.x += hover * hoverIntensity * 0.1 * sin(uv.y * 10.0 + iTime); uv.y += hover * hoverIntensity * 0.1 * sin(uv.x * 10.0 + iTime); return draw(uv); } void main() { vec2 fragCoord = vUv * iResolution.xy; vec4 col = mainImage(fragCoord); gl_FragColor = vec4(col.rgb * col.a, col.a); } `; function hslToRgb(h, s, l) { let r, g, b; if (s === 0) { r = g = b = l; } else { const hue2rgb = (p, q, t) => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1 / 3); } return new THREE.Vector3(r, g, b); } function hexToVec3(color) { if (color.startsWith('#')) { const r = parseInt(color.slice(1, 3), 16) / 255; const g = parseInt(color.slice(3, 5), 16) / 255; const b = parseInt(color.slice(5, 7), 16) / 255; return new THREE.Vector3(r, g, b); } const rgbMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); if (rgbMatch) { return new THREE.Vector3(parseInt(rgbMatch[1], 10) / 255, parseInt(rgbMatch[2], 10) / 255, parseInt(rgbMatch[3], 10) / 255); } const hslMatch = color.match(/hsla?\((\d+),\s*(\d+)%,\s*(\d+)%/); if (hslMatch) { const h = parseInt(hslMatch[1], 10) / 360; const s = parseInt(hslMatch[2], 10) / 100; const l = parseInt(hslMatch[3], 10) / 100; return hslToRgb(h, s, l); } return new THREE.Vector3(0, 0, 0); } class OrbBackgroundComponent { constructor(ngZone) { this.ngZone = ngZone; /** 球体的基本色调(0-360)*/ this.ncHue = 0; /** 控制悬停扭曲效果的强度(0-5)*/ this.ncHoverIntensity = 2; /** 切换开关以启用或禁用鼠标悬停时的连续旋转 */ this._rotateOnHover = true; /** 即使球体实际上并未悬停,也强制显示悬停动画 */ this._forceHoverState = false; /** 容器的背景颜色,十六进制格式 */ this.ncBackgroundColor = '#000000'; this.rafId = null; this.resizeRafId = null; this.isVisible = true; this.targetHover = 0; this.lastTime = 0; this.currentRot = 0; this.rotationSpeed = 0.3; } set ncRotateOnHover(val) { this._rotateOnHover = val !== null && val !== undefined && val !== false && val !== 'false'; } get ncRotateOnHover() { return this._rotateOnHover; } set ncForceHoverState(val) { this._forceHoverState = val !== null && val !== undefined && val !== false && val !== 'false'; } get ncForceHoverState() { return this._forceHoverState; } ngOnInit() { } ngAfterViewInit() { this.initWebGL(); } ngOnDestroy() { this.cleanup(); } ngOnChanges(changes) { if (this.material) { if (changes['ncHue']) { this.material.uniforms['hue'].value = this.ncHue; } if (changes['ncHoverIntensity']) { this.material.uniforms['hoverIntensity'].value = this.ncHoverIntensity; } if (changes['ncBackgroundColor']) { this.material.uniforms['backgroundColor'].value = hexToVec3(this.ncBackgroundColor); } } } initWebGL() { const container = this.containerRef.nativeElement; this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, premultipliedAlpha: false }); this.renderer.setClearColor(0x000000, 0); // Transparent background matching WebGL clearColor(0,0,0,0) 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.RawShaderMaterial({ vertexShader: vert, fragmentShader: frag, transparent: false, blending: THREE.NoBlending, uniforms: { iTime: { value: 0 }, iResolution: { value: new THREE.Vector3(1, 1, 1) }, hue: { value: this.ncHue }, hover: { value: 0 }, rot: { value: 0 }, hoverIntensity: { value: this.ncHoverIntensity }, backgroundColor: { value: hexToVec3(this.ncBackgroundColor) } } }); this.plane = new THREE.Mesh(geometry, this.material); this.scene.add(this.plane); this.setupResizeObserver(container); this.setupIntersectionObserver(container); this.lastTime = performance.now(); this.ngZone.runOutsideAngular(() => { const loop = (t) => { this.rafId = requestAnimationFrame(loop); if (this.isVisible) { this.update(t); } }; loop(performance.now()); }); } update(t) { const dt = (t - this.lastTime) * 0.001; this.lastTime = t; this.material.uniforms['iTime'].value = t * 0.001; const effectiveHover = this.ncForceHoverState ? 1 : this.targetHover; this.material.uniforms['hover'].value += (effectiveHover - this.material.uniforms['hover'].value) * 0.1; if (this.ncRotateOnHover && effectiveHover > 0.5) { this.currentRot += dt * this.rotationSpeed; } this.material.uniforms['rot'].value = this.currentRot; this.renderer.render(this.scene, this.camera); } handleResize() { if (!this.containerRef || !this.renderer) return; const container = this.containerRef.nativeElement; const dpr = Math.min(window.devicePixelRatio || 1, 2); const width = container.clientWidth || 1; const height = container.clientHeight || 1; this.renderer.setPixelRatio(dpr); this.renderer.setSize(width, height, false); const canvas = this.renderer.domElement; canvas.style.width = '100%'; canvas.style.height = '100%'; canvas.style.display = 'block'; if (this.material) { this.material.uniforms['iResolution'].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.handleResize(); 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; }, { threshold: [0, 0.01, 0.1] }); this.intersectionObserver.observe(container); } cleanup() { var _a, _b; if (this.rafId !== null) { cancelAnimationFrame(this.rafId); this.rafId = null; } 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(); } } // ─── HostListeners for Mouse Interaction ────────────────────────── onMouseMove(event) { if (!this.containerRef) return; const container = this.containerRef.nativeElement; const rect = container.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; const width = rect.width; const height = rect.height; // Prevent division by zero if (width === 0 || height === 0) return; const size = Math.min(width, height); const centerX = width / 2; const centerY = height / 2; const uvX = ((x - centerX) / size) * 2.0; const uvY = ((y - centerY) / size) * 2.0; if (Math.sqrt(uvX * uvX + uvY * uvY) < 0.8) { this.targetHover = 1; } else { this.targetHover = 0; } } onMouseLeave() { this.targetHover = 0; } } OrbBackgroundComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: OrbBackgroundComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); OrbBackgroundComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.1.5", type: OrbBackgroundComponent, selector: "nc-orb-background", inputs: { ncHue: "ncHue", ncHoverIntensity: "ncHoverIntensity", ncRotateOnHover: "ncRotateOnHover", ncForceHoverState: "ncForceHoverState", ncBackgroundColor: "ncBackgroundColor" }, 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-orb-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-orb-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: OrbBackgroundComponent, decorators: [{ type: Component, args: [{ selector: 'nc-orb-background', templateUrl: './orb-background.component.html', styleUrls: ['./orb-background.component.less'] }] }], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { containerRef: [{ type: ViewChild, args: ['container', { static: true }] }], ncHue: [{ type: Input }], ncHoverIntensity: [{ type: Input }], ncRotateOnHover: [{ type: Input }], ncForceHoverState: [{ type: Input }], ncBackgroundColor: [{ type: Input }], onMouseMove: [{ type: HostListener, args: ['mousemove', ['$event']] }], onMouseLeave: [{ type: HostListener, args: ['mouseleave'] }] } }); class NcOrbBackgroundModule { } NcOrbBackgroundModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcOrbBackgroundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NcOrbBackgroundModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcOrbBackgroundModule, declarations: [OrbBackgroundComponent], imports: [CommonModule], exports: [OrbBackgroundComponent] }); NcOrbBackgroundModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcOrbBackgroundModule, imports: [[ CommonModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcOrbBackgroundModule, decorators: [{ type: NgModule, args: [{ declarations: [ OrbBackgroundComponent ], imports: [ CommonModule ], exports: [ OrbBackgroundComponent ] }] }] }); /** * Generated bundle index. Do not edit. */ export { NcOrbBackgroundModule, OrbBackgroundComponent }; //# sourceMappingURL=ng-cw-v12-orb-background.js.map