ng-cw-v12
Version:
Angular UI Component Library
591 lines (568 loc) • 24.4 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 = `
precision highp float;
attribute vec3 position;
void main() {
gl_Position = vec4(position.xy, 0.0, 1.0);
}
`;
const fragmentShader = `
precision highp float;
uniform vec2 iResolution;
uniform float iTime;
uniform float uHeight;
uniform float uBaseHalf;
uniform mat3 uRot;
uniform int uUseBaseWobble;
uniform float uGlow;
uniform vec2 uOffsetPx;
uniform float uNoise;
uniform float uSaturation;
uniform float uScale;
uniform float uHueShift;
uniform float uColorFreq;
uniform float uBloom;
uniform float uCenterShift;
uniform float uInvBaseHalf;
uniform float uInvHeight;
uniform float uMinAxis;
uniform float uPxScale;
uniform float uTimeScale;
vec4 tanh4(vec4 x){
vec4 e2x = exp(2.0*x);
return (e2x - 1.0) / (e2x + 1.0);
}
float rand(vec2 co){
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453123);
}
float sdOctaAnisoInv(vec3 p){
vec3 q = vec3(abs(p.x) * uInvBaseHalf, abs(p.y) * uInvHeight, abs(p.z) * uInvBaseHalf);
float m = q.x + q.y + q.z - 1.0;
return m * uMinAxis * 0.5773502691896258;
}
float sdPyramidUpInv(vec3 p){
float oct = sdOctaAnisoInv(p);
float halfSpace = -p.y;
return max(oct, halfSpace);
}
mat3 hueRotation(float a){
float c = cos(a), s = sin(a);
mat3 W = mat3(
0.299, 0.299, 0.299,
0.587, 0.587, 0.587,
0.114, 0.114, 0.114
);
mat3 U = mat3(
0.701, -0.299, -0.300,
-0.587, 0.413, -0.588,
-0.114, -0.114, 0.886
);
mat3 V = mat3(
0.168, 0.328, -0.497,
-0.331, 0.035, 0.296,
0.500, -0.500, 0.201
);
return W + U * c + V * s;
}
void main(){
vec2 f = (gl_FragCoord.xy - 0.5 * iResolution.xy - uOffsetPx) * uPxScale;
float z = 5.0;
float d = 0.0;
vec3 p;
vec4 o = vec4(0.0);
float centerShift = uCenterShift;
float cf = uColorFreq;
mat2 wob = mat2(1.0);
if (uUseBaseWobble == 1) {
float t = iTime * uTimeScale;
float c0 = cos(t + 0.0);
float c1 = cos(t + 33.0);
float c2 = cos(t + 11.0);
wob = mat2(c0, c1, c2, c0);
}
const int STEPS = 100;
for (int i = 0; i < STEPS; i++) {
p = vec3(f, z);
p.xz = p.xz * wob;
p = uRot * p;
vec3 q = p;
q.y += centerShift;
d = 0.1 + 0.2 * abs(sdPyramidUpInv(q));
z -= d;
o += (sin((p.y + z) * cf + vec4(0.0, 1.0, 2.0, 3.0)) + 1.0) / d;
}
o = tanh4(o * o * (uGlow * uBloom) / 1e5);
vec3 col = o.rgb;
float n = rand(gl_FragCoord.xy + vec2(iTime));
col += (n - 0.5) * uNoise;
col = clamp(col, 0.0, 1.0);
float L = dot(col, vec3(0.2126, 0.7152, 0.0722));
col = clamp(mix(vec3(L), col, uSaturation), 0.0, 1.0);
if(abs(uHueShift) > 0.0001){
col = clamp(hueRotation(uHueShift) * col, 0.0, 1.0);
}
gl_FragColor = vec4(col, o.a);
}
`;
class PrismBackgroundComponent {
constructor(ngZone) {
this.ngZone = ngZone;
/** 棱镜高度(1-8) */
this.ncHeight = 3.5;
/** 棱镜基础宽度(1-8) */
this.ncBaseWidth = 5.5;
/** 动画类型:rotate, hover, 3drotate */
this.ncAnimationType = 'rotate';
/** 发光强度(0-3) */
this.ncGlow = 1;
/** 偏移量 {x, y} */
this.ncOffset = { x: 0, y: 0 };
/** 噪点强度(0-1) */
this.ncNoise = 0;
/** 画布是否具有 alpha 通道(透明背景) */
this._transparent = true;
/** 棱镜在屏幕空间中的整体比例(1-5) */
this.ncScale = 3.6;
/** 色相偏移(-3.14-3.14) */
this.ncHueShift = 0;
/** 颜色变化频率(0-4) */
this.ncColorFrequency = 1;
/** 悬停反馈强度 */
this.ncHoverStrength = 2;
/** 悬停缓动系数(0-1,数值越高反应越灵敏) */
this.ncInertia = 0.05;
/** 在光泽之上叠加额外的光晕效果(0.5-2) */
this.ncBloom = 1;
/** 若不在屏幕中,是否挂起以节省性能 */
this._suspendWhenOffscreen = false;
/** 动画速度(0-2) */
this.ncTimeScale = 0.5;
/** 背景颜色 */
this.ncBgColor = '#000000';
this.rafId = null;
this.dpr = 1;
this.pointer = { x: 0, y: 0, inside: false };
this.yaw = 0;
this.pitch = 0;
this.roll = 0;
this.targetYaw = 0;
this.targetPitch = 0;
this.wX = 0;
this.wY = 0;
this.wZ = 0;
this.phX = 0;
this.phZ = 0;
this.isVisible = true;
this.t0 = performance.now();
this.renderLoop = (t) => {
var _a, _b, _c;
if (!this.material || !this.renderer)
return;
const time = (t - this.t0) * 0.001;
this.material.uniforms['iTime'].value = time;
let continueRAF = true;
const NOISE_IS_ZERO = Math.max(0.0, this.ncNoise) < 1e-6;
const TS = Math.max(0, (_a = this.ncTimeScale) !== null && _a !== void 0 ? _a : 1);
const HOVSTR = Math.max(0, (_b = this.ncHoverStrength) !== null && _b !== void 0 ? _b : 1);
const INERT = Math.max(0, Math.min(1, (_c = this.ncInertia) !== null && _c !== void 0 ? _c : 0.12));
if (this.ncAnimationType === 'hover') {
this.material.uniforms['uUseBaseWobble'].value = 0;
const maxPitch = 0.6 * HOVSTR;
const maxYaw = 0.6 * HOVSTR;
this.targetYaw = (this.pointer.inside ? -this.pointer.x : 0) * maxYaw;
this.targetPitch = (this.pointer.inside ? this.pointer.y : 0) * maxPitch;
this.yaw = this.lerp(this.yaw, this.targetYaw, INERT);
this.pitch = this.lerp(this.pitch, this.targetPitch, INERT);
this.roll = this.lerp(this.roll, 0, 0.1);
this.material.uniforms['uRot'].value.copy(this.setMat3FromEuler(this.yaw, this.pitch, this.roll));
if (NOISE_IS_ZERO) {
const settled = Math.abs(this.yaw - this.targetYaw) < 1e-4 &&
Math.abs(this.pitch - this.targetPitch) < 1e-4 &&
Math.abs(this.roll) < 1e-4;
if (settled)
continueRAF = false;
}
}
else if (this.ncAnimationType === '3drotate') {
this.material.uniforms['uUseBaseWobble'].value = 0;
const tScaled = time * TS;
this.yaw = tScaled * this.wY;
this.pitch = Math.sin(tScaled * this.wX + this.phX) * 0.6;
this.roll = Math.sin(tScaled * this.wZ + this.phZ) * 0.5;
this.material.uniforms['uRot'].value.copy(this.setMat3FromEuler(this.yaw, this.pitch, this.roll));
if (TS < 1e-6)
continueRAF = false;
}
else {
this.material.uniforms['uUseBaseWobble'].value = 1;
const mat = new THREE.Matrix3();
mat.identity();
this.material.uniforms['uRot'].value.copy(mat);
if (TS < 1e-6)
continueRAF = false;
}
this.renderer.render(this.scene, this.camera);
if (continueRAF && this.isVisible) {
this.rafId = requestAnimationFrame(this.renderLoop);
}
else {
this.rafId = null;
}
};
}
set ncTransparent(val) {
this._transparent = val !== null && val !== undefined && val !== false && val !== 'false';
}
get ncTransparent() {
return this._transparent;
}
set ncSuspendWhenOffscreen(val) {
this._suspendWhenOffscreen = val !== null && val !== undefined && val !== false && val !== 'false';
}
get ncSuspendWhenOffscreen() {
return this._suspendWhenOffscreen;
}
ngOnInit() {
const rnd = () => Math.random();
this.wX = 0.3 + rnd() * 0.6;
this.wY = 0.2 + rnd() * 0.7;
this.wZ = 0.1 + rnd() * 0.5;
this.phX = rnd() * Math.PI * 2;
this.phZ = rnd() * Math.PI * 2;
}
ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => {
this.initWebGL();
});
}
ngOnDestroy() {
this.cleanup();
}
ngOnChanges(changes) {
var _a;
this.updateUniforms();
// 当参数发生改变时,如果动画不在循环状态下(比如时间缩放由0变回1),应该恢复渲染循环
if (this.isVisible) {
this.ngZone.runOutsideAngular(() => this.startRAF());
}
// 如果离屏挂起状态改变,需要特殊处理
if (changes['ncSuspendWhenOffscreen']) {
if (this.ncSuspendWhenOffscreen && ((_a = this.containerRef) === null || _a === void 0 ? void 0 : _a.nativeElement)) {
if (!this.intersectionObserver) {
this.setupIntersectionObserver(this.containerRef.nativeElement);
}
}
else {
if (this.intersectionObserver) {
this.intersectionObserver.disconnect();
this.intersectionObserver = null;
}
if (this.renderer) {
this.ngZone.runOutsideAngular(() => this.startRAF());
}
}
}
}
initWebGL() {
var _a, _b, _c, _d;
const container = this.containerRef.nativeElement;
this.dpr = Math.min(2, window.devicePixelRatio || 1);
this.renderer = new THREE.WebGLRenderer({
alpha: this.ncTransparent,
antialias: false
});
this.renderer.setPixelRatio(this.dpr);
Object.assign(this.renderer.domElement.style, {
position: 'absolute',
inset: '0',
width: '100%',
height: '100%',
display: 'block'
});
container.appendChild(this.renderer.domElement);
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const H = Math.max(0.001, this.ncHeight);
const BW = Math.max(0.001, this.ncBaseWidth);
const BASE_HALF = BW * 0.5;
this.material = new THREE.RawShaderMaterial({
vertexShader,
fragmentShader,
transparent: this.ncTransparent,
depthTest: false,
depthWrite: false,
uniforms: {
iResolution: { value: new THREE.Vector2() },
iTime: { value: 0 },
uHeight: { value: H },
uBaseHalf: { value: BASE_HALF },
uUseBaseWobble: { value: 1 },
uRot: { value: new THREE.Matrix3() },
uGlow: { value: Math.max(0.0, this.ncGlow) },
uOffsetPx: { value: new THREE.Vector2(this.ncOffset.x * this.dpr, this.ncOffset.y * this.dpr) },
uNoise: { value: Math.max(0.0, this.ncNoise) },
uSaturation: { value: this.ncTransparent ? 1.5 : 1 },
uScale: { value: Math.max(0.001, this.ncScale) },
uHueShift: { value: (_a = this.ncHueShift) !== null && _a !== void 0 ? _a : 0 },
uColorFreq: { value: Math.max(0.0, (_b = this.ncColorFrequency) !== null && _b !== void 0 ? _b : 1) },
uBloom: { value: Math.max(0.0, (_c = this.ncBloom) !== null && _c !== void 0 ? _c : 1) },
uCenterShift: { value: H * 0.25 },
uInvBaseHalf: { value: 1 / BASE_HALF },
uInvHeight: { value: 1 / H },
uMinAxis: { value: Math.min(BASE_HALF, H) },
uPxScale: { value: 1 },
uTimeScale: { value: Math.max(0, (_d = this.ncTimeScale) !== null && _d !== void 0 ? _d : 1) }
}
});
const geometry = new THREE.PlaneGeometry(2, 2);
this.mesh = new THREE.Mesh(geometry, this.material);
this.scene.add(this.mesh);
this.setupResizeObserver(container);
if (this.ncSuspendWhenOffscreen) {
this.setupIntersectionObserver(container);
}
else {
this.startRAF();
}
}
updateUniforms() {
var _a, _b, _c, _d;
if (!this.material)
return;
const H = Math.max(0.001, this.ncHeight);
const BW = Math.max(0.001, this.ncBaseWidth);
const BASE_HALF = BW * 0.5;
const SCALE = Math.max(0.001, this.ncScale);
this.material.uniforms['uHeight'].value = H;
this.material.uniforms['uBaseHalf'].value = BASE_HALF;
this.material.uniforms['uGlow'].value = Math.max(0.0, this.ncGlow);
this.material.uniforms['uOffsetPx'].value.set(this.ncOffset.x * this.dpr, this.ncOffset.y * this.dpr);
this.material.uniforms['uNoise'].value = Math.max(0.0, this.ncNoise);
this.material.uniforms['uSaturation'].value = this.ncTransparent ? 1.5 : 1;
this.material.uniforms['uScale'].value = SCALE;
this.material.uniforms['uHueShift'].value = (_a = this.ncHueShift) !== null && _a !== void 0 ? _a : 0;
this.material.uniforms['uColorFreq'].value = Math.max(0.0, (_b = this.ncColorFrequency) !== null && _b !== void 0 ? _b : 1);
this.material.uniforms['uBloom'].value = Math.max(0.0, (_c = this.ncBloom) !== null && _c !== void 0 ? _c : 1);
this.material.uniforms['uCenterShift'].value = H * 0.25;
this.material.uniforms['uInvBaseHalf'].value = 1 / BASE_HALF;
this.material.uniforms['uInvHeight'].value = 1 / H;
this.material.uniforms['uMinAxis'].value = Math.min(BASE_HALF, H);
this.material.uniforms['uTimeScale'].value = Math.max(0, (_d = this.ncTimeScale) !== null && _d !== void 0 ? _d : 1);
// 更新需要依赖高度和缩放的 uPxScale
const canvasHeight = this.renderer.domElement.height || 1;
this.material.uniforms['uPxScale'].value = 1 / (canvasHeight * 0.1 * SCALE);
// 更新 renderer 的 alpha
if (this.renderer) {
// 通过直接重建背景是否透明
this.renderer.setClearColor(0x000000, this.ncTransparent ? 0 : 1);
this.material.transparent = this.ncTransparent;
}
}
setupResizeObserver(container) {
this.resizeObserver = new ResizeObserver(() => {
this.ngZone.runOutsideAngular(() => {
if (!this.renderer || !container)
return;
const w = container.clientWidth || 1;
const h = container.clientHeight || 1;
this.renderer.setSize(w, h, false);
const canvas = this.renderer.domElement;
this.material.uniforms['iResolution'].value.set(canvas.width, canvas.height);
const SCALE = Math.max(0.001, this.ncScale);
this.material.uniforms['uPxScale'].value = 1 / ((canvas.height || 1) * 0.1 * SCALE);
});
});
this.resizeObserver.observe(container);
}
setupIntersectionObserver(container) {
this.intersectionObserver = new IntersectionObserver(entries => {
const vis = entries.some(e => e.isIntersecting);
this.isVisible = vis;
if (vis) {
this.ngZone.runOutsideAngular(() => this.startRAF());
}
else {
this.stopRAF();
}
});
this.intersectionObserver.observe(container);
}
lerp(a, b, t) {
return a + (b - a) * t;
}
setMat3FromEuler(yawY, pitchX, rollZ) {
const cy = Math.cos(yawY), sy = Math.sin(yawY);
const cx = Math.cos(pitchX), sx = Math.sin(pitchX);
const cz = Math.cos(rollZ), sz = Math.sin(rollZ);
const r00 = cy * cz + sy * sx * sz;
const r01 = -cy * sz + sy * sx * cz;
const r02 = sy * cx;
const r10 = cx * sz;
const r11 = cx * cz;
const r12 = -sx;
const r20 = -sy * cz + cy * sx * sz;
const r21 = sy * sz + cy * sx * cz;
const r22 = cy * cx;
const mat = new THREE.Matrix3();
mat.set(r00, r01, r02, r10, r11, r12, r20, r21, r22);
return mat;
}
startRAF() {
if (this.rafId === null) {
this.rafId = requestAnimationFrame(this.renderLoop);
}
}
stopRAF() {
if (this.rafId !== null) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
}
cleanup() {
var _a, _b;
this.stopRAF();
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();
this.renderer = null;
}
if (this.geometry) {
this.geometry.dispose();
}
if (this.material) {
this.material.dispose();
}
}
// 为了能够在 cleanup 中使用
get geometry() {
var _a;
return (_a = this.mesh) === null || _a === void 0 ? void 0 : _a.geometry;
}
// ─── 鼠标事件 (HostListener) ────────────────────────────────────────────────
onPointerMove(e) {
if (this.ncAnimationType !== 'hover')
return;
const ww = Math.max(1, window.innerWidth);
const wh = Math.max(1, window.innerHeight);
const cx = ww * 0.5;
const cy = wh * 0.5;
const nx = (e.clientX - cx) / (ww * 0.5);
const ny = (e.clientY - cy) / (wh * 0.5);
this.pointer.x = Math.max(-1, Math.min(1, nx));
this.pointer.y = Math.max(-1, Math.min(1, ny));
this.pointer.inside = true;
// 如果动画之前停止了(比如因为 inertia settled),此时重新唤醒
this.ngZone.runOutsideAngular(() => this.startRAF());
}
onMouseLeave() {
if (this.ncAnimationType !== 'hover')
return;
this.pointer.inside = false;
this.ngZone.runOutsideAngular(() => this.startRAF());
}
onBlur() {
if (this.ncAnimationType !== 'hover')
return;
this.pointer.inside = false;
this.ngZone.runOutsideAngular(() => this.startRAF());
}
}
PrismBackgroundComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: PrismBackgroundComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
PrismBackgroundComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.1.5", type: PrismBackgroundComponent, selector: "nc-prism-background", inputs: { ncHeight: "ncHeight", ncBaseWidth: "ncBaseWidth", ncAnimationType: "ncAnimationType", ncGlow: "ncGlow", ncOffset: "ncOffset", ncNoise: "ncNoise", ncTransparent: "ncTransparent", ncScale: "ncScale", ncHueShift: "ncHueShift", ncColorFrequency: "ncColorFrequency", ncHoverStrength: "ncHoverStrength", ncInertia: "ncInertia", ncBloom: "ncBloom", ncSuspendWhenOffscreen: "ncSuspendWhenOffscreen", ncTimeScale: "ncTimeScale", ncBgColor: "ncBgColor" }, host: { listeners: { "window:pointermove": "onPointerMove($event)", "window:mouseleave": "onMouseLeave()", "window:blur": "onBlur()" } }, viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div #container class=\"nc-prism-canvas-container\" [style.background-color]=\"ncBgColor\"></div>\n<div class=\"nc-content-wrapper\">\n <ng-content></ng-content>\n</div>", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden}.nc-prism-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: PrismBackgroundComponent, decorators: [{
type: Component,
args: [{
selector: 'nc-prism-background',
templateUrl: './prism-background.component.html',
styleUrls: ['./prism-background.component.less']
}]
}], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { containerRef: [{
type: ViewChild,
args: ['container', { static: true }]
}], ncHeight: [{
type: Input
}], ncBaseWidth: [{
type: Input
}], ncAnimationType: [{
type: Input
}], ncGlow: [{
type: Input
}], ncOffset: [{
type: Input
}], ncNoise: [{
type: Input
}], ncTransparent: [{
type: Input
}], ncScale: [{
type: Input
}], ncHueShift: [{
type: Input
}], ncColorFrequency: [{
type: Input
}], ncHoverStrength: [{
type: Input
}], ncInertia: [{
type: Input
}], ncBloom: [{
type: Input
}], ncSuspendWhenOffscreen: [{
type: Input
}], ncTimeScale: [{
type: Input
}], ncBgColor: [{
type: Input
}], onPointerMove: [{
type: HostListener,
args: ['window:pointermove', ['$event']]
}], onMouseLeave: [{
type: HostListener,
args: ['window:mouseleave']
}], onBlur: [{
type: HostListener,
args: ['window:blur']
}] } });
class NcPrismBackgroundModule {
}
NcPrismBackgroundModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcPrismBackgroundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
NcPrismBackgroundModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcPrismBackgroundModule, declarations: [PrismBackgroundComponent], imports: [CommonModule], exports: [PrismBackgroundComponent] });
NcPrismBackgroundModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcPrismBackgroundModule, imports: [[
CommonModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcPrismBackgroundModule, decorators: [{
type: NgModule,
args: [{
declarations: [
PrismBackgroundComponent
],
imports: [
CommonModule
],
exports: [
PrismBackgroundComponent
]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { NcPrismBackgroundModule, PrismBackgroundComponent };
//# sourceMappingURL=ng-cw-v12-prism-background.js.map