UNPKG

ng-magnizoom

Version:

Angular image magnifier (based on HTML canvas)

331 lines (324 loc) 14.8 kB
import * as i0 from '@angular/core'; import { Injectable, EventEmitter, Component, Input, Output, ViewChild, NgModule } from '@angular/core'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; class MagnizoomService { constructor() { } } /** @nocollapse */ MagnizoomService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: MagnizoomService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); /** @nocollapse */ MagnizoomService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: MagnizoomService, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: MagnizoomService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return []; } }); class NgMagnizoomComponent { get canvasWidth() { return this.image && this.image.width || 800; } get canvasHeight() { return this.image && this.image.height || 600; } constructor() { this.zoomMode = 'COVER'; this.minZoomFactor = 1.2; this.maxZoomFactor = 3; this.zoomFactor = 2; this.zoomFactorChange = new EventEmitter(); this.lensSizeUnit = 'NORMALIZED'; this.lensSize = { width: 0.5, height: 0.5 }; this.zoomCenterUnit = 'NORMALIZED'; this.zoomCenterChange = new EventEmitter(); this.updateOnMouseEvents = true; this.imageReady = false; } ngOnInit() { this.initContext(); this.loadImage(this.imageSrc); } ngOnChanges(changes) { if (!changes) { return; } if (changes.lensSize || changes.zoomCenter || changes.zoomFactor) { this.updateParameters(); } if (changes.imageSrc && !changes.imageSrc.firstChange) { this.loadImage(changes.imageSrc.currentValue); } } initContext() { this.canvas = this.mainCanvasRef.nativeElement; this.context = this.canvas.getContext('2d'); } loadImage(src) { this.image = new Image(); this.image.onload = () => { this.imageReady = true; this.updateParameters(); setTimeout(() => this.update()); }; this.image.src = src; } updateParameters() { if (this.lensSizeUnit === 'NORMALIZED') { if (this.imageReady) { this._lensSize = { width: this.lensSize.width * this.image.width, height: this.lensSize.height * this.image.height }; } } else { this._lensSize = { width: this.lensSize.width, height: this.lensSize.height }; } if (!this.zoomCenter) { this._centerPosition = undefined; } else if (this.zoomCenterUnit === 'NORMALIZED') { if (this.imageReady) { this._centerPosition = { x: this.zoomCenter.x * this.image.width, y: this.zoomCenter.y * this.image.height }; } } else { this._centerPosition = { x: this.zoomCenter.x, y: this.zoomCenter.y }; } this._zoomFactor = this.zoomFactor; if (this._zoomFactor > this.maxZoomFactor) { this._zoomFactor = this.maxZoomFactor; } if (this._zoomFactor < this.minZoomFactor) { this._zoomFactor = this.minZoomFactor; } this.update(); } update() { this.render(); let currUnitCenter, needUpdate = false; if (!this._centerPosition) { currUnitCenter = undefined; needUpdate = currUnitCenter !== this.zoomCenter; } else if (this.zoomCenterUnit === 'NORMALIZED') { if (this.imageReady) { currUnitCenter = { x: this._centerPosition.x / this.image.width, y: this._centerPosition.y / this.image.height }; needUpdate = currUnitCenter.x !== this.zoomCenter?.x || currUnitCenter.y !== this.zoomCenter?.y; } } else { currUnitCenter = { x: this._centerPosition.x, y: this._centerPosition.y }; needUpdate = currUnitCenter.x !== this.zoomCenter?.x || currUnitCenter.y !== this.zoomCenter?.y; } if (needUpdate) { this.zoomCenterChange.emit(currUnitCenter); } } render() { if (!this.context || !this.imageReady) { return; } this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight); // clear canvas this.context.lineWidth = 1; // border width this.context.drawImage(this.image, 0, 0); // bg image if (this._centerPosition) { switch (this.zoomMode) { case 'LENS': this.renderLensMode(); break; case 'COVER': this.renderCoverMode(); break; } } } renderLensMode() { this.context.lineWidth = 5; // border width const zoomRect = this.getZoomRect(); this.context.fillRect(zoomRect.x, zoomRect.y, zoomRect.w, zoomRect.h); // bg (clear) const clippingRect = this.getClippingRect(); // zoom image this.context.drawImage(this.image, clippingRect.x, clippingRect.y, clippingRect.w, clippingRect.h, zoomRect.x, zoomRect.y, zoomRect.w, zoomRect.h); this.context.strokeRect(zoomRect.x, zoomRect.y, zoomRect.w, zoomRect.h); // border } renderCoverMode() { const covertRect = this.getCoverRect(); this.context.drawImage(this.image, covertRect.x, covertRect.y, covertRect.w, covertRect.h, 0, 0, this.canvasWidth, this.canvasHeight); // cover image } getZoomRect() { const w = this._lensSize.width; const h = this._lensSize.height; const x = this._centerPosition.x - (w / 2); const y = this._centerPosition.y - (h / 2); return this.clampRect(x, y, w, h); } getClippingRect() { const w = this._lensSize.width / this._zoomFactor; const h = this._lensSize.height / this._zoomFactor; const x = this._centerPosition.x - (w / 2); const y = this._centerPosition.y - (h / 2); return this.clampRect(x, y, w, h); } getCoverRect() { const w = this.canvasWidth / this._zoomFactor; const h = this.canvasHeight / this._zoomFactor; // const x = this.mousePosition.x - (w / 2); // const y = this.mousePosition.y - (h / 2); const x = this._centerPosition.x - this._centerPosition.x / this._zoomFactor; const y = this._centerPosition.y - this._centerPosition.y / this._zoomFactor; return this.clampRect(x, y, w, h); } clampRect(x, y, w, h) { if (x <= 0) { x = 0; } if (x + w >= this.canvasWidth) { x = this.canvasWidth - w; } if (y < 0) { y = 0; } if (y + h >= this.canvasHeight) { y = this.canvasHeight - h; } return { x, y, w, h }; } calculateMousePosition(clientX, clientY) { const boundingRect = this.canvas.getBoundingClientRect(); const boundingRectX = (clientX - boundingRect.left); const boundingRectY = (clientY - boundingRect.top); let elementX = boundingRectX, elementY = boundingRectY; let elementWidth = boundingRect.width, elementHeight = boundingRect.height; const computedStyle = window.getComputedStyle(this.canvas, null); const domMatrix = new DOMMatrix(computedStyle.transform); if (!domMatrix.isIdentity) { const { a, b } = domMatrix; const transformRot = Math.atan2(b, a); const transformScale = Math.round(Math.sqrt(a * a + b * b) * 100) / 100; if (transformRot) { const boundingRectCenterX = boundingRect.width / 2; const boundingRectCenterY = boundingRect.height / 2; const dx = (boundingRectX - boundingRectCenterX); const dy = (boundingRectY - boundingRectCenterY); const d = Math.sqrt(dx * dx + dy * dy); const r = Math.atan2(dy, dx); elementWidth = this.canvas.clientWidth * transformScale; elementHeight = this.canvas.clientHeight * transformScale; elementX = (elementWidth / 2) + (d * Math.cos(r - transformRot)); elementY = (elementHeight / 2) + (d * Math.sin(r - transformRot)); } } const viewToModelX = this.canvasWidth / elementWidth; const viewToModelY = this.canvasHeight / elementHeight; const x = elementX * viewToModelX; const y = elementY * viewToModelY; this._centerPosition = { x, y }; } onMouseLeave(event) { if (!this.updateOnMouseEvents) { return; } this._centerPosition = null; this.update(); } onMouseEnterOrMove(event) { if (!this.updateOnMouseEvents) { return; } this.calculateMousePosition(event.clientX, event.clientY); this.update(); } onMouseScroll(event) { if (!this.updateOnMouseEvents) { return; } let newZoomFactor = this._zoomFactor; newZoomFactor -= event.deltaY / 1000; if (newZoomFactor < this.minZoomFactor) { newZoomFactor = this.minZoomFactor; } if (newZoomFactor > this.maxZoomFactor) { newZoomFactor = this.maxZoomFactor; } if (this._zoomFactor !== newZoomFactor) { this._zoomFactor = newZoomFactor; if (this.zoomFactor !== this._zoomFactor) { this.zoomFactorChange.emit(this._zoomFactor); } this.update(); } event.preventDefault(); event.stopPropagation(); } } /** @nocollapse */ NgMagnizoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); /** @nocollapse */ NgMagnizoomComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: NgMagnizoomComponent, selector: "ng-magnizoom", inputs: { imageSrc: "imageSrc", zoomMode: "zoomMode", minZoomFactor: "minZoomFactor", maxZoomFactor: "maxZoomFactor", zoomFactor: "zoomFactor", lensSizeUnit: "lensSizeUnit", lensSize: "lensSize", zoomCenterUnit: "zoomCenterUnit", zoomCenter: "zoomCenter", updateOnMouseEvents: "updateOnMouseEvents", imageStyle: "imageStyle", imageClass: "imageClass" }, outputs: { zoomFactorChange: "zoomFactorChange", zoomCenterChange: "zoomCenterChange" }, viewQueries: [{ propertyName: "mainCanvasRef", first: true, predicate: ["mainCanvas"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<canvas #mainCanvas\n class=\"main-canvas\"\n [ngClass]=\"imageClass\"\n [ngStyle]=\"imageStyle\"\n [width]=\"canvasWidth\"\n [height]=\"canvasHeight\"\n (mouseleave)=\"onMouseLeave($event)\"\n (mouseenter)=\"onMouseEnterOrMove($event)\"\n (mousemove)=\"onMouseEnterOrMove($event)\"\n (wheel)=\"onMouseScroll($event)\">\n</canvas>\n", styles: [""], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomComponent, decorators: [{ type: Component, args: [{ selector: 'ng-magnizoom', template: "<canvas #mainCanvas\n class=\"main-canvas\"\n [ngClass]=\"imageClass\"\n [ngStyle]=\"imageStyle\"\n [width]=\"canvasWidth\"\n [height]=\"canvasHeight\"\n (mouseleave)=\"onMouseLeave($event)\"\n (mouseenter)=\"onMouseEnterOrMove($event)\"\n (mousemove)=\"onMouseEnterOrMove($event)\"\n (wheel)=\"onMouseScroll($event)\">\n</canvas>\n" }] }], ctorParameters: function () { return []; }, propDecorators: { imageSrc: [{ type: Input }], zoomMode: [{ type: Input }], minZoomFactor: [{ type: Input }], maxZoomFactor: [{ type: Input }], zoomFactor: [{ type: Input }], zoomFactorChange: [{ type: Output }], lensSizeUnit: [{ type: Input }], lensSize: [{ type: Input }], zoomCenterUnit: [{ type: Input }], zoomCenter: [{ type: Input }], zoomCenterChange: [{ type: Output }], updateOnMouseEvents: [{ type: Input }], imageStyle: [{ type: Input }], imageClass: [{ type: Input }], mainCanvasRef: [{ type: ViewChild, args: ['mainCanvas', { static: true }] }] } }); class NgMagnizoomModule { } /** @nocollapse */ NgMagnizoomModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); /** @nocollapse */ NgMagnizoomModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomModule, declarations: [NgMagnizoomComponent], imports: [CommonModule], exports: [NgMagnizoomComponent] }); /** @nocollapse */ NgMagnizoomModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomModule, imports: [CommonModule] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomModule, decorators: [{ type: NgModule, args: [{ declarations: [NgMagnizoomComponent], imports: [ CommonModule ], exports: [NgMagnizoomComponent] }] }] }); /* * Public API Surface of ng-magnizoom */ /** * Generated bundle index. Do not edit. */ export { MagnizoomService, NgMagnizoomComponent, NgMagnizoomModule }; //# sourceMappingURL=ng-magnizoom.mjs.map