ngx-parallax-stars
Version:
Angular library to create beautiful stars with parallax effect.
219 lines (213 loc) • 9.13 kB
JavaScript
import * as i0 from '@angular/core';
import { input, inject, Renderer2, ElementRef, signal, effect, Component, ChangeDetectionStrategy } from '@angular/core';
import { Observable, filter, debounceTime, distinctUntilChanged } from 'rxjs';
import { toSignal } from '@angular/core/rxjs-interop';
/**
* Default star layers that are rendered when no layers are provided to the ngx-parallax-stars component
*/
const defaultStarLayers = [
{
color: '#ffffff',
speed: 10,
density: 7,
size: 1,
direction: 'up',
blur: 5,
glow: 1,
isRound: false,
},
{
color: '#ffffff',
speed: 15,
density: 2,
size: 2,
direction: 'up',
blur: 1,
glow: 2,
isRound: false,
},
{
color: '#ffffff',
speed: 20,
density: 1,
size: 3,
direction: 'up',
blur: 0,
glow: 5,
isRound: false,
},
];
/** @internal */
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/** @internal */
function resizeObservable(element) {
return new Observable((subscriber) => {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const contentRect = entry.contentRect;
if (Math.abs(contentRect.height) < 1 && Math.abs(contentRect.width) < 1)
continue;
return subscriber.next({
width: contentRect.width,
height: contentRect.height,
});
}
});
resizeObserver.observe(element);
return function unsubscribe() {
resizeObserver.unobserve(element);
};
});
}
class NgxParallaxStarsComponent {
#renderer;
#elRef;
#initialized;
#size;
constructor() {
/**
* Overrides default star layers
*/
this.layers = input(defaultStarLayers, ...(ngDevMode ? [{ debugName: "layers" }] : []));
/**
* If responsive mode is enabled, the component will automatically be re-rendered when its size changes
*/
this.responsive = input(true, ...(ngDevMode ? [{ debugName: "responsive" }] : []));
this.#renderer = inject(Renderer2);
this.#elRef = inject(ElementRef);
this.#initialized = signal(false, ...(ngDevMode ? [{ debugName: "#initialized" }] : []));
this.#size = toSignal(resizeObservable(this.#elRef.nativeElement).pipe(filter(() => this.responsive() || !this.#initialized()), debounceTime(100), distinctUntilChanged()));
effect(() => {
const size = this.#size();
if (size) {
this.#renderStars(this.layers(), size.width, size.height);
this.#initialized.set(true);
}
});
}
#renderStars(layers, width, height) {
this.#renderer.setProperty(this.#elRef.nativeElement, 'innerHTML', '');
layers.forEach((layer) => {
const boxShadow = this.#createBoxShadow(width, height, layer.density, layer.color, layer.blur, layer.glow);
const layerElement = this.createLayerElement(layer, boxShadow, width, height);
const afterLayerElement = this.createAfterLayerElement(layer, boxShadow, height, width);
this.#renderer.appendChild(layerElement, afterLayerElement);
this.#renderer.appendChild(this.#elRef.nativeElement, layerElement);
});
}
createAfterLayerElement(layer, boxShadow, height, width) {
const afterLayerElement = this.#renderer.createElement('span');
this.#renderer.setStyle(afterLayerElement, 'content', ' ');
this.#renderer.setStyle(afterLayerElement, 'position', 'absolute');
this.#renderer.setStyle(afterLayerElement, 'width', `${layer.size}px`);
this.#renderer.setStyle(afterLayerElement, 'height', `${layer.size}px`);
this.#renderer.setStyle(afterLayerElement, 'background', 'transparent');
this.#renderer.setStyle(afterLayerElement, 'boxShadow', boxShadow);
switch (layer.direction) {
case 'up':
this.#renderer.setStyle(afterLayerElement, 'top', `${height}px`);
break;
case 'down':
this.#renderer.setStyle(afterLayerElement, 'bottom', `${height}px`);
break;
case 'left':
this.#renderer.setStyle(afterLayerElement, 'left', `${width}px`);
break;
case 'right':
this.#renderer.setStyle(afterLayerElement, 'right', `${width}px`);
break;
}
return afterLayerElement;
}
createLayerElement(layer, boxShadow, width, height) {
const layerElement = this.#renderer.createElement('div');
this.#renderer.setStyle(layerElement, 'width', `${layer.size}px`);
this.#renderer.setStyle(layerElement, 'height', `${layer.size}px`);
this.#renderer.setStyle(layerElement, 'background', 'transparent');
this.#renderer.setStyle(layerElement, 'boxShadow', boxShadow);
this.#renderer.setStyle(layerElement, 'borderRadius', layer.isRound ? '50%' : '0');
layerElement.animate(this.#createKeyFramesForLayer(layer, width, height), {
duration: 1000000 / layer.speed,
iterations: Infinity,
});
return layerElement;
}
#createKeyFramesForLayer(layer, width, height) {
switch (layer.direction) {
case 'up': {
return [
{
transform: 'translateY(0)',
},
{
transform: `translateY(-${height}px)`,
},
];
}
case 'down': {
return [
{
transform: 'translateY(0)',
},
{
transform: `translateY(${height}px)`,
},
];
}
case 'left': {
return [
{
transform: 'translateX(0)',
},
{
transform: `translateX(-${width}px)`,
},
];
}
case 'right': {
return [
{
transform: 'translateX(0)',
},
{
transform: `translateX(${width}px)`,
},
];
}
default: {
throw new Error(`Invalid direction: ${layer.direction}`);
}
}
}
#createBoxShadow(width, height, density, color, blur, glow) {
const count = this.#calculateCount(density, width, height);
const shadows = [];
for (let i = 0; i < count; i++) {
const xPos = randomInt(1, width);
const yPos = randomInt(1, height);
const starShadow = `${xPos}px ${yPos}px ${blur}px 0 ${color}`;
shadows.push(starShadow);
if (glow !== 0) {
const glowShadow = `${xPos}px ${yPos}px ${glow}px 0 ${color}`;
shadows.push(glowShadow);
}
}
return shadows.join(',');
}
#calculateCount(density, width, height) {
return density * (width / 100) * (height / 100);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxParallaxStarsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.1.1", type: NgxParallaxStarsComponent, isStandalone: true, selector: "ngx-parallax-stars", inputs: { layers: { classPropertyName: "layers", publicName: "layers", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, styles: [":host{display:block;overflow:hidden}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: NgxParallaxStarsComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-parallax-stars', imports: [], template: '', changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;overflow:hidden}\n"] }]
}], ctorParameters: () => [], propDecorators: { layers: [{ type: i0.Input, args: [{ isSignal: true, alias: "layers", required: false }] }], responsive: [{ type: i0.Input, args: [{ isSignal: true, alias: "responsive", required: false }] }] } });
/**
* Generated bundle index. Do not edit.
*/
export { NgxParallaxStarsComponent, defaultStarLayers };
//# sourceMappingURL=ngx-parallax-stars.mjs.map