ng-cw-v12
Version:
Angular UI Component Library
416 lines (395 loc) • 18 kB
JavaScript
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