Add interactivity
All checks were successful
publish.yml / publish (push) Successful in 56s

This commit is contained in:
2026-04-08 09:27:45 +02:00
parent 00a49f0a11
commit e06a5a29b4

View File

@@ -180,6 +180,7 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy {
private pointerX = 0; private pointerX = 0;
private pointerY = 0; private pointerY = 0;
private pointerActive = false; private pointerActive = false;
private activeTouchId: number | null = null;
private reducedMotion = false; private reducedMotion = false;
private mediaQuery!: MediaQueryList; private mediaQuery!: MediaQueryList;
@@ -203,25 +204,61 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy {
this.isCursorVisible = this.getMediaQuery('(pointer: fine)').matches; this.isCursorVisible = this.getMediaQuery('(pointer: fine)').matches;
const onResize = () => this.resizeCanvas(); const onResize = () => this.resizeCanvas();
const onMove = (event: PointerEvent) => this.handlePointerMove(event); const supportsPointerEvents = 'PointerEvent' in window;
const onLeave = () => this.handlePointerLeave(); const onPointerMove = (event: PointerEvent) => this.handlePointerMove(event);
const onDown = () => (this.isPointerDown = true); const onPointerLeave = () => this.handlePointerLeave();
const onUp = () => (this.isPointerDown = false); const onPointerDown = (event: PointerEvent) => this.handlePointerDown(event);
const onPointerUp = (event: PointerEvent) => this.handlePointerUp(event);
const onPointerCancel = () => this.handlePointerLeave();
const onMouseMove = (event: MouseEvent) => this.handleMouseMove(event);
const onMouseLeave = () => this.handlePointerLeave();
const onMouseDown = (event: MouseEvent) => this.handleMouseDown(event);
const onMouseUp = () => this.handleMouseUp();
const onTouchStart = (event: TouchEvent) => this.handleTouchStart(event);
const onTouchMove = (event: TouchEvent) => this.handleTouchMove(event);
const onTouchEnd = (event: TouchEvent) => this.handleTouchEnd(event);
const onTouchCancel = () => this.handlePointerLeave();
const onMotionChange = (event: MediaQueryListEvent) => this.handleMotionPreferenceChange(event.matches); const onMotionChange = (event: MediaQueryListEvent) => this.handleMotionPreferenceChange(event.matches);
window.addEventListener('resize', onResize); window.addEventListener('resize', onResize);
window.addEventListener('pointermove', onMove, { passive: true });
window.addEventListener('pointerleave', onLeave); if (supportsPointerEvents) {
window.addEventListener('pointerdown', onDown, { passive: true }); window.addEventListener('pointermove', onPointerMove, { passive: true });
window.addEventListener('pointerup', onUp, { passive: true }); window.addEventListener('pointerleave', onPointerLeave);
window.addEventListener('pointerdown', onPointerDown, { passive: true });
window.addEventListener('pointerup', onPointerUp, { passive: true });
window.addEventListener('pointercancel', onPointerCancel, { passive: true });
} else {
window.addEventListener('mousemove', onMouseMove, { passive: true });
window.addEventListener('mouseleave', onMouseLeave);
window.addEventListener('mousedown', onMouseDown, { passive: true });
window.addEventListener('mouseup', onMouseUp, { passive: true });
window.addEventListener('touchstart', onTouchStart, { passive: true });
window.addEventListener('touchmove', onTouchMove, { passive: true });
window.addEventListener('touchend', onTouchEnd, { passive: true });
window.addEventListener('touchcancel', onTouchCancel, { passive: true });
}
this.mediaQuery.addEventListener('change', onMotionChange); this.mediaQuery.addEventListener('change', onMotionChange);
this.destroyCallbacks = [ this.destroyCallbacks = [
() => window.removeEventListener('resize', onResize), () => window.removeEventListener('resize', onResize),
() => window.removeEventListener('pointermove', onMove), () => window.removeEventListener('pointermove', onPointerMove),
() => window.removeEventListener('pointerleave', onLeave), () => window.removeEventListener('pointerleave', onPointerLeave),
() => window.removeEventListener('pointerdown', onDown), () => window.removeEventListener('pointerdown', onPointerDown),
() => window.removeEventListener('pointerup', onUp), () => window.removeEventListener('pointerup', onPointerUp),
() => window.removeEventListener('pointercancel', onPointerCancel),
() => window.removeEventListener('mousemove', onMouseMove),
() => window.removeEventListener('mouseleave', onMouseLeave),
() => window.removeEventListener('mousedown', onMouseDown),
() => window.removeEventListener('mouseup', onMouseUp),
() => window.removeEventListener('touchstart', onTouchStart),
() => window.removeEventListener('touchmove', onTouchMove),
() => window.removeEventListener('touchend', onTouchEnd),
() => window.removeEventListener('touchcancel', onTouchCancel),
() => this.mediaQuery.removeEventListener('change', onMotionChange), () => this.mediaQuery.removeEventListener('change', onMotionChange),
]; ];
} }
@@ -282,18 +319,115 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy {
} }
private handlePointerMove(event: PointerEvent): void { private handlePointerMove(event: PointerEvent): void {
this.pointerX = event.clientX; if (event.pointerType === 'touch' && this.activeTouchId !== null && event.pointerId !== this.activeTouchId) {
this.pointerY = event.clientY; return;
this.pointerActive = true; }
this.cursorTransform = `translate3d(${event.clientX}px, ${event.clientY}px, 0)`;
this.updatePointer(event.clientX, event.clientY, true);
}
private handlePointerDown(event: PointerEvent): void {
if (event.pointerType === 'touch') {
this.activeTouchId = event.pointerId;
}
this.isPointerDown = true;
this.updatePointer(event.clientX, event.clientY, true);
}
private handlePointerUp(event: PointerEvent): void {
this.isPointerDown = false;
if (event.pointerType === 'touch') {
if (this.activeTouchId !== null && event.pointerId !== this.activeTouchId) {
return;
}
this.activeTouchId = null;
this.pointerActive = false;
return;
}
this.updatePointer(event.clientX, event.clientY, true);
}
private handleMouseMove(event: MouseEvent): void {
this.updatePointer(event.clientX, event.clientY, true);
}
private handleMouseDown(event: MouseEvent): void {
this.isPointerDown = true;
this.updatePointer(event.clientX, event.clientY, true);
}
private handleMouseUp(): void {
this.isPointerDown = false;
}
private handleTouchStart(event: TouchEvent): void {
const touch = event.changedTouches.item(0);
if (!touch) {
return;
}
this.activeTouchId = touch.identifier;
this.isPointerDown = true;
this.updatePointer(touch.clientX, touch.clientY, true);
}
private handleTouchMove(event: TouchEvent): void {
if (this.activeTouchId === null) {
return;
}
const touch = this.findTouch(event.touches, this.activeTouchId);
if (!touch) {
return;
}
this.updatePointer(touch.clientX, touch.clientY, true);
}
private handleTouchEnd(event: TouchEvent): void {
if (this.activeTouchId === null) {
return;
}
const touch = this.findTouch(event.changedTouches, this.activeTouchId);
if (!touch) {
return;
}
this.isPointerDown = false;
this.activeTouchId = null;
this.pointerActive = false;
} }
private handlePointerLeave(): void { private handlePointerLeave(): void {
this.pointerActive = false; this.pointerActive = false;
this.isPointerDown = false; this.isPointerDown = false;
this.activeTouchId = null;
this.cursorTransform = 'translate3d(-9999px, -9999px, 0)'; this.cursorTransform = 'translate3d(-9999px, -9999px, 0)';
} }
private updatePointer(x: number, y: number, active: boolean): void {
this.pointerX = x;
this.pointerY = y;
this.pointerActive = active;
this.cursorTransform = `translate3d(${x}px, ${y}px, 0)`;
}
private findTouch(touches: TouchList, identifier: number): Touch | null {
for (let index = 0; index < touches.length; index += 1) {
const touch = touches.item(index);
if (touch && touch.identifier === identifier) {
return touch;
}
}
return null;
}
private syncPointer(): void { private syncPointer(): void {
this.particleEngine?.setPointer({ this.particleEngine?.setPointer({
x: this.pointerX, x: this.pointerX,
@@ -342,3 +476,4 @@ export class InteractiveBackground implements AfterViewInit, OnDestroy {