ng-cw-v12
Version:
Angular UI Component Library
313 lines (301 loc) • 14.1 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 vertexShader = `
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
`;
// ─── 片元着色器 ─────────────────────────────────────────────────────────────
const fragmentShader = `
precision highp float;
uniform float uTime;
uniform vec3 uColor;
uniform vec3 uResolution;
uniform vec2 uMouse;
uniform float uAmplitude;
uniform float uSpeed;
varying vec2 vUv;
void main() {
float mr = min(uResolution.x, uResolution.y);
vec2 uv = (vUv.xy * 2.0 - 1.0) * uResolution.xy / mr;
uv += (uMouse - vec2(0.5)) * uAmplitude;
float d = -uTime * 0.5 * uSpeed;
float a = 0.0;
for (float i = 0.0; i < 8.0; ++i) {
a += cos(i - d - a * uv.x);
d += sin(uv.y * i + a);
}
d += uTime * 0.5 * uSpeed;
vec3 col = vec3(cos(uv * vec2(d, a)) * 0.6 + 0.4, cos(a + d) * 0.5 + 0.5);
col = cos(col * cos(vec3(d, a, 2.5)) * 0.5 + 0.5) * uColor;
gl_FragColor = vec4(col, 1.0);
}
`;
class IridescenceBackgroundComponent {
constructor(ngZone) {
this.ngZone = ngZone;
/** 颜色 [r, g, b] 数组(0-1) */
this.ncColor = [0.5, 0.6, 0.8];
/** 动画速度倍率(0-2) */
this.ncSpeed = 1.0;
/** 鼠标驱动效应的振幅 */
this.ncAmplitude = 0.1;
/** 是否响应鼠标操作 */
this._mouseReact = true;
this.renderer = null;
this.scene = null;
this.camera = null;
this.material = null;
this.mesh = null;
this.rafId = null;
this.resizeRafId = null;
this.isVisible = true;
this.mousePos = new THREE.Vector2(0.5, 0.5);
}
set ncMouseReact(val) {
this._mouseReact = val !== null && val !== undefined && val !== false && val !== 'false';
}
get ncMouseReact() {
return this._mouseReact;
}
ngOnInit() { }
ngAfterViewInit() {
this.initWebGL();
}
ngOnDestroy() {
this.cleanup();
}
ngOnChanges(changes) {
if (!this.material)
return;
if (changes['ncColor']) {
this.material.uniforms['uColor'].value.setRGB(this.ncColor[0], this.ncColor[1], this.ncColor[2]);
}
if (changes['ncSpeed']) {
this.material.uniforms['uSpeed'].value = this.ncSpeed;
}
if (changes['ncAmplitude']) {
this.material.uniforms['uAmplitude'].value = this.ncAmplitude;
}
}
// ─── 初始化 WebGL ──────────────────────────────────────────────────────────
initWebGL() {
const container = this.containerRef.nativeElement;
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.renderer.setClearColor(0xffffff, 1);
// 限定最大 dpr,避免在超高分辨率屏幕上性能过低
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
container.appendChild(this.renderer.domElement);
this.scene = new THREE.Scene();
this.camera = new THREE.Camera(); // 空相机,不使用投影矩阵
const geometry = new THREE.PlaneGeometry(2, 2);
this.material = new THREE.RawShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
uniforms: {
uTime: { value: 0 },
uColor: { value: new THREE.Color(this.ncColor[0], this.ncColor[1], this.ncColor[2]) },
uResolution: { value: new THREE.Vector3() },
uMouse: { value: this.mousePos },
uAmplitude: { value: this.ncAmplitude },
uSpeed: { value: this.ncSpeed }
}
});
this.mesh = new THREE.Mesh(geometry, this.material);
this.scene.add(this.mesh);
this.setupResizeObserver(container);
this.setupIntersectionObserver(container);
this.resize();
this.ngZone.runOutsideAngular(() => {
this.startAnimation();
});
}
// ─── Resize 处理 ──────────────────────────────────────────────────────────
resize() {
const container = this.containerRef.nativeElement;
if (!container || !this.renderer || !this.material)
return;
const width = container.offsetWidth;
const height = container.offsetHeight;
this.renderer.setSize(width, height);
const canvas = this.renderer.domElement;
this.material.uniforms['uResolution'].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.resize();
this.resizeRafId = null;
});
});
});
this.resizeObserver.observe(container);
}
// ─── 渲染循环 ─────────────────────────────────────────────────────────────
startAnimation() {
const update = (t) => {
this.rafId = requestAnimationFrame(update);
if (this.isVisible && this.renderer && this.scene && this.camera && this.material) {
this.material.uniforms['uTime'].value = t * 0.001;
this.renderer.render(this.scene, this.camera);
}
};
update(performance.now());
}
stopAnimation() {
if (this.rafId !== null) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
}
// ─── IntersectionObserver:不可见时暂停渲染以节省性能 ────────────────────
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;
this.stopAnimation();
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();
}
if (this.material) {
this.material.dispose();
}
if (this.mesh && this.mesh.geometry) {
this.mesh.geometry.dispose();
}
this.renderer = null;
this.scene = null;
this.camera = null;
this.material = null;
this.mesh = null;
}
// ─── HostListeners for Mouse Interaction ─────────────────────────────────
onMouseMove(event) {
if (!this.ncMouseReact || !this.material || !this.containerRef)
return;
const container = this.containerRef.nativeElement;
const rect = container.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0)
return;
// 检查是否在容器内
if (event.clientX < rect.left ||
event.clientX > rect.right ||
event.clientY < rect.top ||
event.clientY > rect.bottom) {
return;
}
const x = (event.clientX - rect.left) / rect.width;
const y = 1.0 - (event.clientY - rect.top) / rect.height;
// 更新 THREE.Vector2
this.mousePos.set(x, y);
}
onTouchMove(event) {
if (!this.ncMouseReact || !this.material || !this.containerRef || event.touches.length === 0)
return;
const container = this.containerRef.nativeElement;
const rect = container.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0)
return;
const touch = event.touches[0];
// 检查是否在容器内
if (touch.clientX < rect.left ||
touch.clientX > rect.right ||
touch.clientY < rect.top ||
touch.clientY > rect.bottom) {
return;
}
const x = (touch.clientX - rect.left) / rect.width;
const y = 1.0 - (touch.clientY - rect.top) / rect.height;
// 更新 THREE.Vector2
this.mousePos.set(x, y);
}
}
IridescenceBackgroundComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: IridescenceBackgroundComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
IridescenceBackgroundComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.1.5", type: IridescenceBackgroundComponent, selector: "nc-iridescence-background", inputs: { ncColor: "ncColor", ncSpeed: "ncSpeed", ncAmplitude: "ncAmplitude", ncMouseReact: "ncMouseReact" }, host: { listeners: { "mousemove": "onMouseMove($event)", "touchmove": "onTouchMove($event)" } }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div #container class=\"nc-iridescence-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-iridescence-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: IridescenceBackgroundComponent, decorators: [{
type: Component,
args: [{
selector: 'nc-iridescence-background',
templateUrl: './iridescence-background.component.html',
styleUrls: ['./iridescence-background.component.less']
}]
}], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { containerRef: [{
type: ViewChild,
args: ['container', { static: true }]
}], ncColor: [{
type: Input
}], ncSpeed: [{
type: Input
}], ncAmplitude: [{
type: Input
}], ncMouseReact: [{
type: Input
}], onMouseMove: [{
type: HostListener,
args: ['mousemove', ['$event']]
}], onTouchMove: [{
type: HostListener,
args: ['touchmove', ['$event']]
}] } });
class NcIridescenceBackgroundModule {
}
NcIridescenceBackgroundModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcIridescenceBackgroundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
NcIridescenceBackgroundModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcIridescenceBackgroundModule, declarations: [IridescenceBackgroundComponent], imports: [CommonModule], exports: [IridescenceBackgroundComponent] });
NcIridescenceBackgroundModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcIridescenceBackgroundModule, imports: [[
CommonModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcIridescenceBackgroundModule, decorators: [{
type: NgModule,
args: [{
declarations: [
IridescenceBackgroundComponent
],
imports: [
CommonModule
],
exports: [
IridescenceBackgroundComponent
]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { IridescenceBackgroundComponent, NcIridescenceBackgroundModule };
//# sourceMappingURL=ng-cw-v12-iridescence-background.js.map