ngx-eagle
Version:
UI component infrastructure and Design components for mobile and desktop Angular web applications.
165 lines (136 loc) • 5.31 kB
text/typescript
import { NgFor, NgIf } from '@angular/common';
import { AfterViewInit, booleanAttribute, Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChanges, ViewChild } from '@angular/core';
import { fromEvent, merge, Subscription } from 'rxjs';
import { pairwise, switchMap, takeUntil } from 'rxjs/operators';
export class SignatureComponent implements AfterViewInit, OnChanges, OnDestroy {
public canvas!: ElementRef<HTMLCanvasElement>;
pointerColors: string[] = ['#000000', '#2A7CFF'];
pointerColor: string = '#000000';
showClearButton = true;
showPointerColors: boolean = true;
signatureComplete = new EventEmitter<string>();
public dirty = false;
private ctx!: CanvasRenderingContext2D;
private subscriptions: Subscription[] = [];
public ngAfterViewInit() {
this.initCanvas();
this.captureEvents(this.canvas.nativeElement);
this.adjustCanvasToContainer();
}
public ngOnChanges(changes: SimpleChanges): void {
if (changes['pointerColor'] && this.ctx) {
this.ctx.strokeStyle = changes['pointerColor'].currentValue;
}
}
public ngOnDestroy(): void {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}
private initCanvas(): void {
const canvasEl: HTMLCanvasElement = this.canvas.nativeElement;
this.ctx = canvasEl.getContext('2d')!;
this.ctx.lineWidth = 2;
this.ctx.lineCap = 'round';
this.ctx.strokeStyle = this.pointerColor;
}
private captureEvents(canvasEl: HTMLCanvasElement) {
const mouseDown$ = fromEvent(canvasEl, 'mousedown');
const mouseMove$ = fromEvent(canvasEl, 'mousemove');
const mouseUp$ = fromEvent(window, 'mouseup');
const mouseLeave$ = fromEvent(canvasEl, 'mouseleave');
const touchStart$ = fromEvent(canvasEl, 'touchstart');
const touchMove$ = fromEvent(canvasEl, 'touchmove');
const touchEnd$ = fromEvent(window, 'touchend');
const touchLeave$ = fromEvent(canvasEl, 'touchcancel');
const startDrawing$ = merge(mouseDown$, touchStart$);
const moveDrawing$ = merge(mouseMove$, touchMove$);
const endDrawing$ = merge(mouseUp$, touchEnd$);
const leaveDrawing$ = merge(mouseLeave$, touchLeave$);
const drawing$ = startDrawing$.pipe(
switchMap(() => moveDrawing$.pipe(
takeUntil(endDrawing$),
takeUntil(leaveDrawing$),
pairwise()
))
);
const subscription = drawing$.subscribe(([prev, current]: [Event, Event]) => {
const rect = canvasEl.getBoundingClientRect();
let prevPos, currentPos;
if (prev instanceof MouseEvent && current instanceof MouseEvent) {
prevPos = {
x: prev.clientX - rect.left,
y: prev.clientY - rect.top
};
currentPos = {
x: current.clientX - rect.left,
y: current.clientY - rect.top
};
}
if (prev instanceof TouchEvent && current instanceof TouchEvent) {
prevPos = {
x: prev.touches[0].clientX - rect.left,
y: prev.touches[0].clientY - rect.top
};
currentPos = {
x: current.touches[0].clientX - rect.left,
y: current.touches[0].clientY - rect.top
};
}
if (prevPos && currentPos) {
this.drawOnCanvas(prevPos, currentPos);
}
});
const stopDrawingSubscription = endDrawing$.subscribe(() => {
const image = this.getImage();
this.signatureComplete.emit(image);
});
this.subscriptions.push(subscription, stopDrawingSubscription);
}
private drawOnCanvas(prevPos: { x: number, y: number }, currentPos: { x: number, y: number }) {
if (!this.ctx) { return; }
this.ctx.beginPath();
this.ctx.moveTo(prevPos.x, prevPos.y);
this.ctx.lineTo(currentPos.x, currentPos.y);
this.ctx.stroke();
this.dirty = true;
}
public clean() {
const canvas: HTMLCanvasElement = this.canvas.nativeElement;
const width = canvas.width;
const height = canvas.height;
this.ctx.clearRect(0, 0, width, height);
this.dirty = false;
const image = this.getImage();
this.signatureComplete.emit(image);
}
public changeColor(color: string) {
this.pointerColor = color;
this.ctx.strokeStyle = color;
}
public getImage(): string {
return this.canvas.nativeElement.toDataURL();
}
private adjustCanvasToContainer(): void {
const resizeObserver = new ResizeObserver(() => {
const canvasEl: HTMLCanvasElement = this.canvas.nativeElement;
const container = canvasEl.parentElement;
if (container) {
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
canvasEl.width = containerWidth;
canvasEl.height = containerHeight;
this.ctx.lineWidth = 2;
this.ctx.lineCap = 'round';
this.ctx.strokeStyle = this.pointerColor;
this.dirty = false;
}
});
resizeObserver.observe(this.canvas.nativeElement.parentElement!);
}
}