ng-cw-v12
Version:
Angular UI Component Library
380 lines (358 loc) • 15.3 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 = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}
`;
const fragmentShader = `
precision highp float;
uniform float iTime;
uniform vec3 iResolution;
uniform vec3 uColor;
uniform float uAmplitude;
uniform float uDistance;
uniform vec2 uMouse;
#define PI 3.1415926538
const int u_line_count = 40;
const float u_line_width = 7.0;
const float u_line_blur = 10.0;
float Perlin2D(vec2 P) {
vec2 Pi = floor(P);
vec4 Pf_Pfmin1 = P.xyxy - vec4(Pi, Pi + 1.0);
vec4 Pt = vec4(Pi.xy, Pi.xy + 1.0);
Pt = Pt - floor(Pt * (1.0 / 71.0)) * 71.0;
Pt += vec2(26.0, 161.0).xyxy;
Pt *= Pt;
Pt = Pt.xzxz * Pt.yyww;
vec4 hash_x = fract(Pt * (1.0 / 951.135664));
vec4 hash_y = fract(Pt * (1.0 / 642.949883));
vec4 grad_x = hash_x - 0.49999;
vec4 grad_y = hash_y - 0.49999;
vec4 grad_results = inversesqrt(grad_x * grad_x + grad_y * grad_y)
* (grad_x * Pf_Pfmin1.xzxz + grad_y * Pf_Pfmin1.yyww);
grad_results *= 1.4142135623730950;
vec2 blend = Pf_Pfmin1.xy * Pf_Pfmin1.xy * Pf_Pfmin1.xy
* (Pf_Pfmin1.xy * (Pf_Pfmin1.xy * 6.0 - 15.0) + 10.0);
vec4 blend2 = vec4(blend, vec2(1.0 - blend));
return dot(grad_results, blend2.zxzx * blend2.wwyy);
}
float pixel(float count, vec2 resolution) {
return (1.0 / max(resolution.x, resolution.y)) * count;
}
float lineFn(vec2 st, float width, float perc, float offset, vec2 mouse, float time, float amplitude, float distance) {
float split_offset = (perc * 0.4);
float split_point = 0.1 + split_offset;
float amplitude_normal = smoothstep(split_point, 0.7, st.x);
float amplitude_strength = 0.5;
float finalAmplitude = amplitude_normal * amplitude_strength
* amplitude * (1.0 + (mouse.y - 0.5) * 0.2);
float time_scaled = time / 10.0 + (mouse.x - 0.5) * 1.0;
float blur = smoothstep(split_point, split_point + 0.05, st.x) * perc;
float xnoise = mix(
Perlin2D(vec2(time_scaled, st.x + perc) * 2.5),
Perlin2D(vec2(time_scaled, st.x + time_scaled) * 3.5) / 1.5,
st.x * 0.3
);
float y = 0.5 + (perc - 0.5) * distance + xnoise / 2.0 * finalAmplitude;
float line_start = smoothstep(
y + (width / 2.0) + (u_line_blur * pixel(1.0, iResolution.xy) * blur),
y,
st.y
);
float line_end = smoothstep(
y,
y - (width / 2.0) - (u_line_blur * pixel(1.0, iResolution.xy) * blur),
st.y
);
return clamp(
(line_start - line_end) * (1.0 - smoothstep(0.0, 1.0, pow(perc, 0.3))),
0.0,
1.0
);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
float line_strength = 1.0;
for (int i = 0; i < u_line_count; i++) {
float p = float(i) / float(u_line_count);
line_strength *= (1.0 - lineFn(
uv,
u_line_width * pixel(1.0, iResolution.xy) * (1.0 - p),
p,
(PI * 1.0) * p,
uMouse,
iTime,
uAmplitude,
uDistance
));
}
float colorVal = 1.0 - line_strength;
fragColor = vec4(uColor * colorVal, colorVal);
}
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
`;
class ThreadsBackgroundComponent {
constructor(ngZone) {
this.ngZone = ngZone;
/** 颜色 [r, g, b] 数组(0-1) */
this.ncColor = [1, 1, 1];
/** 振幅大小(0-5) */
this.ncAmplitude = 1;
/** 线条距离(0-2) */
this.ncDistance = 0;
/** 背景颜色 */
this.ncBgColor = 'black';
/** 是否启用鼠标交互 */
this._enableMouseInteraction = false;
this.animationFrameId = null;
this.isVisible = true;
this.isRunning = false;
this.currentMouse = new THREE.Vector2(0.5, 0.5);
this.targetMouse = new THREE.Vector2(0.5, 0.5);
this.onVisibilityChange = () => {
if (document.hidden) {
this.pause();
}
else if (this.isVisible) {
this.ngZone.runOutsideAngular(() => {
this.start();
});
}
};
this.update = (t) => {
if (!this.isRunning)
return;
if (this.ncEnableMouseInteraction) {
const smoothing = 0.05;
this.currentMouse.x += smoothing * (this.targetMouse.x - this.currentMouse.x);
this.currentMouse.y += smoothing * (this.targetMouse.y - this.currentMouse.y);
this.material.uniforms['uMouse'].value.copy(this.currentMouse);
}
else {
this.material.uniforms['uMouse'].value.set(0.5, 0.5);
}
this.material.uniforms['iTime'].value = t * 0.001;
this.renderer.render(this.scene, this.camera);
this.animationFrameId = requestAnimationFrame(this.update);
};
}
set ncEnableMouseInteraction(val) {
this._enableMouseInteraction = val !== null && val !== undefined && val !== false && val !== 'false';
}
get ncEnableMouseInteraction() {
return this._enableMouseInteraction;
}
ngOnInit() { }
ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => {
this.initWebGL();
});
}
ngOnDestroy() {
this.cleanup();
}
ngOnChanges(changes) {
if (this.material) {
if (changes['ncColor']) {
this.material.uniforms['uColor'].value.copy(this.getThreeColor(this.ncColor));
}
if (changes['ncAmplitude']) {
this.material.uniforms['uAmplitude'].value = this.ncAmplitude;
}
if (changes['ncDistance']) {
this.material.uniforms['uDistance'].value = this.ncDistance;
}
}
}
getThreeColor(color) {
if (Array.isArray(color)) {
return new THREE.Color(color[0], color[1], color[2]);
}
return new THREE.Color(color);
}
initWebGL() {
const container = this.containerRef.nativeElement;
// 初始化渲染器
this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: false });
this.renderer.setClearColor(0x000000, 0);
// 采用与 Threads.jsx 中 blendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA) 一致的默认混合方式
container.appendChild(this.renderer.domElement);
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
this.geometry = new THREE.PlaneGeometry(2, 2);
// 初始分辨率设定,后续由 resize 更新
const canvasWidth = container.clientWidth || 1;
const canvasHeight = container.clientHeight || 1;
this.material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
transparent: true,
uniforms: {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector3(canvasWidth, canvasHeight, canvasWidth / canvasHeight) },
uColor: { value: this.getThreeColor(this.ncColor) },
uAmplitude: { value: this.ncAmplitude },
uDistance: { value: this.ncDistance },
uMouse: { value: new THREE.Vector2(0.5, 0.5) }
}
});
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.scene.add(this.mesh);
this.setupResizeObserver(container);
this.setupIntersectionObserver(container);
this.resize();
this.start();
}
setupResizeObserver(container) {
this.resizeObserver = new ResizeObserver(() => {
this.ngZone.runOutsideAngular(() => {
this.resize();
});
});
this.resizeObserver.observe(container);
}
setupIntersectionObserver(container) {
this.intersectionObserver = new IntersectionObserver(entries => {
const entry = entries[0];
this.isVisible = entry.isIntersecting && entry.intersectionRatio > 0;
if (this.isVisible && !document.hidden) {
this.ngZone.runOutsideAngular(() => {
this.start();
});
}
else {
this.pause();
}
}, { threshold: [0, 0.01, 0.1] });
this.intersectionObserver.observe(container);
document.addEventListener('visibilitychange', this.onVisibilityChange);
}
resize() {
if (!this.containerRef || !this.renderer)
return;
const container = this.containerRef.nativeElement;
const width = container.clientWidth;
const height = container.clientHeight;
this.renderer.setSize(width, height);
// 可选设置 pixelRatio,如不需要极高精度可保留 1 提高性能
// this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.material.uniforms['iResolution'].value.set(width, height, (width / Math.max(height, 1)));
}
start() {
if (this.isRunning)
return;
this.isRunning = true;
this.animationFrameId = requestAnimationFrame(this.update);
}
pause() {
this.isRunning = false;
if (this.animationFrameId !== null) {
cancelAnimationFrame(this.animationFrameId);
this.animationFrameId = null;
}
}
cleanup() {
var _a, _b;
this.pause();
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;
}
document.removeEventListener('visibilitychange', this.onVisibilityChange);
if (this.renderer) {
const canvas = this.renderer.domElement;
if (canvas && canvas.parentNode)
canvas.parentNode.removeChild(canvas);
this.renderer.dispose();
this.renderer.forceContextLoss();
}
if (this.geometry)
this.geometry.dispose();
if (this.material)
this.material.dispose();
}
onMouseMove(event) {
if (!this.ncEnableMouseInteraction || !this.containerRef)
return;
const container = this.containerRef.nativeElement;
const rect = container.getBoundingClientRect();
const x = (event.clientX - rect.left) / rect.width;
const y = 1.0 - (event.clientY - rect.top) / rect.height;
this.targetMouse.set(x, y);
}
onMouseLeave() {
if (!this.ncEnableMouseInteraction)
return;
this.targetMouse.set(0.5, 0.5);
}
}
ThreadsBackgroundComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: ThreadsBackgroundComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
ThreadsBackgroundComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.1.5", type: ThreadsBackgroundComponent, selector: "nc-threads-background", inputs: { ncColor: "ncColor", ncAmplitude: "ncAmplitude", ncDistance: "ncDistance", ncBgColor: "ncBgColor", ncEnableMouseInteraction: "ncEnableMouseInteraction" }, 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-threads-canvas-container\" [style.background-color]=\"ncBgColor\"></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-threads-canvas-container{position:absolute;inset:0;z-index:0;pointer-events:none}.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: ThreadsBackgroundComponent, decorators: [{
type: Component,
args: [{
selector: 'nc-threads-background',
templateUrl: './threads-background.component.html',
styleUrls: ['./threads-background.component.less']
}]
}], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { containerRef: [{
type: ViewChild,
args: ['container', { static: true }]
}], ncColor: [{
type: Input
}], ncAmplitude: [{
type: Input
}], ncDistance: [{
type: Input
}], ncBgColor: [{
type: Input
}], ncEnableMouseInteraction: [{
type: Input
}], onMouseMove: [{
type: HostListener,
args: ['mousemove', ['$event']]
}], onMouseLeave: [{
type: HostListener,
args: ['mouseleave']
}] } });
class NcThreadsBackgroundModule {
}
NcThreadsBackgroundModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcThreadsBackgroundModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
NcThreadsBackgroundModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcThreadsBackgroundModule, declarations: [ThreadsBackgroundComponent], imports: [CommonModule], exports: [ThreadsBackgroundComponent] });
NcThreadsBackgroundModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcThreadsBackgroundModule, imports: [[
CommonModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NcThreadsBackgroundModule, decorators: [{
type: NgModule,
args: [{
declarations: [
ThreadsBackgroundComponent
],
imports: [
CommonModule
],
exports: [
ThreadsBackgroundComponent
]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { NcThreadsBackgroundModule, ThreadsBackgroundComponent };
//# sourceMappingURL=ng-cw-v12-threads-background.js.map